From 5cab73e7bb31e8bf03051b6fa5f7917a10050933 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Wed, 19 Mar 2025 11:10:17 -0400 Subject: [PATCH 1/6] workflow library UI updates: scrollbar to make obvious its overflowing, move deselecet all tags to be next to browse button --- .../components/OverlayScrollbars/constants.ts | 20 +++-- .../FloatFieldCollectionInputComponent.tsx | 2 +- .../inputs/FloatGeneratorFieldComponent.tsx | 2 +- .../ImageFieldCollectionInputComponent.tsx | 2 +- .../inputs/ImageGeneratorFieldComponent.tsx | 2 +- .../IntegerFieldCollectionInputComponent.tsx | 2 +- .../inputs/IntegerGeneratorFieldComponent.tsx | 2 +- .../StringFieldCollectionInputComponent.tsx | 2 +- .../inputs/StringGeneratorFieldComponent.tsx | 2 +- .../WorkflowLibrarySideNav.tsx | 77 ++++++++++++------- 10 files changed, 71 insertions(+), 42 deletions(-) diff --git a/invokeai/frontend/web/src/common/components/OverlayScrollbars/constants.ts b/invokeai/frontend/web/src/common/components/OverlayScrollbars/constants.ts index 7aaa9280860..a74480376cb 100644 --- a/invokeai/frontend/web/src/common/components/OverlayScrollbars/constants.ts +++ b/invokeai/frontend/web/src/common/components/OverlayScrollbars/constants.ts @@ -20,12 +20,22 @@ export const overlayScrollbarsParams: UseOverlayScrollbarsParams = { }, }; -export const getOverlayScrollbarsParams = ( - overflowX: 'hidden' | 'scroll' = 'hidden', - overflowY: 'hidden' | 'scroll' = 'scroll' -) => { +export const getOverlayScrollbarsParams = ({ + overflowX = 'hidden', + overflowY = 'scroll', + visibility = 'auto', +}: { + overflowX?: 'hidden' | 'scroll'; + overflowY?: 'hidden' | 'scroll'; + visibility?: 'auto' | 'hidden' | 'visible'; +}) => { const params = deepClone(overlayScrollbarsParams); - merge(params, { options: { overflow: { y: overflowY, x: overflowX } } }); + merge(params, { + options: { + overflow: { y: overflowY, x: overflowX }, + scrollbars: { visibility, autoHide: visibility === 'visible' ? 'never' : 'scroll' }, + }, + }); return params; }; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/FloatFieldCollectionInputComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/FloatFieldCollectionInputComponent.tsx index 9c032a4c05f..f2db6b5a294 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/FloatFieldCollectionInputComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/FloatFieldCollectionInputComponent.tsx @@ -24,7 +24,7 @@ import { PiXBold } from 'react-icons/pi'; import type { FieldComponentProps } from './types'; -const overlayscrollbarsOptions = getOverlayScrollbarsParams().options; +const overlayscrollbarsOptions = getOverlayScrollbarsParams({}).options; const sx = { borderWidth: 1, diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/FloatGeneratorFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/FloatGeneratorFieldComponent.tsx index 78cc03b6d8a..02a30aec645 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/FloatGeneratorFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/FloatGeneratorFieldComponent.tsx @@ -24,7 +24,7 @@ import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useDebounce } from 'use-debounce'; -const overlayscrollbarsOptions = getOverlayScrollbarsParams().options; +const overlayscrollbarsOptions = getOverlayScrollbarsParams({}).options; export const FloatGeneratorFieldInputComponent = memo( (props: FieldComponentProps) => { diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageFieldCollectionInputComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageFieldCollectionInputComponent.tsx index 5a102798ecc..130f7bdc298 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageFieldCollectionInputComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageFieldCollectionInputComponent.tsx @@ -24,7 +24,7 @@ import type { ImageDTO } from 'services/api/types'; import type { FieldComponentProps } from './types'; -const overlayscrollbarsOptions = getOverlayScrollbarsParams().options; +const overlayscrollbarsOptions = getOverlayScrollbarsParams({}).options; const sx = { borderWidth: 1, diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageGeneratorFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageGeneratorFieldComponent.tsx index 055a212352a..ac77fdadf8d 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageGeneratorFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageGeneratorFieldComponent.tsx @@ -17,7 +17,7 @@ import type { ChangeEvent } from 'react'; import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -const overlayscrollbarsOptions = getOverlayScrollbarsParams().options; +const overlayscrollbarsOptions = getOverlayScrollbarsParams({}).options; export const ImageGeneratorFieldInputComponent = memo( (props: FieldComponentProps) => { diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/IntegerFieldCollectionInputComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/IntegerFieldCollectionInputComponent.tsx index f439c2d5ffc..5a69e8dfecd 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/IntegerFieldCollectionInputComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/IntegerFieldCollectionInputComponent.tsx @@ -27,7 +27,7 @@ import { PiXBold } from 'react-icons/pi'; import type { FieldComponentProps } from './types'; -const overlayscrollbarsOptions = getOverlayScrollbarsParams().options; +const overlayscrollbarsOptions = getOverlayScrollbarsParams({}).options; const sx = { borderWidth: 1, diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/IntegerGeneratorFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/IntegerGeneratorFieldComponent.tsx index 8ab93343322..1176e6b4c2d 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/IntegerGeneratorFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/IntegerGeneratorFieldComponent.tsx @@ -27,7 +27,7 @@ import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useDebounce } from 'use-debounce'; -const overlayscrollbarsOptions = getOverlayScrollbarsParams().options; +const overlayscrollbarsOptions = getOverlayScrollbarsParams({}).options; export const IntegerGeneratorFieldInputComponent = memo( (props: FieldComponentProps) => { diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/StringFieldCollectionInputComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/StringFieldCollectionInputComponent.tsx index 1345356119f..d908d016769 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/StringFieldCollectionInputComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/StringFieldCollectionInputComponent.tsx @@ -17,7 +17,7 @@ import { PiXBold } from 'react-icons/pi'; import type { FieldComponentProps } from './types'; -const overlayscrollbarsOptions = getOverlayScrollbarsParams().options; +const overlayscrollbarsOptions = getOverlayScrollbarsParams({}).options; const sx = { borderWidth: 1, diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/StringGeneratorFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/StringGeneratorFieldComponent.tsx index b35469f8a25..656f8acfb55 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/StringGeneratorFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/StringGeneratorFieldComponent.tsx @@ -21,7 +21,7 @@ import type { ChangeEvent } from 'react'; import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -const overlayscrollbarsOptions = getOverlayScrollbarsParams().options; +const overlayscrollbarsOptions = getOverlayScrollbarsParams({}).options; export const StringGeneratorFieldInputComponent = memo( (props: FieldComponentProps) => { diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx index 9c16efc390a..690d22d181c 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx @@ -1,7 +1,18 @@ import type { ButtonProps, CheckboxProps } from '@invoke-ai/ui-library'; -import { Button, Checkbox, Collapse, Flex, Spacer, Text } from '@invoke-ai/ui-library'; +import { + Button, + ButtonGroup, + Checkbox, + Collapse, + Flex, + IconButton, + Spacer, + Text, + Tooltip, +} from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { getOverlayScrollbarsParams, overlayScrollbarsStyles } from 'common/components/OverlayScrollbars/constants'; import type { WorkflowLibraryView, WorkflowTagCategory } from 'features/nodes/store/workflowLibrarySlice'; import { $workflowLibraryCategoriesOptions, @@ -15,6 +26,7 @@ import { } from 'features/nodes/store/workflowLibrarySlice'; import { NewWorkflowButton } from 'features/workflowLibrary/components/NewWorkflowButton'; import { UploadWorkflowButton } from 'features/workflowLibrary/components/UploadWorkflowButton'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiArrowCounterClockwiseBold, PiUsersBold } from 'react-icons/pi'; @@ -25,9 +37,14 @@ export const WorkflowLibrarySideNav = () => { const { t } = useTranslation(); const categoryOptions = useStore($workflowLibraryCategoriesOptions); const view = useAppSelector(selectWorkflowLibraryView); + const dispatch = useAppDispatch(); + const selectedTags = useAppSelector(selectWorkflowLibrarySelectedTags); + const resetTags = useCallback(() => { + dispatch(workflowLibraryTagsReset()); + }, [dispatch]); return ( - + {t('workflows.recentlyOpened')} @@ -48,7 +65,26 @@ export const WorkflowLibrarySideNav = () => { )} - {t('workflows.browseWorkflows')} + {view === 'defaults' && selectedTags.length > 0 ? ( + + + {t('workflows.browseWorkflows')} + + + } + variant="ghost" + bg="base.700" + color="base.50" + /> + + + ) : ( + {t('workflows.browseWorkflows')} + )} @@ -58,39 +94,22 @@ export const WorkflowLibrarySideNav = () => { ); }; +const overlayscrollbarsOptions = getOverlayScrollbarsParams({ visibility: 'visible' }).options; + const DefaultsViewCheckboxesCollapsible = memo(() => { - const { t } = useTranslation(); - const dispatch = useDispatch(); - const tags = useAppSelector(selectWorkflowLibrarySelectedTags); const tagCategoryOptions = useStore($workflowLibraryTagCategoriesOptions); const view = useAppSelector(selectWorkflowLibraryView); - const resetTags = useCallback(() => { - dispatch(workflowLibraryTagsReset()); - }, [dispatch]); - return ( - - - {tagCategoryOptions.map((tagCategory) => ( - - ))} - + + + {tagCategoryOptions.map((tagCategory) => ( + + ))} + + ); From 1fa756efadc955c6840a0b51fa5221f97265b5d8 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Wed, 19 Mar 2025 11:27:56 -0400 Subject: [PATCH 2/6] update workflow tag/categories so that we can pass in 1+ selected tags to start with --- .../WorkflowLibrarySideNav.tsx | 21 ++++++++++++++----- .../nodes/store/workflowLibrarySlice.ts | 19 ++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx index 690d22d181c..6262be6d44e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx @@ -27,7 +27,7 @@ import { import { NewWorkflowButton } from 'features/workflowLibrary/components/NewWorkflowButton'; import { UploadWorkflowButton } from 'features/workflowLibrary/components/UploadWorkflowButton'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; -import { memo, useCallback, useMemo } from 'react'; +import { memo, useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiArrowCounterClockwiseBold, PiUsersBold } from 'react-icons/pi'; import { useDispatch } from 'react-redux'; @@ -43,6 +43,8 @@ export const WorkflowLibrarySideNav = () => { dispatch(workflowLibraryTagsReset()); }, [dispatch]); + useEffect(() => {}, [selectedTags, dispatch]); + return ( @@ -121,7 +123,7 @@ const useCountForIndividualTag = (tag: string) => { const queryArg = useMemo( () => ({ - tags: allTags, + tags: allTags.map((tag) => tag.label), categories: ['default'], }) satisfies Parameters[0], [allTags] @@ -146,7 +148,7 @@ const useCountForTagCategory = (tagCategory: WorkflowTagCategory) => { const queryArg = useMemo( () => ({ - tags: allTags, + tags: allTags.map((tag) => tag.label), categories: ['default'], // We only allow filtering by tag for default workflows }) satisfies Parameters[0], [allTags] @@ -159,7 +161,7 @@ const useCountForTagCategory = (tagCategory: WorkflowTagCategory) => { return { count: 0 }; } return { - count: tagCategory.tags.reduce((acc, tag) => acc + (data[tag] ?? 0), 0), + count: tagCategory.tags.reduce((acc, tag) => acc + (data[tag.label] ?? 0), 0), }; }, }) satisfies Parameters[1], @@ -197,6 +199,15 @@ WorkflowLibraryViewButton.displayName = 'NavButton'; const TagCategory = memo(({ tagCategory }: { tagCategory: WorkflowTagCategory }) => { const { t } = useTranslation(); const count = useCountForTagCategory(tagCategory); + const dispatch = useAppDispatch(); + + useEffect(() => { + for (const tag of tagCategory.tags) { + if (tag.selected) { + dispatch(workflowLibraryTagToggled(tag.label)); + } + } + }, [count, tagCategory.tags, dispatch]); if (count === 0) { return null; @@ -209,7 +220,7 @@ const TagCategory = memo(({ tagCategory }: { tagCategory: WorkflowTagCategory }) {tagCategory.tags.map((tag) => ( - + ))} diff --git a/invokeai/frontend/web/src/features/nodes/store/workflowLibrarySlice.ts b/invokeai/frontend/web/src/features/nodes/store/workflowLibrarySlice.ts index d4b8f8b201d..d10b0bfb047 100644 --- a/invokeai/frontend/web/src/features/nodes/store/workflowLibrarySlice.ts +++ b/invokeai/frontend/web/src/features/nodes/store/workflowLibrarySlice.ts @@ -92,12 +92,21 @@ export const selectWorkflowLibraryView = createWorkflowLibrarySelector(({ view } export const DEFAULT_WORKFLOW_LIBRARY_CATEGORIES = ['user', 'default'] satisfies WorkflowCategory[]; export const $workflowLibraryCategoriesOptions = atom(DEFAULT_WORKFLOW_LIBRARY_CATEGORIES); -export type WorkflowTagCategory = { categoryTKey: string; tags: string[] }; +export type WorkflowTagCategory = { categoryTKey: string; tags: Array<{ label: string; selected?: boolean }> }; export const DEFAULT_WORKFLOW_LIBRARY_TAG_CATEGORIES: WorkflowTagCategory[] = [ - { categoryTKey: 'Industry', tags: ['Architecture', 'Fashion', 'Game Dev', 'Food'] }, - { categoryTKey: 'Common Tasks', tags: ['Upscaling', 'Text to Image', 'Image to Image'] }, - { categoryTKey: 'Model Architecture', tags: ['SD1.5', 'SDXL', 'SD3.5', 'FLUX'] }, - { categoryTKey: 'Tech Showcase', tags: ['Control', 'Reference Image'] }, + { + categoryTKey: 'Industry', + tags: [{ label: 'Architecture' }, { label: 'Fashion' }, { label: 'Game Dev' }, { label: 'Food' }], + }, + { + categoryTKey: 'Common Tasks', + tags: [{ label: 'Upscaling' }, { label: 'Text to Image' }, { label: 'Image to Image' }], + }, + { + categoryTKey: 'Model Architecture', + tags: [{ label: 'SD1.5' }, { label: 'SDXL' }, { label: 'SD3.5' }, { label: 'FLUX' }], + }, + { categoryTKey: 'Tech Showcase', tags: [{ label: 'Control' }, { label: 'Reference Image' }] }, ]; export const $workflowLibraryTagCategoriesOptions = atom( DEFAULT_WORKFLOW_LIBRARY_TAG_CATEGORIES From 428995e2a20248fb7b2d9ea108e51d32edb25905 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Wed, 19 Mar 2025 13:20:56 -0400 Subject: [PATCH 3/6] switch to using recommended with star insteaed of auto-selecting --- invokeai/frontend/web/public/locales/en.json | 1 + .../WorkflowLibrarySideNav.tsx | 35 ++++++++++--------- .../nodes/store/workflowLibrarySlice.ts | 4 +-- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 6b8ace3a480..0a8a16d3479 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1701,6 +1701,7 @@ "shared": "Shared", "browseWorkflows": "Browse Workflows", "deselectAll": "Deselect All", + "recommended": "Recommended For You", "opened": "Opened", "openWorkflow": "Open Workflow", "updated": "Updated", diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx index 6262be6d44e..0d2ef9dbafb 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx @@ -1,10 +1,12 @@ import type { ButtonProps, CheckboxProps } from '@invoke-ai/ui-library'; import { + Box, Button, ButtonGroup, Checkbox, Collapse, Flex, + Icon, IconButton, Spacer, Text, @@ -29,7 +31,7 @@ import { UploadWorkflowButton } from 'features/workflowLibrary/components/Upload import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import { memo, useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiArrowCounterClockwiseBold, PiUsersBold } from 'react-icons/pi'; +import { PiArrowCounterClockwiseBold, PiStarFill, PiUsersBold } from 'react-icons/pi'; import { useDispatch } from 'react-redux'; import { useGetCountsByTagQuery } from 'services/api/endpoints/workflows'; @@ -199,15 +201,6 @@ WorkflowLibraryViewButton.displayName = 'NavButton'; const TagCategory = memo(({ tagCategory }: { tagCategory: WorkflowTagCategory }) => { const { t } = useTranslation(); const count = useCountForTagCategory(tagCategory); - const dispatch = useAppDispatch(); - - useEffect(() => { - for (const tag of tagCategory.tags) { - if (tag.selected) { - dispatch(workflowLibraryTagToggled(tag.label)); - } - } - }, [count, tagCategory.tags, dispatch]); if (count === 0) { return null; @@ -220,7 +213,7 @@ const TagCategory = memo(({ tagCategory }: { tagCategory: WorkflowTagCategory }) {tagCategory.tags.map((tag) => ( - + ))} @@ -228,14 +221,15 @@ const TagCategory = memo(({ tagCategory }: { tagCategory: WorkflowTagCategory }) }); TagCategory.displayName = 'TagCategory'; -const TagCheckbox = memo(({ tag, ...rest }: CheckboxProps & { tag: string }) => { +const TagCheckbox = memo(({ tag, ...rest }: CheckboxProps & { tag: { label: string; recommended?: boolean } }) => { const dispatch = useAppDispatch(); + const { t } = useTranslation(); const selectedTags = useAppSelector(selectWorkflowLibrarySelectedTags); - const isChecked = selectedTags.includes(tag); - const count = useCountForIndividualTag(tag); + const isChecked = selectedTags.includes(tag.label); + const count = useCountForIndividualTag(tag.label); const onChange = useCallback(() => { - dispatch(workflowLibraryTagToggled(tag)); + dispatch(workflowLibraryTagToggled(tag.label)); }, [dispatch, tag]); if (count === 0) { @@ -244,7 +238,16 @@ const TagCheckbox = memo(({ tag, ...rest }: CheckboxProps & { tag: string }) => return ( - {`${tag} (${count})`} + + {`${tag.label} (${count})`} + {tag.recommended && ( + + + + + + )} + ); }); diff --git a/invokeai/frontend/web/src/features/nodes/store/workflowLibrarySlice.ts b/invokeai/frontend/web/src/features/nodes/store/workflowLibrarySlice.ts index d10b0bfb047..7e99376ea20 100644 --- a/invokeai/frontend/web/src/features/nodes/store/workflowLibrarySlice.ts +++ b/invokeai/frontend/web/src/features/nodes/store/workflowLibrarySlice.ts @@ -92,7 +92,7 @@ export const selectWorkflowLibraryView = createWorkflowLibrarySelector(({ view } export const DEFAULT_WORKFLOW_LIBRARY_CATEGORIES = ['user', 'default'] satisfies WorkflowCategory[]; export const $workflowLibraryCategoriesOptions = atom(DEFAULT_WORKFLOW_LIBRARY_CATEGORIES); -export type WorkflowTagCategory = { categoryTKey: string; tags: Array<{ label: string; selected?: boolean }> }; +export type WorkflowTagCategory = { categoryTKey: string; tags: Array<{ label: string; recommended?: boolean }> }; export const DEFAULT_WORKFLOW_LIBRARY_TAG_CATEGORIES: WorkflowTagCategory[] = [ { categoryTKey: 'Industry', @@ -100,7 +100,7 @@ export const DEFAULT_WORKFLOW_LIBRARY_TAG_CATEGORIES: WorkflowTagCategory[] = [ }, { categoryTKey: 'Common Tasks', - tags: [{ label: 'Upscaling' }, { label: 'Text to Image' }, { label: 'Image to Image' }], + tags: [{ label: 'Upscaling' }, { label: 'Text to Image' }, { label: 'Image to Image', recommended: true }], }, { categoryTKey: 'Model Architecture', From 51fd748347a909c06e92ba6ea89a4e909b1ac3bb Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Wed, 19 Mar 2025 13:33:46 -0400 Subject: [PATCH 4/6] add viewAllWorkflowsRecommended to studio init action to show library with only recomended workflows --- .../web/src/app/hooks/useStudioInitAction.ts | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts b/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts index db486b5cee5..979bc978e89 100644 --- a/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts +++ b/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts @@ -12,6 +12,11 @@ import { sentImageToCanvas } from 'features/gallery/store/actions'; import { parseAndRecallAllMetadata } from 'features/metadata/util/handlers'; import { $hasTemplates } from 'features/nodes/store/nodesSlice'; import { $isWorkflowLibraryModalOpen } from 'features/nodes/store/workflowLibraryModal'; +import { + $workflowLibraryTagOptions, + workflowLibraryTagsReset, + workflowLibraryTagToggled, +} from 'features/nodes/store/workflowLibrarySlice'; import { $isStylePresetsMenuOpen, activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice'; import { toast } from 'features/toast/toast'; import { activeTabCanvasRightPanelChanged, setActiveTab } from 'features/ui/store/uiSlice'; @@ -30,9 +35,17 @@ type SendToCanvasAction = _StudioInitAction<'sendToCanvas', { imageName: string type UseAllParametersAction = _StudioInitAction<'useAllParameters', { imageName: string }>; type StudioDestinationAction = _StudioInitAction< 'goToDestination', - { destination: 'generation' | 'canvas' | 'workflows' | 'upscaling' | 'viewAllWorkflows' | 'viewAllStylePresets' } + { + destination: + | 'generation' + | 'canvas' + | 'workflows' + | 'upscaling' + | 'viewAllWorkflows' + | 'viewAllWorkflowsRecommended' + | 'viewAllStylePresets'; + } >; - // Use global state to show loader until we are ready to render the studio. export const $didStudioInit = atom(false); @@ -58,6 +71,7 @@ export const useStudioInitAction = (action?: StudioInitAction) => { const didParseOpenAPISchema = useStore($hasTemplates); const store = useAppStore(); const loadWorkflowWithDialog = useLoadWorkflowWithDialog(); + const workflowLibraryTagOptions = useStore($workflowLibraryTagOptions); const handleSendToCanvas = useCallback( async (imageName: string) => { @@ -173,6 +187,17 @@ export const useStudioInitAction = (action?: StudioInitAction) => { store.dispatch(setActiveTab('workflows')); $isWorkflowLibraryModalOpen.set(true); break; + case 'viewAllWorkflowsRecommended': + // Go to the workflows tab and open the workflow library modal with the recommended workflows view + store.dispatch(setActiveTab('workflows')); + $isWorkflowLibraryModalOpen.set(true); + store.dispatch(workflowLibraryTagsReset()); + for (const tag of workflowLibraryTagOptions) { + if (tag.recommended) { + store.dispatch(workflowLibraryTagToggled(tag.label)); + } + } + break; case 'viewAllStylePresets': // Go to the canvas tab and open the style presets menu store.dispatch(setActiveTab('canvas')); @@ -180,7 +205,7 @@ export const useStudioInitAction = (action?: StudioInitAction) => { break; } }, - [store] + [store, workflowLibraryTagOptions] ); const handleStudioInitAction = useCallback( From 6833c50c8703e80f2abdfdad280b6a854971e502 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Wed, 19 Mar 2025 13:34:59 -0400 Subject: [PATCH 5/6] make sure browse is selected --- invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts b/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts index 979bc978e89..f82949bb1e5 100644 --- a/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts +++ b/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts @@ -16,6 +16,7 @@ import { $workflowLibraryTagOptions, workflowLibraryTagsReset, workflowLibraryTagToggled, + workflowLibraryViewChanged, } from 'features/nodes/store/workflowLibrarySlice'; import { $isStylePresetsMenuOpen, activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice'; import { toast } from 'features/toast/toast'; @@ -191,6 +192,7 @@ export const useStudioInitAction = (action?: StudioInitAction) => { // Go to the workflows tab and open the workflow library modal with the recommended workflows view store.dispatch(setActiveTab('workflows')); $isWorkflowLibraryModalOpen.set(true); + store.dispatch(workflowLibraryViewChanged('defaults')); store.dispatch(workflowLibraryTagsReset()); for (const tag of workflowLibraryTagOptions) { if (tag.recommended) { From 4ff1f48710da6b3a4c21c3af81c5d3acc91fa1e8 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Wed, 19 Mar 2025 13:50:24 -0400 Subject: [PATCH 6/6] tsc fix --- .../components/OverlayScrollbars/ScrollableContent.tsx | 2 +- .../gallery/components/ImageMetadataViewer/DataViewer.tsx | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/common/components/OverlayScrollbars/ScrollableContent.tsx b/invokeai/frontend/web/src/common/components/OverlayScrollbars/ScrollableContent.tsx index 370c85959e0..d61a5e498c8 100644 --- a/invokeai/frontend/web/src/common/components/OverlayScrollbars/ScrollableContent.tsx +++ b/invokeai/frontend/web/src/common/components/OverlayScrollbars/ScrollableContent.tsx @@ -19,7 +19,7 @@ const styles: CSSProperties = { position: 'absolute', top: 0, left: 0, right: 0, const ScrollableContent = ({ children, maxHeight, overflowX = 'hidden', overflowY = 'scroll' }: Props) => { const overlayscrollbarsOptions = useMemo( - () => getOverlayScrollbarsParams(overflowX, overflowY).options, + () => getOverlayScrollbarsParams({ overflowX, overflowY }).options, [overflowX, overflowY] ); const [os, osRef] = useState(null); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx index 7854e4e3a03..1f2c01172d4 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx @@ -21,7 +21,10 @@ type Props = { extraCopyActions?: { label: string; getData: (data: unknown) => unknown }[]; } & FlexProps; -const overlayscrollbarsOptions = getOverlayScrollbarsParams('scroll', 'scroll').options; +const overlayscrollbarsOptions = getOverlayScrollbarsParams({ + overflowX: 'scroll', + overflowY: 'scroll', +}).options; const DataViewer = (props: Props) => { const { label, data, fileName, withDownload = true, withCopy = true, extraCopyActions, ...rest } = props;