diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 4ef86e94b6..e8499493e8 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1,5 +1,6 @@ { "accessibility": { + "about": "About", "copyMetadataJson": "Copy metadata JSON", "createIssue": "Create Issue", "exitViewer": "Exit Viewer", @@ -74,6 +75,8 @@ } }, "common": { + "aboutDesc": "Using Invoke for work? Check out:", + "aboutHeading": "Own Your Creative Power", "accept": "Accept", "advanced": "Advanced", "advancedOptions": "Advanced Options", @@ -136,6 +139,7 @@ "load": "Load", "loading": "Loading", "loadingInvokeAI": "Loading Invoke AI", + "localSystem": "Local System", "learnMore": "Learn More", "modelManager": "Model Manager", "nodeEditor": "Node Editor", diff --git a/invokeai/frontend/web/src/features/system/components/AboutModal/AboutModal.tsx b/invokeai/frontend/web/src/features/system/components/AboutModal/AboutModal.tsx new file mode 100644 index 0000000000..3dc8790c00 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/components/AboutModal/AboutModal.tsx @@ -0,0 +1,138 @@ +import { ExternalLinkIcon } from '@chakra-ui/icons'; +import { + Flex, + Grid, + GridItem, + Image, + Link, + useDisclosure, +} from '@chakra-ui/react'; +import { InvHeading } from 'common/components/InvHeading/wrapper'; +import { + InvModal, + InvModalBody, + InvModalCloseButton, + InvModalContent, + InvModalFooter, + InvModalHeader, + InvModalOverlay, +} from 'common/components/InvModal/wrapper'; +import { InvText } from 'common/components/InvText/wrapper'; +import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; +import { + discordLink, + githubLink, + websiteLink, +} from 'features/system/store/constants'; +import { map } from 'lodash-es'; +import InvokeLogoYellow from 'public/assets/images/invoke-tag-lrg.svg'; +import type { ReactElement } from 'react'; +import { cloneElement, memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + useGetAppDepsQuery, + useGetAppVersionQuery, +} from 'services/api/endpoints/appInfo'; + +type AboutModalProps = { + /* The button to open the Settings Modal */ + children: ReactElement; +}; + +const AboutModal = ({ children }: AboutModalProps) => { + const { isOpen, onOpen, onClose } = useDisclosure(); + const { t } = useTranslation(); + const { deps } = useGetAppDepsQuery(undefined, { + selectFromResult: ({ data }) => ({ + deps: data ? map(data, (version, name) => ({ name, version })) : [], + }), + }); + const { data: appVersion } = useGetAppVersionQuery(); + + return ( + <> + {cloneElement(children, { + onClick: onOpen, + })} + + + + {t('accessibility.about')} + + + + + + + {t('common.localSystem')} + + {deps.map(({ name, version }, i) => ( + + {name} + + {version ? version : t('common.notInstalled')} + + + ))} + + + + + invoke-logo + {appVersion && {`v${appVersion?.version}`}} + + + + {t('common.githubLabel')} + + + + + + {t('common.discordLabel')} + + + + + + {t('common.aboutHeading')} + + {t('common.aboutDesc')} + + {websiteLink} + + + + + + + + + + ); +}; + +export default memo(AboutModal); diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx index 518d53c6d5..f630e2fe59 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx @@ -8,12 +8,15 @@ import { InvMenuGroup, } from 'common/components/InvMenu/wrapper'; import { useGlobalMenuClose } from 'common/hooks/useGlobalMenuClose'; +import AboutModal from 'features/system/components/AboutModal/AboutModal'; import HotkeysModal from 'features/system/components/HotkeysModal/HotkeysModal'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; +import { discordLink, githubLink } from 'features/system/store/constants'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiBugBeetleBold, + PiInfoBold, PiKeyboardBold, PiToggleRightFill, } from 'react-icons/pi'; @@ -29,9 +32,6 @@ const SettingsMenu = () => { const isDiscordLinkEnabled = useFeatureStatus('discordLink').isFeatureEnabled; const isGithubLinkEnabled = useFeatureStatus('githubLink').isFeatureEnabled; - const githubLink = 'http://github.com/invoke-ai/InvokeAI'; - const discordLink = 'https://discord.gg/ZmtBAhwWhy'; - return ( { + + + }> + {t('accessibility.about')} + + + ); diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx index 104061f08a..4449e2883b 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -207,7 +207,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { isCentered > - + {t('common.settingsLabel')} diff --git a/invokeai/frontend/web/src/features/system/store/constants.ts b/invokeai/frontend/web/src/features/system/store/constants.ts new file mode 100644 index 0000000000..68b363c509 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/store/constants.ts @@ -0,0 +1,3 @@ +export const githubLink = 'http://github.com/invoke-ai/InvokeAI'; +export const discordLink = 'https://discord.gg/ZmtBAhwWhy'; +export const websiteLink = 'https://www.invoke.com/'; diff --git a/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts b/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts index 9b314a6d76..8cde3c3528 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts @@ -1,5 +1,9 @@ import type { paths } from 'services/api/schema'; -import type { AppConfig, AppVersion } from 'services/api/types'; +import type { + AppConfig, + AppDependencyVersions, + AppVersion, +} from 'services/api/types'; import { api } from '..'; @@ -10,16 +14,21 @@ export const appInfoApi = api.injectEndpoints({ url: `app/version`, method: 'GET', }), - providesTags: ['AppVersion'], - keepUnusedDataFor: 86400000, // 1 day + providesTags: ['FetchOnReconnect'], + }), + getAppDeps: build.query({ + query: () => ({ + url: `app/app_deps`, + method: 'GET', + }), + providesTags: ['FetchOnReconnect'], }), getAppConfig: build.query({ query: () => ({ url: `app/config`, method: 'GET', }), - providesTags: ['AppConfig'], - keepUnusedDataFor: 86400000, // 1 day + providesTags: ['FetchOnReconnect'], }), getInvocationCacheStatus: build.query< paths['/api/v1/app/invocation_cache/status']['get']['responses']['200']['content']['application/json'], @@ -57,6 +66,7 @@ export const appInfoApi = api.injectEndpoints({ export const { useGetAppVersionQuery, + useGetAppDepsQuery, useGetAppConfigQuery, useClearInvocationCacheMutation, useDisableInvocationCacheMutation, diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index ae2500ecc3..1d5d3a22c8 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -35,6 +35,7 @@ export type InvocationJSONSchemaExtra = s['UIConfigBase']; // App Info export type AppVersion = s['AppVersion']; export type AppConfig = s['AppConfig']; +export type AppDependencyVersions = s['AppDependencyVersions']; // Images export type ImageDTO = s['ImageDTO'];