diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 57d993b27a..eb64b38c0b 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1103,6 +1103,7 @@ "info": "Info", "invoke": { "addingImagesTo": "Adding images to", + "modelDisabledForTrial": "Generating with {{modelName}} is not available on trial accounts. Visit your account settings to upgrade.", "invoke": "Invoke", "missingFieldTemplate": "Missing field template", "missingInputForField": "missing input", @@ -1183,7 +1184,8 @@ "width": "Width", "gaussianBlur": "Gaussian Blur", "boxBlur": "Box Blur", - "staged": "Staged" + "staged": "Staged", + "modelDisabledForTrial": "Generating with {{modelName}} is not available on trial accounts. Visit your account settings to upgrade." }, "dynamicPrompts": { "showDynamicPrompts": "Show Dynamic Prompts", diff --git a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx index 5225d908ac..b16c813b56 100644 --- a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx +++ b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx @@ -5,6 +5,7 @@ import type { StudioInitAction } from 'app/hooks/useStudioInitAction'; import { $didStudioInit } from 'app/hooks/useStudioInitAction'; import type { LoggingOverrides } from 'app/logging/logger'; import { $loggingOverrides, configureLogging } from 'app/logging/logger'; +import { $accountSettingsLink } from 'app/store/nanostores/accountSettingsLink'; import { $authToken } from 'app/store/nanostores/authToken'; import { $baseUrl } from 'app/store/nanostores/baseUrl'; import { $customNavComponent } from 'app/store/nanostores/customNavComponent'; @@ -46,6 +47,7 @@ interface Props extends PropsWithChildren { token?: string; config?: PartialAppConfig; customNavComponent?: ReactNode; + accountSettingsLink?: string; middleware?: Middleware[]; projectId?: string; projectName?: string; @@ -72,6 +74,7 @@ const InvokeAIUI = ({ token, config, customNavComponent, + accountSettingsLink, middleware, projectId, projectName, @@ -175,6 +178,16 @@ const InvokeAIUI = ({ }; }, [customNavComponent]); + useEffect(() => { + if (accountSettingsLink) { + $accountSettingsLink.set(accountSettingsLink); + } + + return () => { + $accountSettingsLink.set(undefined); + }; + }, [accountSettingsLink]); + useEffect(() => { if (openAPISchemaUrl) { $openAPISchemaUrl.set(openAPISchemaUrl); diff --git a/invokeai/frontend/web/src/app/store/nanostores/accountSettingsLink.ts b/invokeai/frontend/web/src/app/store/nanostores/accountSettingsLink.ts new file mode 100644 index 0000000000..cf41facb7c --- /dev/null +++ b/invokeai/frontend/web/src/app/store/nanostores/accountSettingsLink.ts @@ -0,0 +1,3 @@ +import { atom } from 'nanostores'; + +export const $accountSettingsLink = atom(undefined); diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 24ceb38095..701460e0ff 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -28,7 +28,8 @@ export type AppFeature = | 'starterModels' | 'hfToken' | 'retryQueueItem' - | 'cancelAndClearAll'; + | 'cancelAndClearAll' + | 'apiModels'; /** * A disable-able Stable Diffusion feature */ diff --git a/invokeai/frontend/web/src/features/parameters/components/MainModel/DisabledModelWarning.tsx b/invokeai/frontend/web/src/features/parameters/components/MainModel/DisabledModelWarning.tsx new file mode 100644 index 0000000000..4417a47969 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/MainModel/DisabledModelWarning.tsx @@ -0,0 +1,45 @@ +import { Flex, Link, Text } from '@invoke-ai/ui-library'; +import { useStore } from '@nanostores/react'; +import { $accountSettingsLink } from 'app/store/nanostores/accountSettingsLink'; +import { useAppSelector } from 'app/store/storeHooks'; +import { selectIsChatGTP4o, selectIsImagen3, selectModel } from 'features/controlLayers/store/paramsSlice'; +import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; +import { useMemo } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +export const DisabledModelWarning = () => { + const { t } = useTranslation(); + const model = useAppSelector(selectModel); + const isImagen3 = useAppSelector(selectIsImagen3); + const isChatGPT4o = useAppSelector(selectIsChatGTP4o); + const areApiModelsEnabled = useFeatureStatus('apiModels'); + const accountSettingsLink = useStore($accountSettingsLink); + + const isModelDisabled = useMemo(() => { + return (isImagen3 || isChatGPT4o) && !areApiModelsEnabled; + }, [isImagen3, isChatGPT4o, areApiModelsEnabled]); + + if (!isModelDisabled) { + return null; + } + + return ( + + + + {t('parameters.invoke.accountSettings')} + + ), + }} + /> + + + ); +}; diff --git a/invokeai/frontend/web/src/features/parameters/components/MainModel/ParamMainModelSelect.tsx b/invokeai/frontend/web/src/features/parameters/components/MainModel/ParamMainModelSelect.tsx index ecebcb63a2..404ebfbd95 100644 --- a/invokeai/frontend/web/src/features/parameters/components/MainModel/ParamMainModelSelect.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/MainModel/ParamMainModelSelect.tsx @@ -14,6 +14,8 @@ import { MdMoneyOff } from 'react-icons/md'; import { useMainModels } from 'services/api/hooks/modelsByType'; import { type AnyModelConfig, isCheckpointMainModelConfig, type MainModelConfig } from 'services/api/types'; +import { DisabledModelWarning } from './DisabledModelWarning'; + const ParamMainModelSelect = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -79,32 +81,35 @@ const ParamMainModelSelect = () => { }, [selectedModel]); return ( - - - {t('modelManager.model')} - - {isFluxDevSelected && ( - - - - + <> + + + + {t('modelManager.model')} - )} - - - - - - - - + {isFluxDevSelected && ( + + + + + + )} + + + + + + + + + ); }; diff --git a/invokeai/frontend/web/src/features/queue/store/readiness.ts b/invokeai/frontend/web/src/features/queue/store/readiness.ts index 3ba3b038be..752f0d1506 100644 --- a/invokeai/frontend/web/src/features/queue/store/readiness.ts +++ b/invokeai/frontend/web/src/features/queue/store/readiness.ts @@ -34,6 +34,7 @@ import { resolveBatchValue } from 'features/nodes/util/node/resolveBatchValue'; import type { UpscaleState } from 'features/parameters/store/upscaleSlice'; import { selectUpscaleSlice } from 'features/parameters/store/upscaleSlice'; import { getGridSize } from 'features/parameters/util/optimalDimension'; +import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { selectConfigSlice } from 'features/system/store/configSlice'; import { selectActiveTab } from 'features/ui/store/uiSelectors'; import type { TabName } from 'features/ui/store/uiTypes'; @@ -87,7 +88,8 @@ const debouncedUpdateReasons = debounce( upscale: UpscaleState, config: AppConfig, store: AppStore, - isInPublishFlow: boolean + isInPublishFlow: boolean, + areApiModelsEnabled: boolean ) => { if (tab === 'canvas') { const model = selectMainModelConfig(store.getState()); @@ -102,6 +104,7 @@ const debouncedUpdateReasons = debounce( canvasIsRasterizing, canvasIsCompositing, canvasIsSelectingObject, + areApiModelsEnabled, }); $reasonsWhyCannotEnqueue.set(reasons); } else if (tab === 'workflows') { @@ -149,6 +152,7 @@ export const useReadinessWatcher = () => { const canvasIsSelectingObject = useStore(canvasManager?.stateApi.$isSegmenting ?? $true); const canvasIsCompositing = useStore(canvasManager?.compositor.$isBusy ?? $true); const isInPublishFlow = useStore($isInPublishFlow); + const areApiModelsEnabled = useFeatureStatus('apiModels'); useEffect(() => { debouncedUpdateReasons( @@ -168,7 +172,8 @@ export const useReadinessWatcher = () => { upscale, config, store, - isInPublishFlow + isInPublishFlow, + areApiModelsEnabled ); }, [ store, @@ -188,6 +193,7 @@ export const useReadinessWatcher = () => { upscale, workflowSettings, isInPublishFlow, + areApiModelsEnabled, ]); }; @@ -335,6 +341,7 @@ const getReasonsWhyCannotEnqueueCanvasTab = (arg: { canvasIsRasterizing: boolean; canvasIsCompositing: boolean; canvasIsSelectingObject: boolean; + areApiModelsEnabled: boolean; }) => { const { isConnected, @@ -347,6 +354,7 @@ const getReasonsWhyCannotEnqueueCanvasTab = (arg: { canvasIsRasterizing, canvasIsCompositing, canvasIsSelectingObject, + areApiModelsEnabled, } = arg; const { positivePrompt } = params; const reasons: Reason[] = []; @@ -479,6 +487,10 @@ const getReasonsWhyCannotEnqueueCanvasTab = (arg: { } } + if ((model?.base === 'imagen3' || model?.base === 'chatgpt-4o') && !areApiModelsEnabled) { + reasons.push({ content: i18n.t('parameters.invoke.modelDisabledForTrial', { modelName: model.name }) }); + } + const enabledControlLayers = canvas.controlLayers.entities.filter((controlLayer) => controlLayer.isEnabled); // FLUX only supports 1x Control LoRA at a time. diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/GenerationSettingsAccordion/MainModelPicker.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/GenerationSettingsAccordion/MainModelPicker.tsx index 2f3063c3ee..092de0eec2 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/GenerationSettingsAccordion/MainModelPicker.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/GenerationSettingsAccordion/MainModelPicker.tsx @@ -27,6 +27,7 @@ import { typedMemo } from 'common/util/typedMemo'; import { $installModelsTab } from 'features/modelManagerV2/subpanels/InstallModels'; import { BASE_COLOR_MAP } from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelBaseBadge'; import ModelImage from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelImage'; +import { DisabledModelWarning } from 'features/parameters/components/MainModel/DisabledModelWarning'; import { NavigateToModelManagerButton } from 'features/parameters/components/MainModel/NavigateToModelManagerButton'; import { UseDefaultSettingsButton } from 'features/parameters/components/MainModel/UseDefaultSettingsButton'; import { modelSelected } from 'features/parameters/store/actions'; @@ -200,6 +201,7 @@ export const MainModelPicker = memo(() => { onClose={onClose} initialFocusRef={pickerRef.current?.inputRef} > + {t('modelManager.model')}