mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat: simplify default workflows
- Rename "system" -> "default" - Simplify syncing logic - Update UI to match
This commit is contained in:
parent
224438a108
commit
4fd163698c
@ -31,7 +31,6 @@ from ..services.shared.default_graphs import create_system_graphs
|
||||
from ..services.shared.graph import GraphExecutionState, LibraryGraph
|
||||
from ..services.shared.sqlite.sqlite_database import SqliteDatabase
|
||||
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 .events import FastAPIEventService
|
||||
|
||||
@ -124,7 +123,6 @@ class ApiDependencies:
|
||||
)
|
||||
|
||||
create_system_graphs(services.graph_library)
|
||||
sync_system_workflows(workflow_records=services.workflow_records, logger=logger)
|
||||
|
||||
db.clean()
|
||||
ApiDependencies.invoker = Invoker(services)
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"id": "af7030e2-c64f-4f03-b387-9634bb54ae5f",
|
||||
"name": "Text to Image - SD1.5",
|
||||
"author": "InvokeAI",
|
||||
"description": "Sample text to image workflow for Stable Diffusion 1.5/2",
|
||||
@ -30,7 +29,7 @@
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"category": "system",
|
||||
"category": "default",
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"nodes": [
|
||||
@ -394,7 +393,7 @@
|
||||
"notes": "",
|
||||
"isIntermediate": true,
|
||||
"useCache": true,
|
||||
"version": "1.4.0",
|
||||
"version": "1.5.0",
|
||||
"nodePack": "invokeai",
|
||||
"inputs": {
|
||||
"positive_conditioning": {
|
||||
@ -534,6 +533,18 @@
|
||||
"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": {
|
||||
"id": "334d4ba3-5a99-4195-82c5-86fb3f4f7d43",
|
||||
"name": "latents",
|
||||
@ -591,7 +602,7 @@
|
||||
}
|
||||
},
|
||||
"width": 320,
|
||||
"height": 646,
|
||||
"height": 703,
|
||||
"position": {
|
||||
"x": 1400,
|
||||
"y": 25
|
@ -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}")
|
@ -48,23 +48,3 @@ class WorkflowRecordsStorageBase(ABC):
|
||||
) -> PaginatedResults[WorkflowRecordListItemDTO]:
|
||||
"""Gets many workflows."""
|
||||
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
|
||||
|
@ -31,12 +31,12 @@ class WorkflowRecordOrderBy(str, Enum, metaclass=MetaEnum):
|
||||
|
||||
class WorkflowCategory(str, Enum, metaclass=MetaEnum):
|
||||
User = "user"
|
||||
System = "system"
|
||||
Default = "default"
|
||||
|
||||
|
||||
class WorkflowMeta(BaseModel):
|
||||
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")
|
||||
def validate_version(cls, version: str):
|
||||
|
@ -1,3 +1,4 @@
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from invokeai.app.services.invoker import Invoker
|
||||
@ -28,6 +29,7 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
|
||||
def start(self, invoker: Invoker) -> None:
|
||||
self._invoker = invoker
|
||||
self._sync_default_workflows()
|
||||
|
||||
def get(self, workflow_id: str) -> WorkflowRecordDTO:
|
||||
"""Gets a workflow by ID. Updates the opened_at column."""
|
||||
@ -182,76 +184,48 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def _create_system_workflow(self, workflow: Workflow) -> None:
|
||||
try:
|
||||
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 _sync_default_workflows(self) -> None:
|
||||
"""Syncs default workflows to the database. Internal use only."""
|
||||
|
||||
def _update_system_workflow(self, workflow: Workflow) -> None:
|
||||
try:
|
||||
self._lock.acquire()
|
||||
# Only system workflows may be managed by this method
|
||||
assert workflow.meta.category is WorkflowCategory.System
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
UPDATE workflow_library
|
||||
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()
|
||||
"""
|
||||
An enhancment might be to only update workflows that have changed. This would require we
|
||||
ensure workflow IDs don't change, and the workflow version is incremented.
|
||||
|
||||
It's much simpler to just replace them all with whichever workflows are in the directory.
|
||||
|
||||
The downside is that the `updated_at` and `opened_at` timestamps for default workflows are
|
||||
meaningless, as they are overwritten every time the server starts.
|
||||
"""
|
||||
|
||||
def _delete_system_workflow(self, workflow_id: str) -> None:
|
||||
try:
|
||||
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(
|
||||
"""--sql
|
||||
DELETE FROM workflow_library
|
||||
WHERE workflow_id = ? AND category = 'system';
|
||||
""",
|
||||
(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';
|
||||
WHERE category = 'default';
|
||||
"""
|
||||
)
|
||||
rows = self._cursor.fetchall()
|
||||
return [WorkflowValidator.validate_json(dict(row)["workflow"]) for row in rows]
|
||||
for w in workflows:
|
||||
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:
|
||||
self._conn.rollback()
|
||||
raise
|
||||
|
@ -1626,12 +1626,8 @@
|
||||
"workflows": {
|
||||
"workflows": "Workflows",
|
||||
"workflowLibrary": "Workflow Library",
|
||||
"user": "User",
|
||||
"system": "System",
|
||||
"recent": "Recent",
|
||||
"userWorkflows": "$t(workflows.user) $t(workflows.workflows)",
|
||||
"recentWorkflows": "$t(workflows.recent) $t(workflows.workflows)",
|
||||
"systemWorkflows": "$t(workflows.system) $t(workflows.workflows)",
|
||||
"userWorkflows": "My Workflows",
|
||||
"defaultWorkflows": "Default Workflows",
|
||||
"openWorkflow": "Open Workflow",
|
||||
"uploadWorkflow": "Upload Workflow",
|
||||
"deleteWorkflow": "Delete Workflow",
|
||||
|
@ -14,7 +14,7 @@ export type XYPosition = z.infer<typeof zXYPosition>;
|
||||
export const zDimension = z.number().gt(0).nullish();
|
||||
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>;
|
||||
// #endregion
|
||||
|
||||
|
@ -41,7 +41,7 @@ export const validateWorkflow = (
|
||||
|
||||
// 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 (_workflow.meta.category === 'system') {
|
||||
if (_workflow.meta.category === 'default') {
|
||||
_workflow.meta.category = 'user';
|
||||
_workflow.id = undefined;
|
||||
}
|
||||
|
@ -20,7 +20,14 @@ import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableCon
|
||||
import { WorkflowCategory } from 'features/nodes/types/workflow';
|
||||
import WorkflowLibraryListItem from 'features/workflowLibrary/components/WorkflowLibraryListItem';
|
||||
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 { useListWorkflowsQuery } from 'services/api/endpoints/workflows';
|
||||
import { SQLiteDirection, WorkflowRecordOrderBy } from 'services/api/types';
|
||||
@ -29,7 +36,7 @@ import { useDebounce } from 'use-debounce';
|
||||
const PER_PAGE = 10;
|
||||
|
||||
const ORDER_BY_DATA: SelectItem[] = [
|
||||
{ value: 'opened_at', label: 'Recently Opened' },
|
||||
{ value: 'opened_at', label: 'Opened' },
|
||||
{ value: 'created_at', label: 'Created' },
|
||||
{ value: 'updated_at', label: 'Updated' },
|
||||
{ value: 'name', label: 'Name' },
|
||||
@ -48,14 +55,29 @@ const WorkflowLibraryList = () => {
|
||||
const [order_by, setOrderBy] = useState<WorkflowRecordOrderBy>('opened_at');
|
||||
const [direction, setDirection] = useState<SQLiteDirection>('ASC');
|
||||
const [debouncedFilterText] = useDebounce(filter_text, 500);
|
||||
const { data, isLoading, isError, isFetching } = useListWorkflowsQuery({
|
||||
page,
|
||||
per_page: PER_PAGE,
|
||||
order_by,
|
||||
direction,
|
||||
category,
|
||||
filter_text: debouncedFilterText,
|
||||
});
|
||||
|
||||
const query = useMemo(() => {
|
||||
if (category === 'user') {
|
||||
return {
|
||||
page,
|
||||
per_page: PER_PAGE,
|
||||
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(
|
||||
(value: string | null) => {
|
||||
@ -106,10 +128,12 @@ const WorkflowLibraryList = () => {
|
||||
|
||||
const handleSetUserCategory = useCallback(() => {
|
||||
setCategory('user');
|
||||
setPage(0);
|
||||
}, []);
|
||||
|
||||
const handleSetSystemCategory = useCallback(() => {
|
||||
setCategory('system');
|
||||
const handleSetDefaultCategory = useCallback(() => {
|
||||
setCategory('default');
|
||||
setPage(0);
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
@ -132,14 +156,44 @@ const WorkflowLibraryList = () => {
|
||||
{t('workflows.userWorkflows')}
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
variant={category === 'system' ? undefined : 'ghost'}
|
||||
onClick={handleSetSystemCategory}
|
||||
isChecked={category === 'system'}
|
||||
variant={category === 'default' ? undefined : 'ghost'}
|
||||
onClick={handleSetDefaultCategory}
|
||||
isChecked={category === 'default'}
|
||||
>
|
||||
{t('workflows.systemWorkflows')}
|
||||
{t('workflows.defaultWorkflows')}
|
||||
</IAIButton>
|
||||
</ButtonGroup>
|
||||
<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">
|
||||
<Input
|
||||
placeholder={t('workflows.searchWorkflows')}
|
||||
@ -161,32 +215,6 @@ const WorkflowLibraryList = () => {
|
||||
</InputRightElement>
|
||||
)}
|
||||
</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>
|
||||
<Divider />
|
||||
{data.items.length ? (
|
||||
|
@ -36,11 +36,13 @@ const WorkflowLibraryListItem = ({ workflowDTO }: Props) => {
|
||||
{workflowDTO.name || t('workflows.unnamedWorkflow')}
|
||||
</Heading>
|
||||
<Spacer />
|
||||
<Text fontSize="sm" variant="subtext">
|
||||
{t('common.updated')}:{' '}
|
||||
{dateFormat(workflowDTO.updated_at, masks.shortDate)}{' '}
|
||||
{dateFormat(workflowDTO.updated_at, masks.shortTime)}
|
||||
</Text>
|
||||
{workflowDTO.category === 'user' && (
|
||||
<Text fontSize="sm" variant="subtext">
|
||||
{t('common.updated')}:{' '}
|
||||
{dateFormat(workflowDTO.updated_at, masks.shortDate)}{' '}
|
||||
{dateFormat(workflowDTO.updated_at, masks.shortTime)}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex alignItems="center" w="full">
|
||||
{workflowDTO.description ? (
|
||||
@ -58,11 +60,13 @@ const WorkflowLibraryListItem = ({ workflowDTO }: Props) => {
|
||||
</Text>
|
||||
)}
|
||||
<Spacer />
|
||||
<Text fontSize="sm" variant="subtext">
|
||||
{t('common.created')}:{' '}
|
||||
{dateFormat(workflowDTO.created_at, masks.shortDate)}{' '}
|
||||
{dateFormat(workflowDTO.created_at, masks.shortTime)}
|
||||
</Text>
|
||||
{workflowDTO.category === 'user' && (
|
||||
<Text fontSize="sm" variant="subtext">
|
||||
{t('common.created')}:{' '}
|
||||
{dateFormat(workflowDTO.created_at, masks.shortDate)}{' '}
|
||||
{dateFormat(workflowDTO.created_at, masks.shortTime)}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<IAIButton
|
||||
|
@ -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);
|
@ -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
@ -165,7 +165,7 @@ version = { attr = "invokeai.version.__version__" }
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"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.backend" = ["**.png"]
|
||||
"invokeai.configs" = ["*.example", "**/*.yaml", "*.txt"]
|
||||
|
Loading…
Reference in New Issue
Block a user