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.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)
|
||||||
|
@ -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
|
@ -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]:
|
) -> 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
|
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 ? (
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
[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"]
|
||||||
|
Loading…
Reference in New Issue
Block a user