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')}
+
+
+ ))}
+
+
+
+
+
+ {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'];