feat: simplify default workflows

- Rename "system" -> "default"
- Simplify syncing logic
- Update UI to match
This commit is contained in:
psychedelicious 2023-12-02 11:41:05 +11:00
parent 224438a108
commit 4fd163698c
16 changed files with 161 additions and 389 deletions

View File

@ -31,7 +31,6 @@ from ..services.shared.default_graphs import create_system_graphs
from ..services.shared.graph import GraphExecutionState, LibraryGraph from ..services.shared.graph import GraphExecutionState, LibraryGraph
from ..services.shared.sqlite.sqlite_database import SqliteDatabase from ..services.shared.sqlite.sqlite_database import SqliteDatabase
from ..services.urls.urls_default import LocalUrlService from ..services.urls.urls_default import LocalUrlService
from ..services.workflow_records.sync_system_workflows import sync_system_workflows
from ..services.workflow_records.workflow_records_sqlite import SqliteWorkflowRecordsStorage from ..services.workflow_records.workflow_records_sqlite import SqliteWorkflowRecordsStorage
from .events import FastAPIEventService from .events import FastAPIEventService
@ -124,7 +123,6 @@ class ApiDependencies:
) )
create_system_graphs(services.graph_library) create_system_graphs(services.graph_library)
sync_system_workflows(workflow_records=services.workflow_records, logger=logger)
db.clean() db.clean()
ApiDependencies.invoker = Invoker(services) ApiDependencies.invoker = Invoker(services)

View File

@ -1,5 +1,4 @@
{ {
"id": "af7030e2-c64f-4f03-b387-9634bb54ae5f",
"name": "Text to Image - SD1.5", "name": "Text to Image - SD1.5",
"author": "InvokeAI", "author": "InvokeAI",
"description": "Sample text to image workflow for Stable Diffusion 1.5/2", "description": "Sample text to image workflow for Stable Diffusion 1.5/2",
@ -30,7 +29,7 @@
} }
], ],
"meta": { "meta": {
"category": "system", "category": "default",
"version": "2.0.0" "version": "2.0.0"
}, },
"nodes": [ "nodes": [
@ -394,7 +393,7 @@
"notes": "", "notes": "",
"isIntermediate": true, "isIntermediate": true,
"useCache": true, "useCache": true,
"version": "1.4.0", "version": "1.5.0",
"nodePack": "invokeai", "nodePack": "invokeai",
"inputs": { "inputs": {
"positive_conditioning": { "positive_conditioning": {
@ -534,6 +533,18 @@
"name": "T2IAdapterField" "name": "T2IAdapterField"
} }
}, },
"cfg_rescale_multiplier": {
"id": "9101f0a6-5fe0-4826-b7b3-47e5d506826c",
"name": "cfg_rescale_multiplier",
"fieldKind": "input",
"label": "",
"type": {
"isCollection": false,
"isCollectionOrScalar": false,
"name": "FloatField"
},
"value": 0
},
"latents": { "latents": {
"id": "334d4ba3-5a99-4195-82c5-86fb3f4f7d43", "id": "334d4ba3-5a99-4195-82c5-86fb3f4f7d43",
"name": "latents", "name": "latents",
@ -591,7 +602,7 @@
} }
}, },
"width": 320, "width": 320,
"height": 646, "height": 703,
"position": { "position": {
"x": 1400, "x": 1400,
"y": 25 "y": 25

View File

@ -1,56 +0,0 @@
import pkgutil
from logging import Logger
from pathlib import Path
import semver
from invokeai.app.services.workflow_records.workflow_records_base import WorkflowRecordsStorageBase
from invokeai.app.services.workflow_records.workflow_records_common import (
Workflow,
WorkflowValidator,
)
# TODO: When I remove a workflow from system_workflows/ and do a `pip install --upgrade .`, the file
# is not removed from site-packages! The logic to delete old system workflows below doesn't work
# for normal installs. It does work for editable. Not sure why.
system_workflows_dir = "system_workflows"
def get_system_workflows_from_json() -> list[Workflow]:
app_workflows: list[Workflow] = []
workflow_paths = (Path(__file__).parent / Path(system_workflows_dir)).glob("*.json")
for workflow_path in workflow_paths:
workflow_bytes = pkgutil.get_data(__name__, f"{system_workflows_dir}/{workflow_path.name}")
if workflow_bytes is None:
raise ValueError(f"Could not load system workflow: {workflow_path.name}")
app_workflows.append(WorkflowValidator.validate_json(workflow_bytes))
return app_workflows
def sync_system_workflows(workflow_records: WorkflowRecordsStorageBase, logger: Logger) -> None:
"""Syncs system workflows in the workflow_library database with the latest system workflows."""
system_workflows = get_system_workflows_from_json()
system_workflow_ids = [w.id for w in system_workflows]
installed_workflows = workflow_records._get_all_system_workflows()
installed_workflow_ids = [w.id for w in installed_workflows]
for workflow in installed_workflows:
if workflow.id not in system_workflow_ids:
workflow_records._delete_system_workflow(workflow.id)
logger.info(f"Deleted system workflow: {workflow.name}")
for workflow in system_workflows:
if workflow.id not in installed_workflow_ids:
workflow_records._create_system_workflow(workflow)
logger.info(f"Installed system workflow: {workflow.name}")
else:
installed_workflow = workflow_records.get(workflow.id).workflow
installed_version = semver.Version.parse(installed_workflow.version)
new_version = semver.Version.parse(workflow.version)
if new_version.compare(installed_version) > 0:
workflow_records._update_system_workflow(workflow)
logger.info(f"Updated system workflow: {workflow.name}")

View File

@ -48,23 +48,3 @@ class WorkflowRecordsStorageBase(ABC):
) -> PaginatedResults[WorkflowRecordListItemDTO]: ) -> PaginatedResults[WorkflowRecordListItemDTO]:
"""Gets many workflows.""" """Gets many workflows."""
pass pass
@abstractmethod
def _create_system_workflow(self, workflow: Workflow) -> None:
"""Creates a system workflow. Internal use only."""
pass
@abstractmethod
def _update_system_workflow(self, workflow: Workflow) -> None:
"""Updates a system workflow. Internal use only."""
pass
@abstractmethod
def _delete_system_workflow(self, workflow_id: str) -> None:
"""Deletes a system workflow. Internal use only."""
pass
@abstractmethod
def _get_all_system_workflows(self) -> list[Workflow]:
"""Gets all system workflows. Internal use only."""
pass

