feat(ui): on first load, if the selected library view has no workflows, switch to the first view that has workflows

This commit is contained in:
psychedelicious
2025-03-12 09:59:44 +10:00
parent 7f14cee17e
commit 97593f95f6
6 changed files with 245 additions and 174 deletions

View File

@@ -8,9 +8,18 @@ import {
ModalHeader,
ModalOverlay,
} from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { useWorkflowLibraryModal } from 'features/nodes/store/workflowLibraryModal';
import { memo } from 'react';
import {
$workflowLibraryCategoriesOptions,
selectWorkflowLibraryView,
workflowLibraryViewChanged,
} from 'features/nodes/store/workflowLibrarySlice';
import { memo, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetCountsByCategoryQuery } from 'services/api/endpoints/workflows';
import { WorkflowLibrarySideNav } from './WorkflowLibrarySideNav';
import { WorkflowLibraryTopNav } from './WorkflowLibraryTopNav';
@@ -19,6 +28,7 @@ import { WorkflowList } from './WorkflowList';
export const WorkflowLibraryModal = memo(() => {
const { t } = useTranslation();
const workflowLibraryModal = useWorkflowLibraryModal();
const didSync = useSyncInitialWorkflowLibraryCategories();
return (
<Modal isOpen={workflowLibraryModal.isOpen} onClose={workflowLibraryModal.close} isCentered>
<ModalOverlay />
@@ -31,14 +41,17 @@ export const WorkflowLibraryModal = memo(() => {
<ModalHeader>{t('workflows.workflowLibrary')}</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
<Flex gap={4} h="100%">
<WorkflowLibrarySideNav />
<Divider orientation="vertical" />
<Flex flexDir="column" flex={1} gap={4}>
<WorkflowLibraryTopNav />
<WorkflowList />
{didSync && (
<Flex gap={4} h="100%">
<WorkflowLibrarySideNav />
<Divider orientation="vertical" />
<Flex flexDir="column" flex={1} gap={4}>
<WorkflowLibraryTopNav />
<WorkflowList />
</Flex>
</Flex>
</Flex>
)}
{!didSync && <IAINoContentFallback label={t('workflows.loading')} icon={null} />}
</ModalBody>
</ModalContent>
</Modal>
@@ -46,3 +59,84 @@ export const WorkflowLibraryModal = memo(() => {
});
WorkflowLibraryModal.displayName = 'WorkflowLibraryModal';
/**
* On first app load, if the user's selected view has no workflows, switches to the next available view.
*/
const useSyncInitialWorkflowLibraryCategories = () => {
const dispatch = useAppDispatch();
const view = useAppSelector(selectWorkflowLibraryView);
const categoryOptions = useStore($workflowLibraryCategoriesOptions);
const [didSync, setDidSync] = useState(false);
const recentWorkflowsCountQueryArg = useMemo(
() =>
({
categories: ['user', 'project', 'default'],
has_been_opened: true,
}) satisfies Parameters<typeof useGetCountsByCategoryQuery>[0],
[]
);
const yourWorkflowsCountQueryArg = useMemo(
() =>
({
categories: ['user', 'project'],
}) satisfies Parameters<typeof useGetCountsByCategoryQuery>[0],
[]
);
const queryOptions = useMemo(
() =>
({
selectFromResult: ({ data, isLoading }) => {
if (!data) {
return { count: 0, isLoading: true };
}
return {
count: Object.values(data).reduce((acc, count) => acc + count, 0),
isLoading,
};
},
}) satisfies Parameters<typeof useGetCountsByCategoryQuery>[1],
[]
);
const { count: recentWorkflowsCount, isLoading: isLoadingRecentWorkflowsCount } = useGetCountsByCategoryQuery(
recentWorkflowsCountQueryArg,
queryOptions
);
const { count: yourWorkflowsCount, isLoading: isLoadingYourWorkflowsCount } = useGetCountsByCategoryQuery(
yourWorkflowsCountQueryArg,
queryOptions
);
useEffect(() => {
if (didSync || isLoadingRecentWorkflowsCount || isLoadingYourWorkflowsCount) {
return;
}
// If the user's selected view has no workflows, switch to the next available view
if (recentWorkflowsCount === 0 && view === 'recent') {
if (yourWorkflowsCount > 0) {
dispatch(workflowLibraryViewChanged('yours'));
} else {
dispatch(workflowLibraryViewChanged('defaults'));
}
} else if (yourWorkflowsCount === 0 && (view === 'yours' || view === 'shared' || view === 'private')) {
if (recentWorkflowsCount > 0) {
dispatch(workflowLibraryViewChanged('recent'));
} else {
dispatch(workflowLibraryViewChanged('defaults'));
}
}
setDidSync(true);
}, [
categoryOptions,
didSync,
dispatch,
isLoadingRecentWorkflowsCount,
isLoadingYourWorkflowsCount,
recentWorkflowsCount,
view,
yourWorkflowsCount,
]);
return didSync;
};

View File

@@ -2,18 +2,16 @@ import type { ButtonProps, CheckboxProps } from '@invoke-ai/ui-library';
import { Button, Checkbox, Collapse, Flex, Icon, Spacer, Text } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import type { WorkflowTagCategory } from 'features/nodes/store/workflowLibrarySlice';
import type { WorkflowLibraryView, WorkflowTagCategory } from 'features/nodes/store/workflowLibrarySlice';
import {
$workflowLibraryCategoriesOptions,
$workflowLibraryTagCategoriesOptions,
$workflowLibraryTagOptions,
selectWorkflowLibraryCategories,
selectWorkflowLibraryShowOpenedWorkflowsOnly,
selectWorkflowLibraryTags,
workflowLibraryCategoriesChanged,
workflowLibraryShowOpenedWorkflowsOnlyChanged,
selectWorkflowLibrarySelectedTags,
selectWorkflowLibraryView,
workflowLibraryTagsReset,
workflowLibraryTagToggled,
workflowLibraryViewChanged,
} from 'features/nodes/store/workflowLibrarySlice';
import { useLoadWorkflow } from 'features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog';
import { NewWorkflowButton } from 'features/workflowLibrary/components/NewWorkflowButton';
@@ -27,135 +25,33 @@ import type { S } from 'services/api/types';
export const WorkflowLibrarySideNav = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const categories = useAppSelector(selectWorkflowLibraryCategories);
const categoryOptions = useStore($workflowLibraryCategoriesOptions);
const tags = useAppSelector(selectWorkflowLibraryTags);
const showOpenedWorkflowsOnly = useAppSelector(selectWorkflowLibraryShowOpenedWorkflowsOnly);
const tagCategoryOptions = useStore($workflowLibraryTagCategoriesOptions);
const selectYourWorkflows = useCallback(() => {
dispatch(workflowLibraryCategoriesChanged(categoryOptions.includes('project') ? ['user', 'project'] : ['user']));
dispatch(workflowLibraryShowOpenedWorkflowsOnlyChanged(false));
}, [categoryOptions, dispatch]);
const selectPrivateWorkflows = useCallback(() => {
dispatch(workflowLibraryCategoriesChanged(['user']));
dispatch(workflowLibraryShowOpenedWorkflowsOnlyChanged(false));
}, [dispatch]);
const selectSharedWorkflows = useCallback(() => {
dispatch(workflowLibraryCategoriesChanged(['project']));
dispatch(workflowLibraryShowOpenedWorkflowsOnlyChanged(false));
}, [dispatch]);
const selectDefaultWorkflows = useCallback(() => {
dispatch(workflowLibraryCategoriesChanged(['default']));
dispatch(workflowLibraryShowOpenedWorkflowsOnlyChanged(false));
}, [dispatch]);
const selectRecentWorkflows = useCallback(() => {
dispatch(workflowLibraryCategoriesChanged(['default', 'user', 'project']));
dispatch(workflowLibraryShowOpenedWorkflowsOnlyChanged(true));
}, [dispatch]);
const resetTags = useCallback(() => {
dispatch(workflowLibraryTagsReset());
}, [dispatch]);
const isYourWorkflowsSelected = useMemo(() => {
if (categoryOptions.includes('project')) {
return categories.includes('user') && categories.includes('project') && !showOpenedWorkflowsOnly;
} else {
return categories.includes('user') && !showOpenedWorkflowsOnly;
}
}, [categoryOptions, categories, showOpenedWorkflowsOnly]);
const isPrivateWorkflowsExclusivelySelected = useMemo(() => {
return categories.length === 1 && categories.includes('user') && !showOpenedWorkflowsOnly;
}, [categories, showOpenedWorkflowsOnly]);
const isSharedWorkflowsExclusivelySelected = useMemo(() => {
return categories.length === 1 && categories.includes('project') && !showOpenedWorkflowsOnly;
}, [categories, showOpenedWorkflowsOnly]);
const isDefaultWorkflowsExclusivelySelected = useMemo(() => {
return categories.length === 1 && categories.includes('default') && !showOpenedWorkflowsOnly;
}, [categories, showOpenedWorkflowsOnly]);
const isRecentWorkflowsSelected = useMemo(() => {
return categories.length === 3 && showOpenedWorkflowsOnly;
}, [categories, showOpenedWorkflowsOnly]);
const view = useAppSelector(selectWorkflowLibraryView);
return (
<Flex h="full" minH={0} overflow="hidden" flexDir="column" w={64} gap={1}>
<Flex flexDir="column" w="full" pb={2}>
<CategoryButton isSelected={isRecentWorkflowsSelected} onClick={selectRecentWorkflows}>
{t('workflows.recentlyOpened')}
</CategoryButton>
<WorkflowLibraryViewButton view="recent">{t('workflows.recentlyOpened')}</WorkflowLibraryViewButton>
</Flex>
<Flex flexDir="column" w="full" pb={2}>
<CategoryButton isSelected={isYourWorkflowsSelected} onClick={selectYourWorkflows}>
{t('workflows.yourWorkflows')}
</CategoryButton>
<WorkflowLibraryViewButton view="yours">{t('workflows.yourWorkflows')}</WorkflowLibraryViewButton>
{categoryOptions.includes('project') && (
<Collapse
in={
isYourWorkflowsSelected || isPrivateWorkflowsExclusivelySelected || isSharedWorkflowsExclusivelySelected
}
>
<Collapse in={view === 'yours' || view === 'shared' || view === 'private'}>
<Flex flexDir="column" gap={2} pl={4} pt={2}>
<CategoryButton
size="sm"
onClick={selectPrivateWorkflows}
isSelected={isPrivateWorkflowsExclusivelySelected}
>
<WorkflowLibraryViewButton size="sm" view="private">
{t('workflows.private')}
</CategoryButton>
<CategoryButton
size="sm"
rightIcon={<PiUsersBold />}
onClick={selectSharedWorkflows}
isSelected={isSharedWorkflowsExclusivelySelected}
>
</WorkflowLibraryViewButton>
<WorkflowLibraryViewButton size="sm" rightIcon={<PiUsersBold />} view="shared">
{t('workflows.shared')}
<Spacer />
</CategoryButton>
</WorkflowLibraryViewButton>
</Flex>
</Collapse>
)}
</Flex>
<Flex h="full" minH={0} overflow="hidden" flexDir="column">
<CategoryButton isSelected={isDefaultWorkflowsExclusivelySelected} onClick={selectDefaultWorkflows}>
{t('workflows.browseWorkflows')}
</CategoryButton>
<Collapse in={isDefaultWorkflowsExclusivelySelected}>
<Flex flexDir="column" gap={2} pl={4} py={2} overflow="hidden" h="100%" minH={0}>
<Button
isDisabled={!isDefaultWorkflowsExclusivelySelected || tags.length === 0}
onClick={resetTags}
size="sm"
variant="link"
fontWeight="bold"
justifyContent="flex-start"
flexGrow={0}
flexShrink={0}
leftIcon={<PiArrowCounterClockwiseBold />}
h={8}
>
{t('workflows.deselectAll')}
</Button>
<Flex flexDir="column" gap={2} overflow="auto">
{tagCategoryOptions.map((tagCategory) => (
<TagCategory
key={tagCategory.categoryTKey}
tagCategory={tagCategory}
isDisabled={!isDefaultWorkflowsExclusivelySelected}
/>
))}
</Flex>
</Flex>
</Collapse>
<WorkflowLibraryViewButton view="defaults">{t('workflows.browseWorkflows')}</WorkflowLibraryViewButton>
<DefaultsViewCheckboxesCollapsible />
</Flex>
<Spacer />
<NewWorkflowButton />
@@ -164,6 +60,45 @@ export const WorkflowLibrarySideNav = () => {
);
};
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 (
<Collapse in={view === 'defaults'}>
<Flex flexDir="column" gap={2} pl={4} py={2} overflow="hidden" h="100%" minH={0}>
<Button
isDisabled={tags.length === 0}
onClick={resetTags}
size="sm"
variant="link"
fontWeight="bold"
justifyContent="flex-start"
flexGrow={0}
flexShrink={0}
leftIcon={<PiArrowCounterClockwiseBold />}
h={8}
>
{t('workflows.deselectAll')}
</Button>
<Flex flexDir="column" gap={2} overflow="auto">
{tagCategoryOptions.map((tagCategory) => (
<TagCategory key={tagCategory.categoryTKey} tagCategory={tagCategory} />
))}
</Flex>
</Flex>
</Collapse>
);
});
DefaultsViewCheckboxesCollapsible.displayName = 'DefaultsViewCheckboxes';
const useCountForIndividualTag = (tag: string) => {
const allTags = useStore($workflowLibraryTagOptions);
const queryArg = useMemo(
@@ -244,7 +179,13 @@ const RecentWorkflowButton = memo(({ workflow }: { workflow: S['WorkflowRecordLi
});
RecentWorkflowButton.displayName = 'RecentWorkflowButton';
const CategoryButton = memo(({ isSelected, ...rest }: ButtonProps & { isSelected: boolean }) => {
const WorkflowLibraryViewButton = memo(({ view, ...rest }: ButtonProps & { view: WorkflowLibraryView }) => {
const dispatch = useDispatch();
const selectedView = useAppSelector(selectWorkflowLibraryView);
const onClick = useCallback(() => {
dispatch(workflowLibraryViewChanged(view));
}, [dispatch, view]);
return (
<Button
variant="ghost"
@@ -252,15 +193,16 @@ const CategoryButton = memo(({ isSelected, ...rest }: ButtonProps & { isSelected
size="md"
flexShrink={0}
w="full"
onClick={onClick}
{...rest}
bg={isSelected ? 'base.700' : undefined}
color={isSelected ? 'base.50' : undefined}
bg={selectedView === view ? 'base.700' : undefined}
color={selectedView === view ? 'base.50' : undefined}
/>
);
});
CategoryButton.displayName = 'NavButton';
WorkflowLibraryViewButton.displayName = 'NavButton';
const TagCategory = memo(({ tagCategory, isDisabled }: { tagCategory: WorkflowTagCategory; isDisabled: boolean }) => {
const TagCategory = memo(({ tagCategory }: { tagCategory: WorkflowTagCategory }) => {
const { t } = useTranslation();
const count = useCountForTagCategory(tagCategory);
@@ -270,12 +212,12 @@ const TagCategory = memo(({ tagCategory, isDisabled }: { tagCategory: WorkflowTa
return (
<Flex flexDir="column" gap={2}>
<Text fontWeight="semibold" color="base.300" opacity={isDisabled ? 0.5 : 1} flexShrink={0}>
<Text fontWeight="semibold" color="base.300" flexShrink={0}>
{t(tagCategory.categoryTKey)}
</Text>
<Flex flexDir="column" gap={2} pl={4}>
{tagCategory.tags.map((tag) => (
<TagCheckbox key={tag} tag={tag} isDisabled={isDisabled} />
<TagCheckbox key={tag} tag={tag} />
))}
</Flex>
</Flex>
@@ -285,7 +227,7 @@ TagCategory.displayName = 'TagCategory';
const TagCheckbox = memo(({ tag, ...rest }: CheckboxProps & { tag: string }) => {
const dispatch = useAppDispatch();
const selectedTags = useAppSelector(selectWorkflowLibraryTags);
const selectedTags = useAppSelector(selectWorkflowLibrarySelectedTags);
const isChecked = selectedTags.includes(tag);
const count = useCountForIndividualTag(tag);

View File

@@ -2,33 +2,59 @@ import { Button, Flex, Grid, GridItem, Spacer, Spinner } from '@invoke-ai/ui-lib
import { EMPTY_ARRAY } from 'app/store/constants';
import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import type { WorkflowLibraryView } from 'features/nodes/store/workflowLibrarySlice';
import {
selectWorkflowLibraryCategories,
selectWorkflowLibraryDirection,
selectWorkflowLibraryHasSearchTerm,
selectWorkflowLibraryOrderBy,
selectWorkflowLibrarySearchTerm,
selectWorkflowLibraryShowOpenedWorkflowsOnly,
selectWorkflowLibraryTags,
selectWorkflowLibrarySelectedTags,
selectWorkflowLibraryView,
} from 'features/nodes/store/workflowLibrarySlice';
import type { WorkflowCategory } from 'features/nodes/types/workflow';
import { memo, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useListWorkflowsInfiniteInfiniteQuery } from 'services/api/endpoints/workflows';
import type { S } from 'services/api/types';
import type { Equals } from 'tsafe';
import { assert } from 'tsafe';
import { useDebounce } from 'use-debounce';
import { WorkflowListItem } from './WorkflowListItem';
const PER_PAGE = 30;
const getCategories = (view: WorkflowLibraryView): WorkflowCategory[] => {
switch (view) {
case 'defaults':
return ['default'];
case 'recent':
return ['user', 'project', 'default'];
case 'yours':
return ['user', 'project'];
case 'private':
return ['user'];
case 'shared':
return ['project'];
default:
assert<Equals<typeof view, never>>(false);
}
};
const getHasBeenOpened = (view: WorkflowLibraryView): boolean | undefined => {
if (view === 'recent') {
return true;
}
return undefined;
};
const useInfiniteQueryAry = () => {
const categories = useAppSelector(selectWorkflowLibraryCategories);
const orderBy = useAppSelector(selectWorkflowLibraryOrderBy);
const direction = useAppSelector(selectWorkflowLibraryDirection);
const query = useAppSelector(selectWorkflowLibrarySearchTerm);
const tags = useAppSelector(selectWorkflowLibraryTags);
const showOpenedWorkflowsOnly = useAppSelector(selectWorkflowLibraryShowOpenedWorkflowsOnly);
const [debouncedQuery] = useDebounce(query, 500);
const searchTerm = useAppSelector(selectWorkflowLibrarySearchTerm);
const selectedTags = useAppSelector(selectWorkflowLibrarySelectedTags);
const view = useAppSelector(selectWorkflowLibraryView);
const [debouncedSearchTerm] = useDebounce(searchTerm, 500);
const queryArg = useMemo(() => {
return {
@@ -36,12 +62,12 @@ const useInfiniteQueryAry = () => {
per_page: PER_PAGE,
order_by: orderBy ?? 'opened_at',
direction,
categories,
query: debouncedQuery,
tags: categories.length === 1 && categories.includes('default') ? tags : [],
has_been_opened: showOpenedWorkflowsOnly || undefined,
categories: getCategories(view),
query: debouncedSearchTerm,
tags: view === 'defaults' ? selectedTags : [],
has_been_opened: getHasBeenOpened(view),
} satisfies Parameters<typeof useListWorkflowsInfiniteInfiniteQuery>[0];
}, [orderBy, direction, categories, debouncedQuery, tags, showOpenedWorkflowsOnly]);
}, [orderBy, direction, view, debouncedSearchTerm, selectedTags]);
return queryArg;
};

View File

@@ -5,22 +5,22 @@ import type { WorkflowCategory } from 'features/nodes/types/workflow';
import { atom, computed } from 'nanostores';
import type { SQLiteDirection, WorkflowRecordOrderBy } from 'services/api/types';
export type WorkflowLibraryView = 'recent' | 'yours' | 'private' | 'shared' | 'defaults';
type WorkflowLibraryState = {
searchTerm: string;
view: WorkflowLibraryView;
orderBy: WorkflowRecordOrderBy;
direction: SQLiteDirection;
tags: string[];
categories: WorkflowCategory[];
showOpenedWorkflowsOnly: boolean;
searchTerm: string;
selectedTags: string[];
};
const initialWorkflowLibraryState: WorkflowLibraryState = {
searchTerm: '',
orderBy: 'opened_at',
direction: 'DESC',
tags: [],
categories: ['user'],
showOpenedWorkflowsOnly: false,
selectedTags: [],
view: 'defaults',
};
export const workflowLibrarySlice = createSlice({
@@ -36,24 +36,21 @@ export const workflowLibrarySlice = createSlice({
workflowLibraryDirectionChanged: (state, action: PayloadAction<SQLiteDirection>) => {
state.direction = action.payload;
},
workflowLibraryCategoriesChanged: (state, action: PayloadAction<WorkflowCategory[]>) => {
state.categories = action.payload;
workflowLibraryViewChanged: (state, action: PayloadAction<WorkflowLibraryState['view']>) => {
state.view = action.payload;
state.searchTerm = '';
},
workflowLibraryShowOpenedWorkflowsOnlyChanged: (state, action: PayloadAction<boolean>) => {
state.showOpenedWorkflowsOnly = action.payload;
},
workflowLibraryTagToggled: (state, action: PayloadAction<string>) => {
const tag = action.payload;
const tags = state.tags;
const tags = state.selectedTags;
if (tags.includes(tag)) {
state.tags = tags.filter((t) => t !== tag);
state.selectedTags = tags.filter((t) => t !== tag);
} else {
state.tags = [...tags, tag];
state.selectedTags = [...tags, tag];
}
},
workflowLibraryTagsReset: (state) => {
state.tags = [];
state.selectedTags = [];
},
},
});
@@ -62,10 +59,9 @@ export const {
workflowLibrarySearchTermChanged,
workflowLibraryOrderByChanged,
workflowLibraryDirectionChanged,
workflowLibraryCategoriesChanged,
workflowLibraryShowOpenedWorkflowsOnlyChanged,
workflowLibraryTagToggled,
workflowLibraryTagsReset,
workflowLibraryViewChanged,
} = workflowLibrarySlice.actions;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
@@ -86,11 +82,10 @@ export const selectWorkflowLibrarySearchTerm = createWorkflowLibrarySelector(({
export const selectWorkflowLibraryHasSearchTerm = createWorkflowLibrarySelector(({ searchTerm }) => !!searchTerm);
export const selectWorkflowLibraryOrderBy = createWorkflowLibrarySelector(({ orderBy }) => orderBy);
export const selectWorkflowLibraryDirection = createWorkflowLibrarySelector(({ direction }) => direction);
export const selectWorkflowLibraryTags = createWorkflowLibrarySelector(({ tags }) => tags);
export const selectWorkflowLibraryCategories = createWorkflowLibrarySelector(({ categories }) => categories);
export const selectWorkflowLibraryShowOpenedWorkflowsOnly = createWorkflowLibrarySelector(({ showOpenedWorkflowsOnly }) => showOpenedWorkflowsOnly);
export const selectWorkflowLibrarySelectedTags = createWorkflowLibrarySelector(({ selectedTags }) => selectedTags);
export const selectWorkflowLibraryView = createWorkflowLibrarySelector(({ view }) => view);
export const DEFAULT_WORKFLOW_LIBRARY_CATEGORIES = ['user', 'default'] satisfies WorkflowCategory[];
export const DEFAULT_WORKFLOW_LIBRARY_CATEGORIES = ['user', 'default', 'project'] satisfies WorkflowCategory[];
export const $workflowLibraryCategoriesOptions = atom<WorkflowCategory[]>(DEFAULT_WORKFLOW_LIBRARY_CATEGORIES);
export type WorkflowTagCategory = { categoryTKey: string; tags: string[] };

View File

@@ -30,7 +30,8 @@ export const workflowsApi = api.injectEndpoints({
// Because this may change the order of the list, we need to invalidate the whole list
{ type: 'Workflow', id: LIST_TAG },
{ type: 'Workflow', id: workflow_id },
'WorkflowTagCountsWithFilter',
'WorkflowTagCounts',
'WorkflowCategoryCounts',
],
}),
createWorkflow: build.mutation<
@@ -45,7 +46,8 @@ export const workflowsApi = api.injectEndpoints({
invalidatesTags: [
// Because this may change the order of the list, we need to invalidate the whole list
{ type: 'Workflow', id: LIST_TAG },
'WorkflowTagCountsWithFilter',
'WorkflowTagCounts',
'WorkflowCategoryCounts',
],
}),
updateWorkflow: build.mutation<
@@ -59,7 +61,8 @@ export const workflowsApi = api.injectEndpoints({
}),
invalidatesTags: (response, error, workflow) => [
{ type: 'Workflow', id: workflow.id },
'WorkflowTagCountsWithFilter',
'WorkflowTagCounts',
'WorkflowCategoryCounts',
],
}),
listWorkflows: build.query<
@@ -78,7 +81,16 @@ export const workflowsApi = api.injectEndpoints({
query: (params) => ({
url: `${buildWorkflowsUrl('counts_by_tag')}?${queryString.stringify(params, { arrayFormat: 'none' })}`,
}),
providesTags: ['WorkflowTagCountsWithFilter'],
providesTags: ['WorkflowTagCounts'],
}),
getCountsByCategory: build.query<
paths['/api/v1/workflows/counts_by_category']['get']['responses']['200']['content']['application/json'],
NonNullable<paths['/api/v1/workflows/counts_by_category']['get']['parameters']['query']>
>({
query: (params) => ({
url: `${buildWorkflowsUrl('counts_by_category')}?${queryString.stringify(params, { arrayFormat: 'none' })}`,
}),
providesTags: ['WorkflowCategoryCounts'],
}),
listWorkflowsInfinite: build.infiniteQuery<
paths['/api/v1/workflows/']['get']['responses']['200']['content']['application/json'],
@@ -151,6 +163,7 @@ export const workflowsApi = api.injectEndpoints({
export const {
useUpdateOpenedAtMutation,
useGetCountsByTagQuery,
useGetCountsByCategoryQuery,
useLazyGetWorkflowQuery,
useGetWorkflowQuery,
useCreateWorkflowMutation,

View File

@@ -44,7 +44,8 @@ const tagTypes = [
'LoRAModel',
'SDXLRefinerModel',
'Workflow',
'WorkflowTagCountsWithFilter',
'WorkflowTagCounts',
'WorkflowCategoryCounts',
'StylePreset',
'Schema',
'QueueCountsByDestination',