mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat: workflow library - system graphs - wip
This commit is contained in:
parent
0a25efd054
commit
b0350e9bc8
@ -30,6 +30,7 @@ from ..services.session_queue.session_queue_sqlite import SqliteSessionQueue
|
||||
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.shared.system_workflows import create_system_workflows
|
||||
from ..services.urls.urls_default import LocalUrlService
|
||||
from ..services.workflow_records.workflow_records_sqlite import SqliteWorkflowRecordsStorage
|
||||
from .events import FastAPIEventService
|
||||
@ -123,6 +124,7 @@ class ApiDependencies:
|
||||
)
|
||||
|
||||
create_system_graphs(services.graph_library)
|
||||
create_system_workflows(workflow_records=services.workflow_records, logger=logger)
|
||||
|
||||
db.clean()
|
||||
ApiDependencies.invoker = Invoker(services)
|
||||
|
@ -7,6 +7,7 @@ from invokeai.app.services.shared.pagination import PaginatedResults
|
||||
from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection
|
||||
from invokeai.app.services.workflow_records.workflow_records_common import (
|
||||
Workflow,
|
||||
WorkflowCategory,
|
||||
WorkflowNotFoundError,
|
||||
WorkflowRecordDTO,
|
||||
WorkflowRecordListItemDTO,
|
||||
@ -70,7 +71,7 @@ async def create_workflow(
|
||||
workflow: WorkflowWithoutID = Body(description="The workflow to create", embed=True),
|
||||
) -> WorkflowRecordDTO:
|
||||
"""Creates a workflow"""
|
||||
return ApiDependencies.invoker.services.workflow_records.create(workflow)
|
||||
return ApiDependencies.invoker.services.workflow_records.create(workflow=workflow)
|
||||
|
||||
|
||||
@workflows_router.get(
|
||||
@ -85,9 +86,10 @@ async def list_workflows(
|
||||
per_page: int = Query(default=10, description="The number of workflows per page"),
|
||||
order_by: WorkflowRecordOrderBy = Query(default=WorkflowRecordOrderBy.Name, description="The order by"),
|
||||
direction: SQLiteDirection = Query(default=SQLiteDirection.Ascending, description="The order by"),
|
||||
category: WorkflowCategory = Query(default=WorkflowCategory.User, description="The category to get"),
|
||||
filter_text: Optional[str] = Query(default=None, description="The name to filter by"),
|
||||
) -> PaginatedResults[WorkflowRecordListItemDTO]:
|
||||
"""Gets a page of workflows"""
|
||||
return ApiDependencies.invoker.services.workflow_records.get_many(
|
||||
page=page, per_page=per_page, order_by=order_by, direction=direction, filter_text=filter_text
|
||||
page=page, per_page=per_page, order_by=order_by, direction=direction, filter_text=filter_text, category=category
|
||||
)
|
||||
|
787
invokeai/app/assets/workflows/Text_to_Image_SD15.json
Normal file
787
invokeai/app/assets/workflows/Text_to_Image_SD15.json
Normal file
@ -0,0 +1,787 @@
|
||||
{
|
||||
"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",
|
||||
"version": "1.1.0",
|
||||
"contact": "invoke@invoke.ai",
|
||||
"tags": "text2image, SD1.5, SD2, default",
|
||||
"notes": "",
|
||||
"exposedFields": [
|
||||
{
|
||||
"nodeId": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
||||
"fieldName": "model"
|
||||
},
|
||||
{
|
||||
"nodeId": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
|
||||
"fieldName": "prompt"
|
||||
},
|
||||
{
|
||||
"nodeId": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
|
||||
"fieldName": "prompt"
|
||||
},
|
||||
{
|
||||
"nodeId": "55705012-79b9-4aac-9f26-c0b10309785b",
|
||||
"fieldName": "width"
|
||||
},
|
||||
{
|
||||
"nodeId": "55705012-79b9-4aac-9f26-c0b10309785b",
|
||||
"fieldName": "height"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"category": "system",
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
|
||||
"type": "invocation",
|
||||
"data": {
|
||||
"id": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
|
||||
"type": "compel",
|
||||
"label": "Negative Compel Prompt",
|
||||
"isOpen": true,
|
||||
"notes": "",
|
||||
"isIntermediate": true,
|
||||
"useCache": true,
|
||||
"version": "1.0.0",
|
||||
"nodePack": "invokeai",
|
||||
"inputs": {
|
||||
"prompt": {
|
||||
"id": "7739aff6-26cb-4016-8897-5a1fb2305e4e",
|
||||
"name": "prompt",
|
||||
"fieldKind": "input",
|
||||
"label": "Negative Prompt",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "StringField"
|
||||
},
|
||||
"value": ""
|
||||
},
|
||||
"clip": {
|
||||
"id": "48d23dce-a6ae-472a-9f8c-22a714ea5ce0",
|
||||
"name": "clip",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "ClipField"
|
||||
}
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"conditioning": {
|
||||
"id": "37cf3a9d-f6b7-4b64-8ff6-2558c5ecc447",
|
||||
"name": "conditioning",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "ConditioningField"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 320,
|
||||
"height": 259,
|
||||
"position": {
|
||||
"x": 1000,
|
||||
"y": 350
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "55705012-79b9-4aac-9f26-c0b10309785b",
|
||||
"type": "invocation",
|
||||
"data": {
|
||||
"id": "55705012-79b9-4aac-9f26-c0b10309785b",
|
||||
"type": "noise",
|
||||
"label": "",
|
||||
"isOpen": true,
|
||||
"notes": "",
|
||||
"isIntermediate": true,
|
||||
"useCache": true,
|
||||
"version": "1.0.1",
|
||||
"nodePack": "invokeai",
|
||||
"inputs": {
|
||||
"seed": {
|
||||
"id": "6431737c-918a-425d-a3b4-5d57e2f35d4d",
|
||||
"name": "seed",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "IntegerField"
|
||||
},
|
||||
"value": 0
|
||||
},
|
||||
"width": {
|
||||
"id": "38fc5b66-fe6e-47c8-bba9-daf58e454ed7",
|
||||
"name": "width",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "IntegerField"
|
||||
},
|
||||
"value": 512
|
||||
},
|
||||
"height": {
|
||||
"id": "16298330-e2bf-4872-a514-d6923df53cbb",
|
||||
"name": "height",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "IntegerField"
|
||||
},
|
||||
"value": 512
|
||||
},
|
||||
"use_cpu": {
|
||||
"id": "c7c436d3-7a7a-4e76-91e4-c6deb271623c",
|
||||
"name": "use_cpu",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "BooleanField"
|
||||
},
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"noise": {
|
||||
"id": "50f650dc-0184-4e23-a927-0497a96fe954",
|
||||
"name": "noise",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "LatentsField"
|
||||
}
|
||||
},
|
||||
"width": {
|
||||
"id": "bb8a452b-133d-42d1-ae4a-3843d7e4109a",
|
||||
"name": "width",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "IntegerField"
|
||||
}
|
||||
},
|
||||
"height": {
|
||||
"id": "35cfaa12-3b8b-4b7a-a884-327ff3abddd9",
|
||||
"name": "height",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "IntegerField"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 320,
|
||||
"height": 388,
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 325
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
||||
"type": "invocation",
|
||||
"data": {
|
||||
"id": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
||||
"type": "main_model_loader",
|
||||
"label": "",
|
||||
"isOpen": true,
|
||||
"notes": "",
|
||||
"isIntermediate": true,
|
||||
"useCache": true,
|
||||
"version": "1.0.0",
|
||||
"nodePack": "invokeai",
|
||||
"inputs": {
|
||||
"model": {
|
||||
"id": "993eabd2-40fd-44fe-bce7-5d0c7075ddab",
|
||||
"name": "model",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "MainModelField"
|
||||
},
|
||||
"value": {
|
||||
"model_name": "stable-diffusion-v1-5",
|
||||
"base_model": "sd-1",
|
||||
"model_type": "main"
|
||||
}
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"unet": {
|
||||
"id": "5c18c9db-328d-46d0-8cb9-143391c410be",
|
||||
"name": "unet",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "UNetField"
|
||||
}
|
||||
},
|
||||
"clip": {
|
||||
"id": "6effcac0-ec2f-4bf5-a49e-a2c29cf921f4",
|
||||
"name": "clip",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "ClipField"
|
||||
}
|
||||
},
|
||||
"vae": {
|
||||
"id": "57683ba3-f5f5-4f58-b9a2-4b83dacad4a1",
|
||||
"name": "vae",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "VaeField"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 320,
|
||||
"height": 226,
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 25
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
|
||||
"type": "invocation",
|
||||
"data": {
|
||||
"id": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
|
||||
"type": "compel",
|
||||
"label": "Positive Compel Prompt",
|
||||
"isOpen": true,
|
||||
"notes": "",
|
||||
"isIntermediate": true,
|
||||
"useCache": true,
|
||||
"version": "1.0.0",
|
||||
"nodePack": "invokeai",
|
||||
"inputs": {
|
||||
"prompt": {
|
||||
"id": "7739aff6-26cb-4016-8897-5a1fb2305e4e",
|
||||
"name": "prompt",
|
||||
"fieldKind": "input",
|
||||
"label": "Positive Prompt",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "StringField"
|
||||
},
|
||||
"value": "Super cute tiger cub, national geographic award-winning photograph"
|
||||
},
|
||||
"clip": {
|
||||
"id": "48d23dce-a6ae-472a-9f8c-22a714ea5ce0",
|
||||
"name": "clip",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "ClipField"
|
||||
}
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"conditioning": {
|
||||
"id": "37cf3a9d-f6b7-4b64-8ff6-2558c5ecc447",
|
||||
"name": "conditioning",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "ConditioningField"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 320,
|
||||
"height": 259,
|
||||
"position": {
|
||||
"x": 1000,
|
||||
"y": 25
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ea94bc37-d995-4a83-aa99-4af42479f2f2",
|
||||
"type": "invocation",
|
||||
"data": {
|
||||
"id": "ea94bc37-d995-4a83-aa99-4af42479f2f2",
|
||||
"type": "rand_int",
|
||||
"label": "Random Seed",
|
||||
"isOpen": false,
|
||||
"notes": "",
|
||||
"isIntermediate": true,
|
||||
"useCache": false,
|
||||
"version": "1.0.0",
|
||||
"nodePack": "invokeai",
|
||||
"inputs": {
|
||||
"low": {
|
||||
"id": "3ec65a37-60ba-4b6c-a0b2-553dd7a84b84",
|
||||
"name": "low",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "IntegerField"
|
||||
},
|
||||
"value": 0
|
||||
},
|
||||
"high": {
|
||||
"id": "085f853a-1a5f-494d-8bec-e4ba29a3f2d1",
|
||||
"name": "high",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "IntegerField"
|
||||
},
|
||||
"value": 2147483647
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"value": {
|
||||
"id": "812ade4d-7699-4261-b9fc-a6c9d2ab55ee",
|
||||
"name": "value",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "IntegerField"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 320,
|
||||
"height": 32,
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 275
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
||||
"type": "invocation",
|
||||
"data": {
|
||||
"id": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
||||
"type": "denoise_latents",
|
||||
"label": "",
|
||||
"isOpen": true,
|
||||
"notes": "",
|
||||
"isIntermediate": true,
|
||||
"useCache": true,
|
||||
"version": "1.4.0",
|
||||
"nodePack": "invokeai",
|
||||
"inputs": {
|
||||
"positive_conditioning": {
|
||||
"id": "90b7f4f8-ada7-4028-8100-d2e54f192052",
|
||||
"name": "positive_conditioning",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "ConditioningField"
|
||||
}
|
||||
},
|
||||
"negative_conditioning": {
|
||||
"id": "9393779e-796c-4f64-b740-902a1177bf53",
|
||||
"name": "negative_conditioning",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "ConditioningField"
|
||||
}
|
||||
},
|
||||
"noise": {
|
||||
"id": "8e17f1e5-4f98-40b1-b7f4-86aeeb4554c1",
|
||||
"name": "noise",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "LatentsField"
|
||||
}
|
||||
},
|
||||
"steps": {
|
||||
"id": "9b63302d-6bd2-42c9-ac13-9b1afb51af88",
|
||||
"name": "steps",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "IntegerField"
|
||||
},
|
||||
"value": 50
|
||||
},
|
||||
"cfg_scale": {
|
||||
"id": "87dd04d3-870e-49e1-98bf-af003a810109",
|
||||
"name": "cfg_scale",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": true,
|
||||
"name": "FloatField"
|
||||
},
|
||||
"value": 7.5
|
||||
},
|
||||
"denoising_start": {
|
||||
"id": "f369d80f-4931-4740-9bcd-9f0620719fab",
|
||||
"name": "denoising_start",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "FloatField"
|
||||
},
|
||||
"value": 0
|
||||
},
|
||||
"denoising_end": {
|
||||
"id": "747d10e5-6f02-445c-994c-0604d814de8c",
|
||||
"name": "denoising_end",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "FloatField"
|
||||
},
|
||||
"value": 1
|
||||
},
|
||||
"scheduler": {
|
||||
"id": "1de84a4e-3a24-4ec8-862b-16ce49633b9b",
|
||||
"name": "scheduler",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "SchedulerField"
|
||||
},
|
||||
"value": "unipc"
|
||||
},
|
||||
"unet": {
|
||||
"id": "ffa6fef4-3ce2-4bdb-9296-9a834849489b",
|
||||
"name": "unet",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "UNetField"
|
||||
}
|
||||
},
|
||||
"control": {
|
||||
"id": "077b64cb-34be-4fcc-83f2-e399807a02bd",
|
||||
"name": "control",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": true,
|
||||
"name": "ControlField"
|
||||
}
|
||||
},
|
||||
"ip_adapter": {
|
||||
"id": "1d6948f7-3a65-4a65-a20c-768b287251aa",
|
||||
"name": "ip_adapter",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": true,
|
||||
"name": "IPAdapterField"
|
||||
}
|
||||
},
|
||||
"t2i_adapter": {
|
||||
"id": "75e67b09-952f-4083-aaf4-6b804d690412",
|
||||
"name": "t2i_adapter",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": true,
|
||||
"name": "T2IAdapterField"
|
||||
}
|
||||
},
|
||||
"latents": {
|
||||
"id": "334d4ba3-5a99-4195-82c5-86fb3f4f7d43",
|
||||
"name": "latents",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "LatentsField"
|
||||
}
|
||||
},
|
||||
"denoise_mask": {
|
||||
"id": "0d3dbdbf-b014-4e95-8b18-ff2ff9cb0bfa",
|
||||
"name": "denoise_mask",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "DenoiseMaskField"
|
||||
}
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"latents": {
|
||||
"id": "70fa5bbc-0c38-41bb-861a-74d6d78d2f38",
|
||||
"name": "latents",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "LatentsField"
|
||||
}
|
||||
},
|
||||
"width": {
|
||||
"id": "98ee0e6c-82aa-4e8f-8be5-dc5f00ee47f0",
|
||||
"name": "width",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "IntegerField"
|
||||
}
|
||||
},
|
||||
"height": {
|
||||
"id": "e8cb184a-5e1a-47c8-9695-4b8979564f5d",
|
||||
"name": "height",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "IntegerField"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 320,
|
||||
"height": 646,
|
||||
"position": {
|
||||
"x": 1400,
|
||||
"y": 25
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
|
||||
"type": "invocation",
|
||||
"data": {
|
||||
"id": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
|
||||
"type": "l2i",
|
||||
"label": "",
|
||||
"isOpen": true,
|
||||
"notes": "",
|
||||
"isIntermediate": false,
|
||||
"useCache": true,
|
||||
"version": "1.2.0",
|
||||
"nodePack": "invokeai",
|
||||
"inputs": {
|
||||
"metadata": {
|
||||
"id": "ab375f12-0042-4410-9182-29e30db82c85",
|
||||
"name": "metadata",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "MetadataField"
|
||||
}
|
||||
},
|
||||
"latents": {
|
||||
"id": "3a7e7efd-bff5-47d7-9d48-615127afee78",
|
||||
"name": "latents",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "LatentsField"
|
||||
}
|
||||
},
|
||||
"vae": {
|
||||
"id": "a1f5f7a1-0795-4d58-b036-7820c0b0ef2b",
|
||||
"name": "vae",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "VaeField"
|
||||
}
|
||||
},
|
||||
"tiled": {
|
||||
"id": "da52059a-0cee-4668-942f-519aa794d739",
|
||||
"name": "tiled",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "BooleanField"
|
||||
},
|
||||
"value": false
|
||||
},
|
||||
"fp32": {
|
||||
"id": "c4841df3-b24e-4140-be3b-ccd454c2522c",
|
||||
"name": "fp32",
|
||||
"fieldKind": "input",
|
||||
"label": "",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "BooleanField"
|
||||
},
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"image": {
|
||||
"id": "72d667d0-cf85-459d-abf2-28bd8b823fe7",
|
||||
"name": "image",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "ImageField"
|
||||
}
|
||||
},
|
||||
"width": {
|
||||
"id": "c8c907d8-1066-49d1-b9a6-83bdcd53addc",
|
||||
"name": "width",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "IntegerField"
|
||||
}
|
||||
},
|
||||
"height": {
|
||||
"id": "230f359c-b4ea-436c-b372-332d7dcdca85",
|
||||
"name": "height",
|
||||
"fieldKind": "output",
|
||||
"type": {
|
||||
"isCollection": false,
|
||||
"isCollectionOrScalar": false,
|
||||
"name": "IntegerField"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": 320,
|
||||
"height": 266,
|
||||
"position": {
|
||||
"x": 1800,
|
||||
"y": 25
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "reactflow__edge-ea94bc37-d995-4a83-aa99-4af42479f2f2value-55705012-79b9-4aac-9f26-c0b10309785bseed",
|
||||
"source": "ea94bc37-d995-4a83-aa99-4af42479f2f2",
|
||||
"target": "55705012-79b9-4aac-9f26-c0b10309785b",
|
||||
"type": "default",
|
||||
"sourceHandle": "value",
|
||||
"targetHandle": "seed"
|
||||
},
|
||||
{
|
||||
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8clip-7d8bf987-284f-413a-b2fd-d825445a5d6cclip",
|
||||
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
||||
"target": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
|
||||
"type": "default",
|
||||
"sourceHandle": "clip",
|
||||
"targetHandle": "clip"
|
||||
},
|
||||
{
|
||||
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8clip-93dc02a4-d05b-48ed-b99c-c9b616af3402clip",
|
||||
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
||||
"target": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
|
||||
"type": "default",
|
||||
"sourceHandle": "clip",
|
||||
"targetHandle": "clip"
|
||||
},
|
||||
{
|
||||
"id": "reactflow__edge-55705012-79b9-4aac-9f26-c0b10309785bnoise-eea2702a-19fb-45b5-9d75-56b4211ec03cnoise",
|
||||
"source": "55705012-79b9-4aac-9f26-c0b10309785b",
|
||||
"target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
||||
"type": "default",
|
||||
"sourceHandle": "noise",
|
||||
"targetHandle": "noise"
|
||||
},
|
||||
{
|
||||
"id": "reactflow__edge-7d8bf987-284f-413a-b2fd-d825445a5d6cconditioning-eea2702a-19fb-45b5-9d75-56b4211ec03cpositive_conditioning",
|
||||
"source": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
|
||||
"target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
||||
"type": "default",
|
||||
"sourceHandle": "conditioning",
|
||||
"targetHandle": "positive_conditioning"
|
||||
},
|
||||
{
|
||||
"id": "reactflow__edge-93dc02a4-d05b-48ed-b99c-c9b616af3402conditioning-eea2702a-19fb-45b5-9d75-56b4211ec03cnegative_conditioning",
|
||||
"source": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
|
||||
"target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
||||
"type": "default",
|
||||
"sourceHandle": "conditioning",
|
||||
"targetHandle": "negative_conditioning"
|
||||
},
|
||||
{
|
||||
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8unet-eea2702a-19fb-45b5-9d75-56b4211ec03cunet",
|
||||
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
||||
"target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
||||
"type": "default",
|
||||
"sourceHandle": "unet",
|
||||
"targetHandle": "unet"
|
||||
},
|
||||
{
|
||||
"id": "reactflow__edge-eea2702a-19fb-45b5-9d75-56b4211ec03clatents-58c957f5-0d01-41fc-a803-b2bbf0413d4flatents",
|
||||
"source": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
||||
"target": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
|
||||
"type": "default",
|
||||
"sourceHandle": "latents",
|
||||
"targetHandle": "latents"
|
||||
},
|
||||
{
|
||||
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8vae-58c957f5-0d01-41fc-a803-b2bbf0413d4fvae",
|
||||
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
||||
"target": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
|
||||
"type": "default",
|
||||
"sourceHandle": "vae",
|
||||
"targetHandle": "vae"
|
||||
}
|
||||
]
|
||||
}
|
33
invokeai/app/services/shared/system_workflows.py
Normal file
33
invokeai/app/services/shared/system_workflows.py
Normal file
@ -0,0 +1,33 @@
|
||||
from logging import Logger
|
||||
from pathlib import Path
|
||||
|
||||
import semver
|
||||
|
||||
import invokeai.app.assets.workflows as system_workflows_dir
|
||||
from invokeai.app.services.workflow_records.workflow_records_base import WorkflowRecordsStorageBase
|
||||
from invokeai.app.services.workflow_records.workflow_records_common import WorkflowNotFoundError, WorkflowValidator
|
||||
|
||||
system_workflows = Path(system_workflows_dir.__path__[0]).glob("*.json")
|
||||
|
||||
|
||||
def create_system_workflows(workflow_records: WorkflowRecordsStorageBase, logger: Logger) -> None:
|
||||
"""Creates the system workflows."""
|
||||
for workflow_filename in system_workflows:
|
||||
with open(workflow_filename, "rb") as f:
|
||||
workflow_bytes = f.read()
|
||||
if workflow_bytes is None:
|
||||
raise ValueError(f"Could not find system workflow: {workflow_filename}")
|
||||
|
||||
new_workflow = WorkflowValidator.validate_json(workflow_bytes)
|
||||
|
||||
try:
|
||||
installed_workflow = workflow_records.get(new_workflow.id).workflow
|
||||
installed_version = semver.Version.parse(installed_workflow.version)
|
||||
new_version = semver.Version.parse(new_workflow.version)
|
||||
|
||||
if new_version.compare(installed_version) > 0:
|
||||
logger.info(f"Updating system workflow: {new_workflow.name}")
|
||||
workflow_records._add_system_workflow(new_workflow)
|
||||
except WorkflowNotFoundError:
|
||||
logger.info(f"Installing system workflow: {new_workflow.name}")
|
||||
workflow_records._add_system_workflow(new_workflow)
|
@ -5,6 +5,7 @@ from invokeai.app.services.shared.pagination import PaginatedResults
|
||||
from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection
|
||||
from invokeai.app.services.workflow_records.workflow_records_common import (
|
||||
Workflow,
|
||||
WorkflowCategory,
|
||||
WorkflowRecordDTO,
|
||||
WorkflowRecordListItemDTO,
|
||||
WorkflowRecordOrderBy,
|
||||
@ -42,7 +43,13 @@ class WorkflowRecordsStorageBase(ABC):
|
||||
per_page: int,
|
||||
order_by: WorkflowRecordOrderBy,
|
||||
direction: SQLiteDirection,
|
||||
category: WorkflowCategory,
|
||||
filter_text: Optional[str],
|
||||
) -> PaginatedResults[WorkflowRecordListItemDTO]:
|
||||
"""Gets many workflows."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _add_system_workflow(self, workflow: Workflow) -> None:
|
||||
"""Adds a system workflow. Internal use only."""
|
||||
pass
|
||||
|
@ -16,8 +16,27 @@ class ExposedField(BaseModel):
|
||||
fieldName: str
|
||||
|
||||
|
||||
class WorkflowNotFoundError(Exception):
|
||||
"""Raised when a workflow is not found"""
|
||||
|
||||
|
||||
class WorkflowRecordOrderBy(str, Enum, metaclass=MetaEnum):
|
||||
"""The order by options for workflow records"""
|
||||
|
||||
CreatedAt = "created_at"
|
||||
UpdatedAt = "updated_at"
|
||||
OpenedAt = "opened_at"
|
||||
Name = "name"
|
||||
|
||||
|
||||
class WorkflowCategory(str, Enum, metaclass=MetaEnum):
|
||||
User = "user"
|
||||
System = "system"
|
||||
|
||||
|
||||
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).")
|
||||
|
||||
@field_validator("version")
|
||||
def validate_version(cls, version: str):
|
||||
@ -56,14 +75,17 @@ class Workflow(WorkflowWithoutID):
|
||||
WorkflowValidator = TypeAdapter(Workflow)
|
||||
|
||||
|
||||
class WorkflowRecordDTO(BaseModel):
|
||||
class WorkflowRecordDTOBase(BaseModel):
|
||||
workflow_id: str = Field(description="The id of the workflow.")
|
||||
workflow: Workflow = Field(description="The workflow.")
|
||||
name: str = Field(description="The name of the workflow.")
|
||||
created_at: Union[datetime.datetime, str] = Field(description="The created timestamp of the workflow.")
|
||||
updated_at: Union[datetime.datetime, str] = Field(description="The updated timestamp of the workflow.")
|
||||
opened_at: Union[datetime.datetime, str] = Field(description="The opened timestamp of the workflow.")
|
||||
|
||||
|
||||
class WorkflowRecordDTO(WorkflowRecordDTOBase):
|
||||
workflow: Workflow = Field(description="The workflow.")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> "WorkflowRecordDTO":
|
||||
data["workflow"] = WorkflowValidator.validate_json(data.get("workflow", ""))
|
||||
@ -73,26 +95,8 @@ class WorkflowRecordDTO(BaseModel):
|
||||
WorkflowRecordDTOValidator = TypeAdapter(WorkflowRecordDTO)
|
||||
|
||||
|
||||
class WorkflowRecordListItemDTO(BaseModel):
|
||||
workflow_id: str = Field(description="The id of the workflow.")
|
||||
name: str = Field(description="The name of the workflow.")
|
||||
class WorkflowRecordListItemDTO(WorkflowRecordDTOBase):
|
||||
description: str = Field(description="The description of the workflow.")
|
||||
created_at: Union[datetime.datetime, str] = Field(description="The created timestamp of the workflow.")
|
||||
updated_at: Union[datetime.datetime, str] = Field(description="The updated timestamp of the workflow.")
|
||||
opened_at: Union[datetime.datetime, str] = Field(description="The opened timestamp of the workflow.")
|
||||
|
||||
|
||||
WorkflowRecordListItemDTOValidator = TypeAdapter(WorkflowRecordListItemDTO)
|
||||
|
||||
|
||||
class WorkflowNotFoundError(Exception):
|
||||
"""Raised when a workflow is not found"""
|
||||
|
||||
|
||||
class WorkflowRecordOrderBy(str, Enum, metaclass=MetaEnum):
|
||||
"""The order by options for workflow records"""
|
||||
|
||||
CreatedAt = "created_at"
|
||||
UpdatedAt = "updated_at"
|
||||
OpenedAt = "opened_at"
|
||||
Name = "name"
|
||||
|
@ -7,6 +7,7 @@ from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
|
||||
from invokeai.app.services.workflow_records.workflow_records_base import WorkflowRecordsStorageBase
|
||||
from invokeai.app.services.workflow_records.workflow_records_common import (
|
||||
Workflow,
|
||||
WorkflowCategory,
|
||||
WorkflowNotFoundError,
|
||||
WorkflowRecordDTO,
|
||||
WorkflowRecordListItemDTO,
|
||||
@ -40,6 +41,7 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
""",
|
||||
(workflow_id,),
|
||||
)
|
||||
self._conn.commit()
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
SELECT workflow_id, workflow, name, created_at, updated_at, opened_at
|
||||
@ -60,6 +62,8 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
|
||||
def create(self, workflow: WorkflowWithoutID) -> WorkflowRecordDTO:
|
||||
try:
|
||||
# Only user workflows may be created by this method
|
||||
assert workflow.meta.category is WorkflowCategory.User
|
||||
workflow_with_id = WorkflowValidator.validate_python(workflow.model_dump())
|
||||
self._lock.acquire()
|
||||
self._cursor.execute(
|
||||
@ -70,10 +74,7 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
)
|
||||
VALUES (?, ?);
|
||||
""",
|
||||
(
|
||||
workflow_with_id.id,
|
||||
workflow_with_id.model_dump_json(),
|
||||
),
|
||||
(workflow_with_id.id, workflow_with_id.model_dump_json()),
|
||||
)
|
||||
self._conn.commit()
|
||||
except Exception:
|
||||
@ -90,7 +91,7 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
"""--sql
|
||||
UPDATE workflow_library
|
||||
SET workflow = ?
|
||||
WHERE workflow_id = ?;
|
||||
WHERE workflow_id = ? AND category = "user";
|
||||
""",
|
||||
(workflow.model_dump_json(), workflow.id),
|
||||
)
|
||||
@ -108,7 +109,7 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
DELETE from workflow_library
|
||||
WHERE workflow_id = ?;
|
||||
WHERE workflow_id = ? AND category = "user";
|
||||
""",
|
||||
(workflow_id,),
|
||||
)
|
||||
@ -126,6 +127,7 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
per_page: int,
|
||||
order_by: WorkflowRecordOrderBy,
|
||||
direction: SQLiteDirection,
|
||||
category: WorkflowCategory,
|
||||
filter_text: Optional[str] = None,
|
||||
) -> PaginatedResults[WorkflowRecordListItemDTO]:
|
||||
try:
|
||||
@ -133,24 +135,27 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
# sanitize!
|
||||
assert order_by in WorkflowRecordOrderBy
|
||||
assert direction in SQLiteDirection
|
||||
count_query = "SELECT COUNT(*) FROM workflow_library"
|
||||
assert category in WorkflowCategory
|
||||
count_query = "SELECT COUNT(*) FROM workflow_library WHERE category = ?"
|
||||
main_query = """
|
||||
SELECT
|
||||
workflow_id,
|
||||
category,
|
||||
name,
|
||||
description,
|
||||
created_at,
|
||||
updated_at,
|
||||
opened_at
|
||||
FROM workflow_library
|
||||
WHERE category = ?
|
||||
"""
|
||||
main_params = []
|
||||
count_params = []
|
||||
main_params = [category.value]
|
||||
count_params = [category.value]
|
||||
stripped_filter_name = filter_text.strip() if filter_text else None
|
||||
if stripped_filter_name:
|
||||
filter_string = "%" + stripped_filter_name + "%"
|
||||
main_query += " WHERE name LIKE ? OR description LIKE ? "
|
||||
count_query += " WHERE name LIKE ? OR description LIKE ?;"
|
||||
main_query += " AND name LIKE ? OR description LIKE ? "
|
||||
count_query += " AND name LIKE ? OR description LIKE ?;"
|
||||
main_params.extend([filter_string, filter_string])
|
||||
count_params.extend([filter_string, filter_string])
|
||||
|
||||
@ -177,6 +182,28 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def _add_system_workflow(self, workflow: Workflow) -> None:
|
||||
try:
|
||||
self._lock.acquire()
|
||||
# Only system workflows may be created 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 _create_tables(self) -> None:
|
||||
try:
|
||||
self._lock.acquire()
|
||||
@ -191,6 +218,7 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
-- updated manually when retrieving workflow
|
||||
opened_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),
|
||||
-- Generated columns, needed for indexing and searching
|
||||
category TEXT GENERATED ALWAYS as (json_extract(workflow, '$.meta.category')) VIRTUAL NOT NULL,
|
||||
name TEXT GENERATED ALWAYS as (json_extract(workflow, '$.name')) VIRTUAL NOT NULL,
|
||||
description TEXT GENERATED ALWAYS as (json_extract(workflow, '$.description')) VIRTUAL NOT NULL
|
||||
);
|
||||
@ -225,6 +253,11 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_library_opened_at ON workflow_library(opened_at);
|
||||
"""
|
||||
)
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_library_category ON workflow_library(category);
|
||||
"""
|
||||
)
|
||||
self._cursor.execute(
|
||||
"""--sql
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_library_name ON workflow_library(name);
|
||||
|
@ -128,6 +128,8 @@
|
||||
"random": "Random",
|
||||
"reportBugLabel": "Report Bug",
|
||||
"safetensors": "Safetensors",
|
||||
"save": "Save",
|
||||
"saveAs": "Save As",
|
||||
"settingsLabel": "Settings",
|
||||
"simple": "Simple",
|
||||
"somethingWentWrong": "Something went wrong",
|
||||
@ -1625,7 +1627,7 @@
|
||||
"unnamedWorkflow": "Unnamed Workflow",
|
||||
"downloadWorkflow": "Download Workflow",
|
||||
"saveWorkflow": "Save Workflow",
|
||||
"duplicateWorkflow": "Duplicate Workflow",
|
||||
"saveWorkflowAs": "Save Workflow As",
|
||||
"problemSavingWorkflow": "Problem Saving Workflow",
|
||||
"workflowSaved": "Workflow Saved",
|
||||
"noRecentWorkflows": "No Recent Workflows",
|
||||
@ -1635,6 +1637,7 @@
|
||||
"loading": "Loading Workflows",
|
||||
"noDescription": "No description",
|
||||
"searchWorkflows": "Search Workflows",
|
||||
"clearWorkflowSearchFilter": "Clear Workflow Search Filter"
|
||||
"clearWorkflowSearchFilter": "Clear Workflow Search Filter",
|
||||
"workflowName": "Workflow Name"
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import DownloadWorkflowButton from 'features/workflowLibrary/components/Download
|
||||
import UploadWorkflowButton from 'features/workflowLibrary/components/LoadWorkflowFromFileButton';
|
||||
import ResetWorkflowButton from 'features/workflowLibrary/components/ResetWorkflowButton';
|
||||
import SaveWorkflowButton from 'features/workflowLibrary/components/SaveWorkflowButton';
|
||||
import DuplicateWorkflowButton from 'features/workflowLibrary/components/DuplicateWorkflowButton';
|
||||
import SaveWorkflowAsButton from 'features/workflowLibrary/components/SaveWorkflowAsButton';
|
||||
|
||||
const TopCenterPanel = () => {
|
||||
return (
|
||||
@ -20,7 +20,7 @@ const TopCenterPanel = () => {
|
||||
<DownloadWorkflowButton />
|
||||
<UploadWorkflowButton />
|
||||
<SaveWorkflowButton />
|
||||
<DuplicateWorkflowButton />
|
||||
<SaveWorkflowAsButton />
|
||||
<ResetWorkflowButton />
|
||||
</Flex>
|
||||
);
|
||||
|
@ -1,33 +1,4 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { cloneDeep, forEach, isEqual, uniqBy } from 'lodash-es';
|
||||
import {
|
||||
addEdge,
|
||||
applyEdgeChanges,
|
||||
applyNodeChanges,
|
||||
Connection,
|
||||
Edge,
|
||||
EdgeChange,
|
||||
EdgeRemoveChange,
|
||||
getConnectedEdges,
|
||||
getIncomers,
|
||||
getOutgoers,
|
||||
Node,
|
||||
NodeChange,
|
||||
OnConnectStartParams,
|
||||
SelectionMode,
|
||||
updateEdge,
|
||||
Viewport,
|
||||
XYPosition,
|
||||
} from 'reactflow';
|
||||
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
||||
import {
|
||||
appSocketGeneratorProgress,
|
||||
appSocketInvocationComplete,
|
||||
appSocketInvocationError,
|
||||
appSocketInvocationStarted,
|
||||
appSocketQueueItemStatusChanged,
|
||||
} from 'services/events/actions';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
|
||||
import {
|
||||
BoardFieldValue,
|
||||
@ -58,6 +29,35 @@ import {
|
||||
zNodeStatus,
|
||||
} from 'features/nodes/types/invocation';
|
||||
import { WorkflowV2 } from 'features/nodes/types/workflow';
|
||||
import { cloneDeep, forEach, isEqual, uniqBy } from 'lodash-es';
|
||||
import {
|
||||
addEdge,
|
||||
applyEdgeChanges,
|
||||
applyNodeChanges,
|
||||
Connection,
|
||||
Edge,
|
||||
EdgeChange,
|
||||
EdgeRemoveChange,
|
||||
getConnectedEdges,
|
||||
getIncomers,
|
||||
getOutgoers,
|
||||
Node,
|
||||
NodeChange,
|
||||
OnConnectStartParams,
|
||||
SelectionMode,
|
||||
updateEdge,
|
||||
Viewport,
|
||||
XYPosition,
|
||||
} from 'reactflow';
|
||||
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
||||
import {
|
||||
appSocketGeneratorProgress,
|
||||
appSocketInvocationComplete,
|
||||
appSocketInvocationError,
|
||||
appSocketInvocationStarted,
|
||||
appSocketQueueItemStatusChanged,
|
||||
} from 'services/events/actions';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { NodesState } from './types';
|
||||
import { findConnectionToValidHandle } from './util/findConnectionToValidHandle';
|
||||
import { findUnoccupiedPosition } from './util/findUnoccupiedPosition';
|
||||
@ -81,7 +81,7 @@ const INITIAL_WORKFLOW: WorkflowV2 = {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
exposedFields: [],
|
||||
meta: { version: '2.0.0' },
|
||||
meta: { version: '2.0.0', category: 'user' },
|
||||
};
|
||||
|
||||
export const initialNodesState: NodesState = {
|
||||
|
@ -13,6 +13,9 @@ 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 type WorkflowCategory = z.infer<typeof zWorkflowCategory>;
|
||||
// #endregion
|
||||
|
||||
// #region Workflow Nodes
|
||||
@ -85,6 +88,7 @@ export const zWorkflowV2 = z.object({
|
||||
edges: z.array(zWorkflowEdge),
|
||||
exposedFields: z.array(zFieldIdentifier),
|
||||
meta: z.object({
|
||||
category: zWorkflowCategory.default('user'),
|
||||
version: z.literal('2.0.0'),
|
||||
}),
|
||||
});
|
||||
|
@ -8,11 +8,15 @@ import {
|
||||
import { fromZodError } from 'zod-validation-error';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import i18n from 'i18next';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
export const buildWorkflow = (nodesState: NodesState): WorkflowV2 => {
|
||||
const { workflow: workflowMeta, nodes, edges } = nodesState;
|
||||
const workflow: WorkflowV2 = {
|
||||
...workflowMeta,
|
||||
const workflow = cloneDeep(nodesState.workflow);
|
||||
const nodes = cloneDeep(nodesState.nodes);
|
||||
const edges = cloneDeep(nodesState.edges);
|
||||
|
||||
const newWorkflow: WorkflowV2 = {
|
||||
...workflow,
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
@ -30,7 +34,7 @@ export const buildWorkflow = (nodesState: NodesState): WorkflowV2 => {
|
||||
logger('nodes').warn({ node: parseify(node) }, message);
|
||||
return;
|
||||
}
|
||||
workflow.nodes.push(result.data);
|
||||
newWorkflow.nodes.push(result.data);
|
||||
});
|
||||
|
||||
edges.forEach((edge) => {
|
||||
@ -42,8 +46,8 @@ export const buildWorkflow = (nodesState: NodesState): WorkflowV2 => {
|
||||
logger('nodes').warn({ edge: parseify(edge) }, message);
|
||||
return;
|
||||
}
|
||||
workflow.edges.push(result.data);
|
||||
newWorkflow.edges.push(result.data);
|
||||
});
|
||||
|
||||
return workflow;
|
||||
return newWorkflow;
|
||||
};
|
||||
|
@ -66,6 +66,7 @@ const migrateV1toV2 = (workflowToMigrate: WorkflowV1): WorkflowV2 => {
|
||||
});
|
||||
// Bump version
|
||||
(workflowToMigrate as unknown as WorkflowV2).meta.version = '2.0.0';
|
||||
(workflowToMigrate as unknown as WorkflowV2).meta.category = 'user';
|
||||
// Parsing strips out any extra properties not in the latest version
|
||||
return zWorkflowV2.parse(workflowToMigrate);
|
||||
};
|
||||
|
@ -39,6 +39,13 @@ export const validateWorkflow = (
|
||||
// Parse the raw workflow data & migrate it to the latest version
|
||||
const _workflow = parseAndMigrateWorkflow(workflow);
|
||||
|
||||
// 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') {
|
||||
_workflow.meta.category = 'user';
|
||||
_workflow.id = undefined;
|
||||
}
|
||||
|
||||
// Now we can validate the graph
|
||||
const { nodes, edges } = _workflow;
|
||||
const warnings: WorkflowWarning[] = [];
|
||||
|
@ -1,21 +0,0 @@
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { useDuplicateLibraryWorkflow } from 'features/workflowLibrary/hooks/useDuplicateWorkflow';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaClone } from 'react-icons/fa';
|
||||
|
||||
const DuplicateLibraryWorkflowButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const { duplicateWorkflow, isLoading } = useDuplicateLibraryWorkflow();
|
||||
return (
|
||||
<IAIIconButton
|
||||
icon={<FaClone />}
|
||||
onClick={duplicateWorkflow}
|
||||
isLoading={isLoading}
|
||||
tooltip={t('workflows.duplicateWorkflow')}
|
||||
aria-label={t('workflows.duplicateWorkflow')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(DuplicateLibraryWorkflowButton);
|
@ -0,0 +1,83 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogBody,
|
||||
AlertDialogContent,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogOverlay,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Input,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { useSaveWorkflowAs } from 'features/workflowLibrary/hooks/useDuplicateWorkflow';
|
||||
import { ChangeEvent, memo, useCallback, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaClone } from 'react-icons/fa';
|
||||
|
||||
const SaveWorkflowAsButton = () => {
|
||||
const currentName = useAppSelector((state) => state.nodes.workflow.name);
|
||||
const { t } = useTranslation();
|
||||
const { saveWorkflowAs, isLoading } = useSaveWorkflowAs();
|
||||
const [name, setName] = useState(currentName.trim());
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const onSave = useCallback(async () => {
|
||||
saveWorkflowAs({ name, onSuccess: onClose, onError: onClose });
|
||||
}, [name, onClose, saveWorkflowAs]);
|
||||
|
||||
const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setName(e.target.value);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IAIIconButton
|
||||
icon={<FaClone />}
|
||||
onClick={onOpen}
|
||||
isLoading={isLoading}
|
||||
tooltip={t('workflows.saveWorkflowAs')}
|
||||
aria-label={t('workflows.saveWorkflowAs')}
|
||||
/>
|
||||
<AlertDialog
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
leastDestructiveRef={inputRef}
|
||||
isCentered
|
||||
>
|
||||
<AlertDialogOverlay>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||
{t('workflows.saveWorkflowAs')}
|
||||
</AlertDialogHeader>
|
||||
|
||||
<AlertDialogBody>
|
||||
<FormControl>
|
||||
<FormLabel>{t('workflows.workflowName')}</FormLabel>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={name}
|
||||
onChange={onChange}
|
||||
placeholder={t('workflows.workflowName')}
|
||||
/>
|
||||
</FormControl>
|
||||
</AlertDialogBody>
|
||||
|
||||
<AlertDialogFooter>
|
||||
<IAIButton onClick={onClose}>{t('common.cancel')}</IAIButton>
|
||||
<IAIButton colorScheme="accent" onClick={onSave} ml={3}>
|
||||
{t('common.saveAs')}
|
||||
</IAIButton>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogOverlay>
|
||||
</AlertDialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(SaveWorkflowAsButton);
|
@ -1,33 +1,15 @@
|
||||
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
|
||||
import WorkflowLibraryList from 'features/workflowLibrary/components/WorkflowLibraryList';
|
||||
import WorkflowLibraryListWrapper from 'features/workflowLibrary/components/WorkflowLibraryListWrapper';
|
||||
import WorkflowLibrarySystemList from 'features/workflowLibrary/components/WorkflowLibrarySystemList';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import WorkflowLibraryUserList from './WorkflowLibraryUserList';
|
||||
|
||||
const WorkflowLibraryContent = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Tabs w="full" h="full" isLazy>
|
||||
<TabList w="10rem" layerStyle="second" borderRadius="base" p={2}>
|
||||
<Tab>{t('workflows.user')}</Tab>
|
||||
<Tab>{t('workflows.system')}</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<WorkflowLibraryListWrapper>
|
||||
<WorkflowLibraryUserList />
|
||||
</WorkflowLibraryListWrapper>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<WorkflowLibraryListWrapper>
|
||||
<WorkflowLibrarySystemList />
|
||||
</WorkflowLibraryListWrapper>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
<WorkflowLibraryListWrapper>
|
||||
<WorkflowLibraryList />
|
||||
</WorkflowLibraryListWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { CloseIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
ButtonGroup,
|
||||
Divider,
|
||||
Flex,
|
||||
Heading,
|
||||
IconButton,
|
||||
Input,
|
||||
InputGroup,
|
||||
@ -10,12 +10,14 @@ import {
|
||||
Spacer,
|
||||
} from '@chakra-ui/react';
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import {
|
||||
IAINoContentFallback,
|
||||
IAINoContentFallbackWithSpinner,
|
||||
} from 'common/components/IAIImageFallback';
|
||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
|
||||
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';
|
||||
@ -40,19 +42,19 @@ const DIRECTION_DATA: SelectItem[] = [
|
||||
|
||||
const WorkflowLibraryList = () => {
|
||||
const { t } = useTranslation();
|
||||
const [category, setCategory] = useState<WorkflowCategory>('user');
|
||||
const [page, setPage] = useState(0);
|
||||
const [filter_text, setFilterText] = useState('');
|
||||
const [order_by, setOrderBy] = useState<WorkflowRecordOrderBy>('opened_at');
|
||||
const [direction, setDirection] = useState<SQLiteDirection>('ASC');
|
||||
const [debouncedFilterText] = useDebounce(filter_text, 500, {
|
||||
leading: true,
|
||||
});
|
||||
const [debouncedFilterText] = useDebounce(filter_text, 500);
|
||||
const { data, isLoading, isError, isFetching } = useListWorkflowsQuery({
|
||||
page,
|
||||
per_page: PER_PAGE,
|
||||
order_by,
|
||||
direction,
|
||||
filter_name: debouncedFilterText,
|
||||
category,
|
||||
filter_text: debouncedFilterText,
|
||||
});
|
||||
|
||||
const handleChangeOrderBy = useCallback(
|
||||
@ -102,6 +104,14 @@ const WorkflowLibraryList = () => {
|
||||
[]
|
||||
);
|
||||
|
||||
const handleSetUserCategory = useCallback(() => {
|
||||
setCategory('user');
|
||||
}, []);
|
||||
|
||||
const handleSetSystemCategory = useCallback(() => {
|
||||
setCategory('system');
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return <IAINoContentFallbackWithSpinner label={t('workflows.loading')} />;
|
||||
}
|
||||
@ -113,7 +123,22 @@ const WorkflowLibraryList = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex gap={4} alignItems="center" h={10} flexShrink={0} flexGrow={0}>
|
||||
<Heading size="md">{t('workflows.userWorkflows')}</Heading>
|
||||
<ButtonGroup>
|
||||
<IAIButton
|
||||
variant={category === 'user' ? undefined : 'ghost'}
|
||||
onClick={handleSetUserCategory}
|
||||
isChecked={category === 'user'}
|
||||
>
|
||||
{t('workflows.userWorkflows')}
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
variant={category === 'system' ? undefined : 'ghost'}
|
||||
onClick={handleSetSystemCategory}
|
||||
isChecked={category === 'system'}
|
||||
>
|
||||
{t('workflows.systemWorkflows')}
|
||||
</IAIButton>
|
||||
</ButtonGroup>
|
||||
<Spacer />
|
||||
<InputGroup w="20rem">
|
||||
<Input
|
||||
@ -166,7 +191,7 @@ const WorkflowLibraryList = () => {
|
||||
<Divider />
|
||||
{data.items.length ? (
|
||||
<ScrollableContent>
|
||||
<Flex w="full" h="full" gap={2} flexDir="column">
|
||||
<Flex w="full" h="full" gap={2} px={1} flexDir="column">
|
||||
{data.items.map((w) => (
|
||||
<WorkflowLibraryListItem key={w.workflow_id} workflowDTO={w} />
|
||||
))}
|
@ -72,14 +72,16 @@ const WorkflowLibraryListItem = ({ workflowDTO }: Props) => {
|
||||
>
|
||||
{t('common.load')}
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
colorScheme="error"
|
||||
onClick={handleDeleteWorkflow}
|
||||
isLoading={deleteWorkflowResult.isLoading}
|
||||
aria-label={t('workflows.deleteWorkflow')}
|
||||
>
|
||||
{t('common.delete')}
|
||||
</IAIButton>
|
||||
{workflowDTO.category === 'user' && (
|
||||
<IAIButton
|
||||
colorScheme="error"
|
||||
onClick={handleDeleteWorkflow}
|
||||
isLoading={deleteWorkflowResult.isLoading}
|
||||
aria-label={t('workflows.deleteWorkflow')}
|
||||
>
|
||||
{t('common.delete')}
|
||||
</IAIButton>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
|
@ -3,35 +3,47 @@ import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
||||
import { workflowLoaded } from 'features/nodes/store/nodesSlice';
|
||||
import { zWorkflowV2 } from 'features/nodes/types/workflow';
|
||||
import { omit } from 'lodash-es';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCreateWorkflowMutation } from 'services/api/endpoints/workflows';
|
||||
|
||||
export const useDuplicateLibraryWorkflow = () => {
|
||||
type Arg = {
|
||||
name: string;
|
||||
onSuccess?: () => void;
|
||||
onError?: () => void;
|
||||
};
|
||||
|
||||
export const useSaveWorkflowAs = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const workflow = useWorkflow();
|
||||
const [createWorkflow, createWorkflowResult] = useCreateWorkflowMutation();
|
||||
const toaster = useAppToaster();
|
||||
const duplicateWorkflow = useCallback(async () => {
|
||||
try {
|
||||
const data = await createWorkflow(omit(workflow, 'id')).unwrap();
|
||||
const createdWorkflow = zWorkflowV2.parse(data.workflow);
|
||||
dispatch(workflowLoaded(createdWorkflow));
|
||||
toaster({
|
||||
title: t('workflows.workflowSaved'),
|
||||
status: 'success',
|
||||
});
|
||||
} catch (e) {
|
||||
toaster({
|
||||
title: t('workflows.problemSavingWorkflow'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
}, [workflow, dispatch, toaster, t, createWorkflow]);
|
||||
const saveWorkflowAs = useCallback(
|
||||
async ({ name: newName, onSuccess, onError }: Arg) => {
|
||||
try {
|
||||
workflow.id = undefined;
|
||||
workflow.name = newName;
|
||||
const data = await createWorkflow(workflow).unwrap();
|
||||
const createdWorkflow = zWorkflowV2.parse(data.workflow);
|
||||
dispatch(workflowLoaded(createdWorkflow));
|
||||
onSuccess && onSuccess();
|
||||
toaster({
|
||||
title: t('workflows.workflowSaved'),
|
||||
status: 'success',
|
||||
});
|
||||
} catch (e) {
|
||||
onError && onError();
|
||||
toaster({
|
||||
title: t('workflows.problemSavingWorkflow'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
},
|
||||
[workflow, dispatch, toaster, t, createWorkflow]
|
||||
);
|
||||
return {
|
||||
duplicateWorkflow,
|
||||
saveWorkflowAs,
|
||||
isLoading: createWorkflowResult.isLoading,
|
||||
isError: createWorkflowResult.isError,
|
||||
};
|
||||
|
File diff suppressed because one or more lines are too long
@ -165,6 +165,7 @@ version = { attr = "invokeai.version.__version__" }
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"invokeai.app.assets" = ["**/*.png"]
|
||||
"invokeai.app.assets.workflows" = ["**/*.json"]
|
||||
"invokeai.assets.fonts" = ["**/*.ttf"]
|
||||
"invokeai.backend" = ["**.png"]
|
||||
"invokeai.configs" = ["*.example", "**/*.yaml", "*.txt"]
|
||||
|
Loading…
Reference in New Issue
Block a user