View File

@ -31,12 +31,12 @@ class WorkflowRecordOrderBy(str, Enum, metaclass=MetaEnum):
class WorkflowCategory(str, Enum, metaclass=MetaEnum): class WorkflowCategory(str, Enum, metaclass=MetaEnum):
User = "user" User = "user"
System = "system" Default = "default"
class WorkflowMeta(BaseModel): class WorkflowMeta(BaseModel):
version: str = Field(description="The version of the workflow schema.") version: str = Field(description="The version of the workflow schema.")
category: WorkflowCategory = Field(description="The category of the workflow (user or system).") category: WorkflowCategory = Field(description="The category of the workflow (user or default).")
@field_validator("version") @field_validator("version")
def validate_version(cls, version: str): def validate_version(cls, version: str):

View File

@ -1,3 +1,4 @@
from pathlib import Path
from typing import Optional from typing import Optional
from invokeai.app.services.invoker import Invoker from invokeai.app.services.invoker import Invoker
@ -28,6 +29,7 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
def start(self, invoker: Invoker) -> None: def start(self, invoker: Invoker) -> None:
self._invoker = invoker self._invoker = invoker
self._sync_default_workflows()
def get(self, workflow_id: str) -> WorkflowRecordDTO: def get(self, workflow_id: str) -> WorkflowRecordDTO:
"""Gets a workflow by ID. Updates the opened_at column.""" """Gets a workflow by ID. Updates the opened_at column."""
@ -182,76 +184,48 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
finally: finally:
self._lock.release() self._lock.release()
def _create_system_workflow(self, workflow: Workflow) -> None: def _sync_default_workflows(self) -> None:
try: """Syncs default workflows to the database. Internal use only."""
self._lock.acquire()
# Only system workflows may be managed by this method
assert workflow.meta.category is WorkflowCategory.System
self._cursor.execute(
"""--sql
INSERT OR REPLACE INTO workflow_library (
workflow_id,
workflow
)
VALUES (?, ?);
""",
(workflow.id, workflow.model_dump_json()),
)
self._conn.commit()
except Exception:
self._conn.rollback()
raise
finally:
self._lock.release()
def _update_system_workflow(self, workflow: Workflow) -> None: """
try: An enhancment might be to only update workflows that have changed. This would require we
self._lock.acquire() ensure workflow IDs don't change, and the workflow version is incremented.
# Only system workflows may be managed by this method
assert workflow.meta.category is WorkflowCategory.System It's much simpler to just replace them all with whichever workflows are in the directory.
self._cursor.execute(
"""--sql The downside is that the `updated_at` and `opened_at` timestamps for default workflows are
UPDATE workflow_library meaningless, as they are overwritten every time the server starts.
SET workflow = ? """
WHERE workflow_id = ? AND category = 'system';
""",
(workflow.model_dump_json(), workflow.id),
)
self._conn.commit()
except Exception:
self._conn.rollback()
raise
finally:
self._lock.release()
def _delete_system_workflow(self, workflow_id: str) -> None:
try: try:
self._lock.acquire() self._lock.acquire()
workflows: list[Workflow] = []
workflows_dir = Path(__file__).parent / Path("default_workflows")
workflow_paths = workflows_dir.glob("*.json")
for path in workflow_paths:
bytes_ = path.read_bytes()
workflow = WorkflowValidator.validate_json(bytes_)
workflows.append(workflow)
# Only default workflows may be managed by this method
assert all(w.meta.category is WorkflowCategory.Default for w in workflows)
self._cursor.execute( self._cursor.execute(
"""--sql """--sql
DELETE FROM workflow_library DELETE FROM workflow_library
WHERE workflow_id = ? AND category = 'system'; WHERE category = 'default';
""",
(workflow_id,),
)
self._conn.commit()
except Exception:
self._conn.rollback()
raise
finally:
self._lock.release()
def _get_all_system_workflows(self) -> list[Workflow]:
try:
self._lock.acquire()
self._cursor.execute(
"""--sql
SELECT workflow FROM workflow_library
WHERE category = 'system';
""" """
) )
rows = self._cursor.fetchall() for w in workflows:
return [WorkflowValidator.validate_json(dict(row)["workflow"]) for row in rows] self._cursor.execute(
"""--sql
INSERT OR REPLACE INTO workflow_library (
workflow_id,
workflow
)
VALUES (?, ?);
""",
(w.id, w.model_dump_json()),
)
self._conn.commit()
except Exception: except Exception:
self._conn.rollback() self._conn.rollback()
raise raise

