diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json
index b8cd3e02000..285356cb6e2 100644
--- a/invokeai/frontend/web/package.json
+++ b/invokeai/frontend/web/package.json
@@ -58,7 +58,7 @@
"@dagrejs/dagre": "^1.1.4",
"@dagrejs/graphlib": "^2.2.4",
"@fontsource-variable/inter": "^5.1.0",
- "@invoke-ai/ui-library": "^0.0.43",
+ "@invoke-ai/ui-library": "^0.0.44",
"@nanostores/react": "^0.7.3",
"@reduxjs/toolkit": "2.2.3",
"@roarr/browser-log-writer": "^1.3.0",
diff --git a/invokeai/frontend/web/pnpm-lock.yaml b/invokeai/frontend/web/pnpm-lock.yaml
index 17ead7a8e59..2251219af39 100644
--- a/invokeai/frontend/web/pnpm-lock.yaml
+++ b/invokeai/frontend/web/pnpm-lock.yaml
@@ -24,8 +24,8 @@ dependencies:
specifier: ^5.1.0
version: 5.1.0
'@invoke-ai/ui-library':
- specifier: ^0.0.43
- version: 0.0.43(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.1.0)(@types/react@18.3.11)(i18next@23.15.1)(react-dom@18.3.1)(react@18.3.1)
+ specifier: ^0.0.44
+ version: 0.0.44(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.1.0)(@types/react@18.3.11)(i18next@23.15.1)(react-dom@18.3.1)(react@18.3.1)
'@nanostores/react':
specifier: ^0.7.3
version: 0.7.3(nanostores@0.11.3)(react@18.3.1)
@@ -515,8 +515,8 @@ packages:
resolution: {integrity: sha512-MV6D4VLRIHr4PkW4zMyqfrNS1mPlCTiCXwvYGtDFQYr+xHFfonhAuf9WjsSc0nyp2m0OdkSLnzmVKkZFLo25Tg==}
dev: false
- /@chakra-ui/anatomy@2.3.4:
- resolution: {integrity: sha512-fFIYN7L276gw0Q7/ikMMlZxP7mvnjRaWJ7f3Jsf9VtDOi6eAYIBRrhQe6+SZ0PGmoOkRaBc7gSE5oeIbgFFyrw==}
+ /@chakra-ui/anatomy@2.3.5:
+ resolution: {integrity: sha512-3im33cUOxCbISjaBlINE2u8BOwJSCdzpjCX0H+0JxK2xz26UaVA5xeI3NYHUoxDnr/QIrgfrllGxS0szYwOcyg==}
dev: false
/@chakra-ui/breakpoint-utils@2.0.8:
@@ -573,12 +573,12 @@ packages:
react: 18.3.1
dev: false
- /@chakra-ui/hooks@2.4.2(react@18.3.1):
- resolution: {integrity: sha512-LRKiVE1oA7afT5tbbSKAy7Uas2xFHE6IkrQdbhWCHmkHBUtPvjQQDgwtnd4IRZPmoEfNGwoJ/MQpwOM/NRTTwA==}
+ /@chakra-ui/hooks@2.4.3(react@18.3.1):
+ resolution: {integrity: sha512-Sr2zsoTZw3p7HbrUy4aLpTIkE2XXUelAUgg3NGwMzrmx75bE0qVyiuuTFOuyEzGxYVV2Fe8QtcKKilm6RwzTGg==}
peerDependencies:
react: '>=18'
dependencies:
- '@chakra-ui/utils': 2.2.2(react@18.3.1)
+ '@chakra-ui/utils': 2.2.3(react@18.3.1)
'@zag-js/element-size': 0.31.1
copy-to-clipboard: 3.3.3
framesync: 6.1.2
@@ -596,13 +596,13 @@ packages:
react: 18.3.1
dev: false
- /@chakra-ui/icons@2.2.4(@chakra-ui/react@2.10.2)(react@18.3.1):
+ /@chakra-ui/icons@2.2.4(@chakra-ui/react@2.10.4)(react@18.3.1):
resolution: {integrity: sha512-l5QdBgwrAg3Sc2BRqtNkJpfuLw/pWRDwwT58J6c4PqQT6wzXxyNa8Q0PForu1ltB5qEiFb1kxr/F/HO1EwNa6g==}
peerDependencies:
'@chakra-ui/react': '>=2.0.0'
react: '>=18'
dependencies:
- '@chakra-ui/react': 2.10.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1)
+ '@chakra-ui/react': 2.10.4(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1)
react: 18.3.1
dev: false
@@ -825,8 +825,8 @@ packages:
react: 18.3.1
dev: false
- /@chakra-ui/react@2.10.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1):
- resolution: {integrity: sha512-TfIHTqTlxTHYJZBtpiR5EZasPUrLYKJxdbHkdOJb5G1OQ+2c5kKl5XA7c2pMtsEptzb7KxAAIB62t3hxdfWp1w==}
+ /@chakra-ui/react@2.10.4(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1):
+ resolution: {integrity: sha512-XyRWnuZ1Uw7Mlj5pKUGO5/WhnIHP/EOrpy6lGZC1yWlkd0eIfIpYMZ1ALTZx4KPEdbBaes48dgiMT2ROCqLhkA==}
peerDependencies:
'@emotion/react': '>=11'
'@emotion/styled': '>=11'
@@ -834,10 +834,10 @@ packages:
react: '>=18'
react-dom: '>=18'
dependencies:
- '@chakra-ui/hooks': 2.4.2(react@18.3.1)
- '@chakra-ui/styled-system': 2.11.2(react@18.3.1)
- '@chakra-ui/theme': 3.4.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1)
- '@chakra-ui/utils': 2.2.2(react@18.3.1)
+ '@chakra-ui/hooks': 2.4.3(react@18.3.1)
+ '@chakra-ui/styled-system': 2.12.1(react@18.3.1)
+ '@chakra-ui/theme': 3.4.7(@chakra-ui/styled-system@2.12.1)(react@18.3.1)
+ '@chakra-ui/utils': 2.2.3(react@18.3.1)
'@emotion/react': 11.13.3(@types/react@18.3.11)(react@18.3.1)
'@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.11)(react@18.3.1)
'@popperjs/core': 2.11.8
@@ -868,10 +868,10 @@ packages:
react: 18.3.1
dev: false
- /@chakra-ui/styled-system@2.11.2(react@18.3.1):
- resolution: {integrity: sha512-y++z2Uop+hjfZX9mbH88F1ikazPv32asD2er56zMJBemUAzweXnHTpiCQbluEDSUDhqmghVZAdb+5L4XLbsRxA==}
+ /@chakra-ui/styled-system@2.12.1(react@18.3.1):
+ resolution: {integrity: sha512-DQph1nDiCPtgze7nDe0a36530ByXb5VpPosKGyWMvKocVeZJcDtYG6XM0+V5a0wKuFBXsViBBRIFUTiUesJAcg==}
dependencies:
- '@chakra-ui/utils': 2.2.2(react@18.3.1)
+ '@chakra-ui/utils': 2.2.3(react@18.3.1)
csstype: 3.1.3
transitivePeerDependencies:
- react
@@ -915,14 +915,14 @@ packages:
color2k: 2.0.3
dev: false
- /@chakra-ui/theme-tools@2.2.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1):
- resolution: {integrity: sha512-3UhKPyzKbV3l/bg1iQN9PBvffYp+EBOoYMUaeTUdieQRPFzo2jbYR0lNCxqv8h5aGM/k54nCHU2M/GStyi9F2A==}
+ /@chakra-ui/theme-tools@2.2.7(@chakra-ui/styled-system@2.12.1)(react@18.3.1):
+ resolution: {integrity: sha512-K/VJd0QcnKik7m+qZTkggqNLep6+MPUu8IP5TUpHsnSM5R/RVjsJIR7gO8IZVAIMIGLLTIhGshHxeMekqv6LcQ==}
peerDependencies:
'@chakra-ui/styled-system': '>=2.0.0'
dependencies:
- '@chakra-ui/anatomy': 2.3.4
- '@chakra-ui/styled-system': 2.11.2(react@18.3.1)
- '@chakra-ui/utils': 2.2.2(react@18.3.1)
+ '@chakra-ui/anatomy': 2.3.5
+ '@chakra-ui/styled-system': 2.12.1(react@18.3.1)
+ '@chakra-ui/utils': 2.2.3(react@18.3.1)
color2k: 2.0.3
transitivePeerDependencies:
- react
@@ -948,15 +948,15 @@ packages:
'@chakra-ui/theme-tools': 2.1.2(@chakra-ui/styled-system@2.9.2)
dev: false
- /@chakra-ui/theme@3.4.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1):
- resolution: {integrity: sha512-ZwFBLfiMC3URwaO31ONXoKH9k0TX0OW3UjdPF3EQkQpYyrk/fm36GkkzajjtdpWEd7rzDLRsQjPmvwNaSoNDtg==}
+ /@chakra-ui/theme@3.4.7(@chakra-ui/styled-system@2.12.1)(react@18.3.1):
+ resolution: {integrity: sha512-pfewthgZTFNUYeUwGvhPQO/FTIyf375cFV1AT8N1y0aJiw4KDe7YTGm7p0aFy4AwAjH2ydMgeEx/lua4tx8qyQ==}
peerDependencies:
'@chakra-ui/styled-system': '>=2.8.0'
dependencies:
- '@chakra-ui/anatomy': 2.3.4
- '@chakra-ui/styled-system': 2.11.2(react@18.3.1)
- '@chakra-ui/theme-tools': 2.2.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1)
- '@chakra-ui/utils': 2.2.2(react@18.3.1)
+ '@chakra-ui/anatomy': 2.3.5
+ '@chakra-ui/styled-system': 2.12.1(react@18.3.1)
+ '@chakra-ui/theme-tools': 2.2.7(@chakra-ui/styled-system@2.12.1)(react@18.3.1)
+ '@chakra-ui/utils': 2.2.3(react@18.3.1)
transitivePeerDependencies:
- react
dev: false
@@ -981,8 +981,8 @@ packages:
lodash.mergewith: 4.6.2
dev: false
- /@chakra-ui/utils@2.2.2(react@18.3.1):
- resolution: {integrity: sha512-jUPLT0JzRMWxpdzH6c+t0YMJYrvc5CLericgITV3zDSXblkfx3DsYXqU11DJTSGZI9dUKzM1Wd0Wswn4eJwvFQ==}
+ /@chakra-ui/utils@2.2.3(react@18.3.1):
+ resolution: {integrity: sha512-cldoCQuexZ6e07/9hWHKD4l1QXXlM1Nax9tuQOBvVf/EgwNZt3nZu8zZRDFlhAOKCTQDkmpLTTu+eXXjChNQOw==}
peerDependencies:
react: '>=16.8.0'
dependencies:
@@ -1675,20 +1675,20 @@ packages:
prettier: 3.3.3
dev: true
- /@invoke-ai/ui-library@0.0.43(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.1.0)(@types/react@18.3.11)(i18next@23.15.1)(react-dom@18.3.1)(react@18.3.1):
- resolution: {integrity: sha512-t3fPYyks07ue3dEBPJuTHbeDLnDckDCOrtvc07mMDbLOnlPEZ0StaeiNGH+oO8qLzAuMAlSTdswgHfzTc2MmPw==}
+ /@invoke-ai/ui-library@0.0.44(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.1.0)(@types/react@18.3.11)(i18next@23.15.1)(react-dom@18.3.1)(react@18.3.1):
+ resolution: {integrity: sha512-PDseHmdr8oi8cmrpx3UwIYHn4NduAJX2R0pM0pyM54xrCMPMgYiCbC/eOs8Gt4fBc2ziiPZ9UGoW4evnE3YJsg==}
peerDependencies:
'@fontsource-variable/inter': ^5.0.16
react: ^18.2.0
react-dom: ^18.2.0
dependencies:
- '@chakra-ui/anatomy': 2.3.4
- '@chakra-ui/icons': 2.2.4(@chakra-ui/react@2.10.2)(react@18.3.1)
+ '@chakra-ui/anatomy': 2.2.2
+ '@chakra-ui/icons': 2.2.4(@chakra-ui/react@2.10.4)(react@18.3.1)
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
- '@chakra-ui/react': 2.10.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1)
- '@chakra-ui/styled-system': 2.11.2(react@18.3.1)
- '@chakra-ui/theme-tools': 2.2.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1)
+ '@chakra-ui/react': 2.10.4(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1)
+ '@chakra-ui/styled-system': 2.9.2
+ '@chakra-ui/theme-tools': 2.1.2(@chakra-ui/styled-system@2.9.2)
'@emotion/react': 11.13.3(@types/react@18.3.11)(react@18.3.1)
'@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.11)(react@18.3.1)
'@fontsource-variable/inter': 5.1.0
diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index 8b679897138..9ea13a37382 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -2124,5 +2124,67 @@
"readReleaseNotes": "Read Release Notes",
"watchRecentReleaseVideos": "Watch Recent Release Videos",
"watchUiUpdatesOverview": "Watch UI Updates Overview"
+ },
+ "supportVideos": {
+ "supportVideos": "Support Videos",
+ "gettingStarted": "Getting Started",
+ "controlCanvas": "Control Canvas",
+ "watch": "Watch",
+ "studioSessionsDesc1": "Check out the for Invoke deep dives.",
+ "studioSessionsDesc2": "Join our to participate in the live sessions and ask questions. Sessions are uploaded to the playlist the following week.",
+ "videos": {
+ "creatingYourFirstImage": {
+ "title": "Creating Your First Image",
+ "description": "Introduction to creating an image from scratch using Invoke's tools."
+ },
+ "usingControlLayersAndReferenceGuides": {
+ "title": "Using Control Layers and Reference Guides",
+ "description": "Learn how to guide your image creation with control layers and reference images."
+ },
+ "understandingImageToImageAndDenoising": {
+ "title": "Understanding Image-to-Image and Denoising",
+ "description": "Overview of image-to-image transformations and denoising in Invoke."
+ },
+ "exploringAIModelsAndConceptAdapters": {
+ "title": "Exploring AI Models and Concept Adapters",
+ "description": "Dive into AI models and how to use concept adapters for creative control."
+ },
+ "creatingAndComposingOnInvokesControlCanvas": {
+ "title": "Creating and Composing on Invoke's Control Canvas",
+ "description": "Learn to compose images using Invoke's control canvas."
+ },
+ "upscaling": {
+ "title": "Upscaling",
+ "description": "How to upscale images with Invoke's tools to enhance resolution."
+ },
+ "howDoIGenerateAndSaveToTheGallery": {
+ "title": "How Do I Generate and Save to the Gallery?",
+ "description": "Steps to generate and save images to the gallery."
+ },
+ "howDoIEditOnTheCanvas": {
+ "title": "How Do I Edit on the Canvas?",
+ "description": "Guide to editing images directly on the canvas."
+ },
+ "howDoIDoImageToImageTransformation": {
+ "title": "How Do I Do Image-to-Image Transformation?",
+ "description": "Tutorial on performing image-to-image transformations in Invoke."
+ },
+ "howDoIUseControlNetsAndControlLayers": {
+ "title": "How Do I Use Control Nets and Control Layers?",
+ "description": "Learn to apply control layers and controlnets to your images."
+ },
+ "howDoIUseGlobalIPAdaptersAndReferenceImages": {
+ "title": "How Do I Use Global IP Adapters and Reference Images?",
+ "description": "Introduction to adding reference images and global IP adapters."
+ },
+ "howDoIUseInpaintMasks": {
+ "title": "How Do I Use Inpaint Masks?",
+ "description": "How to apply inpaint masks for image correction and variation."
+ },
+ "howDoIOutpaint": {
+ "title": "How Do I Outpaint?",
+ "description": "Guide to outpainting beyond the original image borders."
+ }
+ }
}
}
diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx
index 67003f5b62e..2902c344adb 100644
--- a/invokeai/frontend/web/src/app/components/App.tsx
+++ b/invokeai/frontend/web/src/app/components/App.tsx
@@ -27,6 +27,7 @@ import { ClearQueueConfirmationsAlertDialog } from 'features/queue/components/Cl
import { DeleteStylePresetDialog } from 'features/stylePresets/components/DeleteStylePresetDialog';
import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal';
import RefreshAfterResetModal from 'features/system/components/SettingsModal/RefreshAfterResetModal';
+import { VideosModal } from 'features/system/components/VideosModal/VideosModal';
import { configChanged } from 'features/system/store/configSlice';
import { selectLanguage } from 'features/system/store/systemSelectors';
import { AppContent } from 'features/ui/components/AppContent';
@@ -108,6 +109,7 @@ const App = ({ config = DEFAULT_CONFIG, studioInitAction }: Props) => {
+
);
};
diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCard.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCard.tsx
new file mode 100644
index 00000000000..8e4275ea8b7
--- /dev/null
+++ b/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCard.tsx
@@ -0,0 +1,30 @@
+import { ExternalLink, Flex, Spacer, Text } from '@invoke-ai/ui-library';
+import type { VideoData } from 'features/system/components/VideosModal/data';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+
+const formatTime = ({ minutes, seconds }: { minutes: number; seconds: number }) => {
+ return `${minutes}:${seconds.toString().padStart(2, '0')}`;
+};
+
+export const VideoCard = memo(({ video }: { video: VideoData }) => {
+ const { t } = useTranslation();
+ const { tKey, link, length } = video;
+ return (
+
+
+
+ {t(`supportVideos.videos.${tKey}.title`)}
+
+
+ {formatTime(length)}
+
+
+
+ {t(`supportVideos.videos.${tKey}.description`)}
+
+
+ );
+});
+
+VideoCard.displayName = 'VideoCard';
diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCardList.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCardList.tsx
new file mode 100644
index 00000000000..4140b05c03d
--- /dev/null
+++ b/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCardList.tsx
@@ -0,0 +1,20 @@
+import { Divider } from '@invoke-ai/ui-library';
+import { StickyScrollable } from 'features/system/components/StickyScrollable';
+import { gettingStartedVideos, type VideoData } from 'features/system/components/VideosModal/data';
+import { VideoCard } from 'features/system/components/VideosModal/VideoCard';
+import { Fragment, memo } from 'react';
+
+export const VideoCardList = memo(({ category, videos }: { category: string; videos: VideoData[] }) => {
+ return (
+
+ {videos.map((video, i) => (
+
+
+ {i < gettingStartedVideos.length - 1 && }
+
+ ))}
+
+ );
+});
+
+VideoCardList.displayName = 'VideoCardList';
diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx
new file mode 100644
index 00000000000..5f7c9e025cb
--- /dev/null
+++ b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx
@@ -0,0 +1,79 @@
+import {
+ ExternalLink,
+ Flex,
+ Modal,
+ ModalBody,
+ ModalCloseButton,
+ ModalContent,
+ ModalFooter,
+ ModalHeader,
+ ModalOverlay,
+ Text,
+} from '@invoke-ai/ui-library';
+import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
+import { buildUseDisclosure } from 'common/hooks/useBoolean';
+import {
+ controlCanvasVideos,
+ gettingStartedVideos,
+ studioSessionsPlaylistLink,
+} from 'features/system/components/VideosModal/data';
+import { VideoCardList } from 'features/system/components/VideosModal/VideoCardList';
+import { discordLink } from 'features/system/store/constants';
+import { memo } from 'react';
+import { Trans, useTranslation } from 'react-i18next';
+
+export const [useVideosModal] = buildUseDisclosure(false);
+
+const StudioSessionsPlaylistLink = () => {
+ return (
+
+ );
+};
+
+const DiscordLink = () => {
+ return ;
+};
+
+const components = {
+ StudioSessionsPlaylistLink: ,
+ DiscordLink: ,
+};
+
+export const VideosModal = memo(() => {
+ const { t } = useTranslation();
+ const videosModal = useVideosModal();
+
+ return (
+
+
+
+ {t('supportVideos.supportVideos')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+});
+
+VideosModal.displayName = 'VideosModal';
diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModalButton.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModalButton.tsx
new file mode 100644
index 00000000000..7436b8ec7f7
--- /dev/null
+++ b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModalButton.tsx
@@ -0,0 +1,20 @@
+import { IconButton } from '@invoke-ai/ui-library';
+import { useVideosModal } from 'features/system/components/VideosModal/VideosModal';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiYoutubeLogoFill } from 'react-icons/pi';
+
+export const VideosModalButton = memo(() => {
+ const { t } = useTranslation();
+ const videosModal = useVideosModal();
+ return (
+ }
+ boxSize={8}
+ onClick={videosModal.open}
+ />
+ );
+});
+VideosModalButton.displayName = 'VideosModalButton';
diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/data.ts b/invokeai/frontend/web/src/features/system/components/VideosModal/data.ts
new file mode 100644
index 00000000000..0531c0d2e34
--- /dev/null
+++ b/invokeai/frontend/web/src/features/system/components/VideosModal/data.ts
@@ -0,0 +1,88 @@
+/**
+ * To add a support video, you'll need to add the video to the list below.
+ *
+ * The `tKey` is a sub-key in the translation file `invokeai/frontend/web/public/locales/en.json`.
+ * Add the title and description under `supportVideos.videos`, following the existing format.
+ */
+
+export type VideoData = {
+ tKey: string;
+ link: string;
+ length: {
+ minutes: number;
+ seconds: number;
+ };
+};
+
+export const gettingStartedVideos: VideoData[] = [
+ {
+ tKey: 'creatingYourFirstImage',
+ link: 'https://www.youtube.com/watch?v=jVi2XgSGrfY&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&index=1&t=29s&pp=iAQB',
+ length: { minutes: 6, seconds: 0 },
+ },
+ {
+ tKey: 'usingControlLayersAndReferenceGuides',
+ link: 'https://www.youtube.com/watch?v=crgw6bEgyrw&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&index=2&t=70s&pp=iAQB',
+ length: { minutes: 5, seconds: 30 },
+ },
+ {
+ tKey: 'understandingImageToImageAndDenoising',
+ link: 'https://www.youtube.com/watch?v=tvj8-0s6S2U&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&index=3&t=1s&pp=iAQB',
+ length: { minutes: 2, seconds: 37 },
+ },
+ {
+ tKey: 'exploringAIModelsAndConceptAdapters',
+ link: 'https://www.youtube.com/watch?v=iwBmBQMZ0UA&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&index=4&pp=iAQB',
+ length: { minutes: 8, seconds: 52 },
+ },
+ {
+ tKey: 'creatingAndComposingOnInvokesControlCanvas',
+ link: 'https://www.youtube.com/watch?v=MohWv5GZVGM&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&index=5&t=28s&pp=iAQB',
+ length: { minutes: 13, seconds: 56 },
+ },
+ {
+ tKey: 'upscaling',
+ link: 'https://www.youtube.com/watch?v=OCb19_P0nro&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&index=6&t=2s&pp=iAQB',
+ length: { minutes: 4, seconds: 0 },
+ },
+];
+
+export const controlCanvasVideos: VideoData[] = [
+ {
+ tKey: 'howDoIGenerateAndSaveToTheGallery',
+ link: 'https://youtu.be/Tl-69JvwJ2s?si=dbjmBc1iDAUpE1k5&t=26',
+ length: { minutes: 0, seconds: 49 },
+ },
+ {
+ tKey: 'howDoIEditOnTheCanvas',
+ link: 'https://youtu.be/Tl-69JvwJ2s?si=U_bFl9HsvSuejbxp&t=76',
+ length: { minutes: 0, seconds: 58 },
+ },
+ {
+ tKey: 'howDoIDoImageToImageTransformation',
+ link: 'https://youtu.be/Tl-69JvwJ2s?si=fjhTeY-yZ3qsEzEM&t=138',
+ length: { minutes: 0, seconds: 51 },
+ },
+ {
+ tKey: 'howDoIUseControlNetsAndControlLayers',
+ link: 'https://youtu.be/Tl-69JvwJ2s?si=x5KcYvkHbvR9ifsX&t=192',
+ length: { minutes: 1, seconds: 41 },
+ },
+ {
+ tKey: 'howDoIUseGlobalIPAdaptersAndReferenceImages',
+ link: 'https://youtu.be/Tl-69JvwJ2s?si=O940rNHiHGKXknK2&t=297',
+ length: { minutes: 0, seconds: 43 },
+ },
+ {
+ tKey: 'howDoIUseInpaintMasks',
+ link: 'https://youtu.be/Tl-69JvwJ2s?si=3DZhmerkzUmvJJSn&t=345',
+ length: { minutes: 1, seconds: 9 },
+ },
+ {
+ tKey: 'howDoIOutpaint',
+ link: 'https://youtu.be/Tl-69JvwJ2s?si=IIwkGZLq1PfLf80Q&t=420',
+ length: { minutes: 0, seconds: 48 },
+ },
+];
+
+export const studioSessionsPlaylistLink = 'https://www.youtube.com/playlist?list=PLvWK1Kc8iXGq_8tWZqnwDVaf9uhlDC09U';
diff --git a/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx b/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx
index 2dbb87e8b50..a5925d8de58 100644
--- a/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx
@@ -4,6 +4,7 @@ import { $customNavComponent } from 'app/store/nanostores/customNavComponent';
import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent';
import SettingsMenu from 'features/system/components/SettingsModal/SettingsMenu';
import StatusIndicator from 'features/system/components/StatusIndicator';
+import { VideosModalButton } from 'features/system/components/VideosModal/VideosModalButton';
import { TabMountGate } from 'features/ui/components/TabMountGate';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -39,6 +40,7 @@ export const VerticalNavBar = memo(() => {
+
{customNavComponent ? customNavComponent : }
);