View File

@ -1626,12 +1626,8 @@
"workflows": { "workflows": {
"workflows": "Workflows", "workflows": "Workflows",
"workflowLibrary": "Workflow Library", "workflowLibrary": "Workflow Library",
"user": "User", "userWorkflows": "My Workflows",
"system": "System", "defaultWorkflows": "Default Workflows",
"recent": "Recent",
"userWorkflows": "$t(workflows.user) $t(workflows.workflows)",
"recentWorkflows": "$t(workflows.recent) $t(workflows.workflows)",
"systemWorkflows": "$t(workflows.system) $t(workflows.workflows)",
"openWorkflow": "Open Workflow", "openWorkflow": "Open Workflow",
"uploadWorkflow": "Upload Workflow", "uploadWorkflow": "Upload Workflow",
"deleteWorkflow": "Delete Workflow", "deleteWorkflow": "Delete Workflow",

View File

@ -14,7 +14,7 @@ export type XYPosition = z.infer<typeof zXYPosition>;
export const zDimension = z.number().gt(0).nullish(); export const zDimension = z.number().gt(0).nullish();
export type Dimension = z.infer<typeof zDimension>; export type Dimension = z.infer<typeof zDimension>;
export const zWorkflowCategory = z.enum(['user', 'system']); export const zWorkflowCategory = z.enum(['user', 'default']);
export type WorkflowCategory = z.infer<typeof zWorkflowCategory>; export type WorkflowCategory = z.infer<typeof zWorkflowCategory>;
// #endregion // #endregion

View File

@ -41,7 +41,7 @@ export const validateWorkflow = (
// System workflows are only allowed to be used as templates. // System workflows are only allowed to be used as templates.
// If a system workflow is loaded, change its category to user and remove its ID so that we can save it as a user workflow. // If a system workflow is loaded, change its category to user and remove its ID so that we can save it as a user workflow.
if (_workflow.meta.category === 'system') { if (_workflow.meta.category === 'default') {
_workflow.meta.category = 'user'; _workflow.meta.category = 'user';
_workflow.id = undefined; _workflow.id = undefined;
} }

View File

@ -20,7 +20,14 @@ import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableCon
import { WorkflowCategory } from 'features/nodes/types/workflow'; import { WorkflowCategory } from 'features/nodes/types/workflow';
import WorkflowLibraryListItem from 'features/workflowLibrary/components/WorkflowLibraryListItem'; import WorkflowLibraryListItem from 'features/workflowLibrary/components/WorkflowLibraryListItem';
import WorkflowLibraryPagination from 'features/workflowLibrary/components/WorkflowLibraryPagination'; import WorkflowLibraryPagination from 'features/workflowLibrary/components/WorkflowLibraryPagination';
import { ChangeEvent, KeyboardEvent, memo, useCallback, useState } from 'react'; import {
ChangeEvent,
KeyboardEvent,
memo,
useCallback,
useMemo,
useState,
} from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useListWorkflowsQuery } from 'services/api/endpoints/workflows'; import { useListWorkflowsQuery } from 'services/api/endpoints/workflows';
import { SQLiteDirection, WorkflowRecordOrderBy } from 'services/api/types'; import { SQLiteDirection, WorkflowRecordOrderBy } from 'services/api/types';
@ -29,7 +36,7 @@ import { useDebounce } from 'use-debounce';
const PER_PAGE = 10; const PER_PAGE = 10;
const ORDER_BY_DATA: SelectItem[] = [ const ORDER_BY_DATA: SelectItem[] = [
{ value: 'opened_at', label: 'Recently Opened' }, { value: 'opened_at', label: 'Opened' },
{ value: 'created_at', label: 'Created' }, { value: 'created_at', label: 'Created' },
{ value: 'updated_at', label: 'Updated' }, { value: 'updated_at', label: 'Updated' },
{ value: 'name', label: 'Name' }, { value: 'name', label: 'Name' },
@ -48,14 +55,29 @@ const WorkflowLibraryList = () => {
const [order_by, setOrderBy] = useState<WorkflowRecordOrderBy>('opened_at'); const [order_by, setOrderBy] = useState<WorkflowRecordOrderBy>('opened_at');
const [direction, setDirection] = useState<SQLiteDirection>('ASC'); const [direction, setDirection] = useState<SQLiteDirection>('ASC');
const [debouncedFilterText] = useDebounce(filter_text, 500); const [debouncedFilterText] = useDebounce(filter_text, 500);
const { data, isLoading, isError, isFetching } = useListWorkflowsQuery({
page, const query = useMemo(() => {
per_page: PER_PAGE, if (category === 'user') {
order_by, return {
direction, page,
category, per_page: PER_PAGE,
filter_text: debouncedFilterText, order_by,
}); direction,
category,
filter_text: debouncedFilterText,
};
}
return {
page,
per_page: PER_PAGE,
order_by: 'name' as const,
direction: 'ASC' as const,
category,
filter_text: debouncedFilterText,
};
}, [category, debouncedFilterText, direction, order_by, page]);
const { data, isLoading, isError, isFetching } = useListWorkflowsQuery(query);
const handleChangeOrderBy = useCallback( const handleChangeOrderBy = useCallback(
(value: string | null) => { (value: string | null) => {
@ -106,10 +128,12 @@ const WorkflowLibraryList = () => {
const handleSetUserCategory = useCallback(() => { const handleSetUserCategory = useCallback(() => {
setCategory('user'); setCategory('user');
setPage(0);
}, []); }, []);
const handleSetSystemCategory = useCallback(() => { const handleSetDefaultCategory = useCallback(() => {
setCategory('system'); setCategory('default');
setPage(0);
}, []); }, []);
if (isLoading) { if (isLoading) {
@ -132,14 +156,44 @@ const WorkflowLibraryList = () => {
{t('workflows.userWorkflows')} {t('workflows.userWorkflows')}
</IAIButton> </IAIButton>
<IAIButton <IAIButton
variant={category === 'system' ? undefined : 'ghost'} variant={category === 'default' ? undefined : 'ghost'}
onClick={handleSetSystemCategory} onClick={handleSetDefaultCategory}
isChecked={category === 'system'} isChecked={category === 'default'}
> >
{t('workflows.systemWorkflows')} {t('workflows.defaultWorkflows')}
</IAIButton> </IAIButton>
</ButtonGroup> </ButtonGroup>
<Spacer /> <Spacer />
{category === 'user' && (
<>
<IAIMantineSelect
label={t('common.orderBy')}
value={order_by}
data={ORDER_BY_DATA}
onChange={handleChangeOrderBy}
formControlProps={{
w: '12rem',
display: 'flex',
alignItems: 'center',
gap: 2,
}}
disabled={isFetching}
/>
<IAIMantineSelect
label={t('common.direction')}
value={direction}
data={DIRECTION_DATA}
onChange={handleChangeDirection}
formControlProps={{
w: '12rem',
display: 'flex',
alignItems: 'center',
gap: 2,
}}
disabled={isFetching}
/>
</>
)}
<InputGroup w="20rem"> <InputGroup w="20rem">
<Input <Input
placeholder={t('workflows.searchWorkflows')} placeholder={t('workflows.searchWorkflows')}
@ -161,32 +215,6 @@ const WorkflowLibraryList = () => {
</InputRightElement> </InputRightElement>
)} )}
</InputGroup> </InputGroup>
<IAIMantineSelect
label={t('common.orderBy')}
value={order_by}
data={ORDER_BY_DATA}
onChange={handleChangeOrderBy}
formControlProps={{
w: '15rem',
display: 'flex',
alignItems: 'center',
gap: 2,
}}
disabled={isFetching}
/>
<IAIMantineSelect
label={t('common.direction')}
value={direction}
data={DIRECTION_DATA}
onChange={handleChangeDirection}
formControlProps={{
w: '12rem',
display: 'flex',
alignItems: 'center',
gap: 2,
}}
disabled={isFetching}
/>
</Flex> </Flex>
<Divider /> <Divider />
{data.items.length ? ( {data.items.length ? (

View File

@ -36,11 +36,13 @@ const WorkflowLibraryListItem = ({ workflowDTO }: Props) => {
{workflowDTO.name || t('workflows.unnamedWorkflow')} {workflowDTO.name || t('workflows.unnamedWorkflow')}
</Heading> </Heading>
<Spacer /> <Spacer />
<Text fontSize="sm" variant="subtext"> {workflowDTO.category === 'user' && (
{t('common.updated')}:{' '} <Text fontSize="sm" variant="subtext">
{dateFormat(workflowDTO.updated_at, masks.shortDate)}{' '} {t('common.updated')}:{' '}
{dateFormat(workflowDTO.updated_at, masks.shortTime)} {dateFormat(workflowDTO.updated_at, masks.shortDate)}{' '}
</Text> {dateFormat(workflowDTO.updated_at, masks.shortTime)}
</Text>
)}
</Flex> </Flex>
<Flex alignItems="center" w="full"> <Flex alignItems="center" w="full">
{workflowDTO.description ? ( {workflowDTO.description ? (
@ -58,11 +60,13 @@ const WorkflowLibraryListItem = ({ workflowDTO }: Props) => {
</Text> </Text>
)} )}
<Spacer /> <Spacer />
<Text fontSize="sm" variant="subtext"> {workflowDTO.category === 'user' && (
{t('common.created')}:{' '} <Text fontSize="sm" variant="subtext">
{dateFormat(workflowDTO.created_at, masks.shortDate)}{' '} {t('common.created')}:{' '}
{dateFormat(workflowDTO.created_at, masks.shortTime)} {dateFormat(workflowDTO.created_at, masks.shortDate)}{' '}
</Text> {dateFormat(workflowDTO.created_at, masks.shortTime)}
</Text>
)}
</Flex> </Flex>
</Flex> </Flex>
<IAIButton <IAIButton

View File

@ -1,46 +0,0 @@
import { Divider, Flex, Heading, Spacer } from '@chakra-ui/react';
import {
IAINoContentFallback,
IAINoContentFallbackWithSpinner,
} from 'common/components/IAIImageFallback';
import WorkflowLibraryListItem from 'features/workflowLibrary/components/WorkflowLibraryListItem';
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { useListRecentWorkflowsQuery } from 'services/api/endpoints/workflows';
const WorkflowLibraryRecentList = () => {
const { t } = useTranslation();
const { data, isLoading, isError } = useListRecentWorkflowsQuery();
if (isLoading) {
return <IAINoContentFallbackWithSpinner label={t('workflows.loading')} />;
}
if (!data || isError) {
return <IAINoContentFallback label={t('workflows.problemLoading')} />;
}
if (!data.items.length) {
return <IAINoContentFallback label={t('workflows.noRecentWorkflows')} />;
}
return (
<>
<Flex gap={4} alignItems="center" h={10} flexShrink={0} flexGrow={0}>
<Heading size="md">{t('workflows.recentWorkflows')}</Heading>
<Spacer />
</Flex>
<Divider />
<ScrollableContent>
<Flex w="full" h="full" gap={2} flexDir="column">
{data.items.map((w) => (
<WorkflowLibraryListItem key={w.workflow_id} workflowDTO={w} />
))}
</Flex>
</ScrollableContent>
</>
);
};
export default memo(WorkflowLibraryRecentList);

View File

@ -1,117 +0,0 @@
import { Divider, Flex, Heading, Spacer } from '@chakra-ui/react';
import { SelectItem } from '@mantine/core';
import {
IAINoContentFallback,
IAINoContentFallbackWithSpinner,
} from 'common/components/IAIImageFallback';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
import WorkflowLibraryListItem from 'features/workflowLibrary/components/WorkflowLibraryListItem';
import WorkflowLibraryPagination from 'features/workflowLibrary/components/WorkflowLibraryPagination';
import { memo, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useListSystemWorkflowsQuery } from 'services/api/endpoints/workflows';
import { SQLiteDirection, WorkflowRecordOrderBy } from 'services/api/types';
const ORDER_BY_DATA: SelectItem[] = [
{ value: 'created_at', label: 'Created' },
{ value: 'updated_at', label: 'Updated' },
{ value: 'name', label: 'Name' },
];
const DIRECTION_DATA: SelectItem[] = [
{ value: 'ASC', label: 'Ascending' },
{ value: 'DESC', label: 'Descending' },
];
const WorkflowLibraryList = () => {
const { t } = useTranslation();
const [page, setPage] = useState(0);
const [order_by, setOrderBy] = useState<WorkflowRecordOrderBy>('created_at');
const [direction, setDirection] = useState<SQLiteDirection>('ASC');
const { data, isLoading, isError, isFetching } =
useListSystemWorkflowsQuery();
const handleChangeOrderBy = useCallback(
(value: string | null) => {
if (!value || value === order_by) {
return;
}
setOrderBy(value as WorkflowRecordOrderBy);
setPage(0);
},
[order_by]
);
const handleChangeDirection = useCallback(
(value: string | null) => {
if (!value || value === direction) {
return;
}
setDirection(value as SQLiteDirection);
setPage(0);
},
[direction]
);
if (isLoading) {
return <IAINoContentFallbackWithSpinner label={t('workflows.loading')} />;
}
if (!data || isError) {
return <IAINoContentFallback label={t('workflows.problemLoading')} />;
}
if (!data.items.length) {
return <IAINoContentFallback label={t('workflows.noSystemWorkflows')} />;
}
return (
<>
<Flex gap={4} alignItems="center" h={10} flexShrink={0} flexGrow={0}>
<Heading size="md">{t('workflows.systemWorkflows')}</Heading>
<Spacer />
<IAIMantineSelect
label={t('common.orderBy')}
value={order_by}
data={ORDER_BY_DATA}
onChange={handleChangeOrderBy}
formControlProps={{
w: '12rem',
display: 'flex',
alignItems: 'center',
gap: 2,
}}
disabled={isFetching}
/>
<IAIMantineSelect
label={t('common.direction')}
value={direction}
data={DIRECTION_DATA}
onChange={handleChangeDirection}
formControlProps={{
w: '12rem',
display: 'flex',
alignItems: 'center',
gap: 2,
}}
disabled={isFetching}
/>
</Flex>
<Divider />
<ScrollableContent>
<Flex w="full" h="full" gap={2} flexDir="column">
{data.items.map((w) => (
<WorkflowLibraryListItem key={w.workflow_id} workflowDTO={w} />
))}
</Flex>
</ScrollableContent>
<Divider />
<Flex w="full" justifyContent="space-around">
<WorkflowLibraryPagination data={data} page={page} setPage={setPage} />
</Flex>
</>
);
};
export default memo(WorkflowLibraryList);

File diff suppressed because one or more lines are too long

View File

@ -165,7 +165,7 @@ version = { attr = "invokeai.version.__version__" }
[tool.setuptools.package-data] [tool.setuptools.package-data]
"invokeai.app.assets" = ["**/*.png"] "invokeai.app.assets" = ["**/*.png"]
"invokeai.app.services.workflow_records.system_workflows" = ["*.json"] "invokeai.app.services.workflow_records.default_workflows" = ["*.json"]
"invokeai.assets.fonts" = ["**/*.ttf"] "invokeai.assets.fonts" = ["**/*.ttf"]
"invokeai.backend" = ["**.png"] "invokeai.backend" = ["**.png"]
"invokeai.configs" = ["*.example", "**/*.yaml", "*.txt"] "invokeai.configs" = ["*.example", "**/*.yaml", "*.txt"]