mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-01-15 02:18:02 -05:00
refactor workflow thumbnails to be separate flow/endpoints
This commit is contained in:
committed by
psychedelicious
parent
d4423aa16f
commit
ab4433da2f
@@ -122,7 +122,7 @@ class ApiDependencies:
|
||||
workflow_records = SqliteWorkflowRecordsStorage(db=db)
|
||||
style_preset_records = SqliteStylePresetRecordsStorage(db=db)
|
||||
style_preset_image_files = StylePresetImageFileStorageDisk(style_presets_folder / "images")
|
||||
workflow_thumbnails = WorkflowThumbnailFileStorageDisk(workflow_thumbnails_folder / "thumbnails")
|
||||
workflow_thumbnails = WorkflowThumbnailFileStorageDisk(workflow_thumbnails_folder)
|
||||
|
||||
services = InvocationServices(
|
||||
board_image_records=board_image_records,
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
from typing import Optional
|
||||
import io
|
||||
import traceback
|
||||
from typing import Optional
|
||||
import json
|
||||
|
||||
from fastapi import APIRouter, Body, File, HTTPException, Path, Query, UploadFile, Form
|
||||
from fastapi import APIRouter, Body, HTTPException, Path, Query, File, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
from PIL import Image
|
||||
|
||||
from invokeai.app.api.dependencies import ApiDependencies
|
||||
from invokeai.app.api.routers.model_manager import IMAGE_MAX_AGE
|
||||
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 (
|
||||
WorkflowValidator,
|
||||
Workflow,
|
||||
WorkflowCategory,
|
||||
WorkflowNotFoundError,
|
||||
WorkflowRecordDTO,
|
||||
WorkflowRecordListItemDTO,
|
||||
WorkflowRecordListItemWithThumbnailDTO,
|
||||
WorkflowRecordOrderBy,
|
||||
WorkflowWithoutIDValidator,
|
||||
)
|
||||
from invokeai.app.services.workflow_thumbnails.workflow_thumbnails_common import (
|
||||
WorkflowThumbnailFileNotFoundException,
|
||||
WorkflowWithoutID,
|
||||
WorkflowRecordWithThumbnailDTO,
|
||||
)
|
||||
|
||||
IMAGE_MAX_AGE = 31536000
|
||||
workflows_router = APIRouter(prefix="/v1/workflows", tags=["workflows"])
|
||||
|
||||
|
||||
@@ -31,18 +27,17 @@ workflows_router = APIRouter(prefix="/v1/workflows", tags=["workflows"])
|
||||
"/i/{workflow_id}",
|
||||
operation_id="get_workflow",
|
||||
responses={
|
||||
200: {"model": WorkflowRecordDTO},
|
||||
200: {"model": WorkflowRecordWithThumbnailDTO},
|
||||
},
|
||||
)
|
||||
async def get_workflow(
|
||||
workflow_id: str = Path(description="The workflow to get"),
|
||||
) -> WorkflowRecordDTO:
|
||||
) -> WorkflowRecordWithThumbnailDTO:
|
||||
"""Gets a workflow"""
|
||||
try:
|
||||
thumbnail_url = ApiDependencies.invoker.services.workflow_thumbnails.get_url(workflow_id)
|
||||
workflow = ApiDependencies.invoker.services.workflow_records.get(workflow_id)
|
||||
workflow.thumbnail_url = thumbnail_url
|
||||
return workflow
|
||||
return WorkflowRecordWithThumbnailDTO(thumbnail_url=thumbnail_url, **workflow.model_dump())
|
||||
except WorkflowNotFoundError:
|
||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
||||
|
||||
@@ -55,42 +50,10 @@ async def get_workflow(
|
||||
},
|
||||
)
|
||||
async def update_workflow(
|
||||
workflow: str = Form(description="The updated workflow"),
|
||||
image: Optional[UploadFile] = File(description="The image file to upload", default=None),
|
||||
workflow: Workflow = Body(description="The updated workflow", embed=True),
|
||||
) -> WorkflowRecordDTO:
|
||||
"""Updates a workflow"""
|
||||
|
||||
# parsed_data = json.loads(workflow)
|
||||
validated_workflow = WorkflowValidator.validate_json(workflow)
|
||||
|
||||
if image is not None:
|
||||
if not image.content_type or not image.content_type.startswith("image"):
|
||||
raise HTTPException(status_code=415, detail="Not an image")
|
||||
|
||||
contents = await image.read()
|
||||
try:
|
||||
pil_image = Image.open(io.BytesIO(contents))
|
||||
|
||||
except Exception:
|
||||
ApiDependencies.invoker.services.logger.error(traceback.format_exc())
|
||||
raise HTTPException(status_code=415, detail="Failed to read image")
|
||||
|
||||
try:
|
||||
ApiDependencies.invoker.services.workflow_thumbnails.save(
|
||||
workflow_id=validated_workflow.id, image=pil_image
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=409, detail=str(e))
|
||||
else:
|
||||
try:
|
||||
ApiDependencies.invoker.services.workflow_thumbnails.delete(workflow_id=validated_workflow.id)
|
||||
except WorkflowThumbnailFileNotFoundException:
|
||||
pass
|
||||
|
||||
updated_workflow = ApiDependencies.invoker.services.workflow_records.update(workflow=validated_workflow)
|
||||
thumbnail_url = ApiDependencies.invoker.services.workflow_thumbnails.get_url(validated_workflow.id)
|
||||
updated_workflow.thumbnail_url = thumbnail_url
|
||||
return updated_workflow
|
||||
return ApiDependencies.invoker.services.workflow_records.update(workflow=workflow)
|
||||
|
||||
|
||||
@workflows_router.delete(
|
||||
@@ -101,8 +64,8 @@ async def delete_workflow(
|
||||
workflow_id: str = Path(description="The workflow to delete"),
|
||||
) -> None:
|
||||
"""Deletes a workflow"""
|
||||
ApiDependencies.invoker.services.workflow_records.delete(workflow_id)
|
||||
ApiDependencies.invoker.services.workflow_thumbnails.delete(workflow_id)
|
||||
ApiDependencies.invoker.services.workflow_records.delete(workflow_id)
|
||||
|
||||
|
||||
@workflows_router.post(
|
||||
@@ -113,44 +76,17 @@ async def delete_workflow(
|
||||
},
|
||||
)
|
||||
async def create_workflow(
|
||||
workflow: str = Form(description="The workflow to create"),
|
||||
image: Optional[UploadFile] = File(description="The image file to upload", default=None),
|
||||
workflow: WorkflowWithoutID = Body(description="The workflow to create", embed=True),
|
||||
) -> WorkflowRecordDTO:
|
||||
"""Creates a workflow"""
|
||||
|
||||
# parsed_data = json.loads(workflow)
|
||||
validated_workflow = WorkflowWithoutIDValidator.validate_json(workflow)
|
||||
|
||||
new_workflow = ApiDependencies.invoker.services.workflow_records.create(workflow=validated_workflow)
|
||||
|
||||
if image is not None:
|
||||
if not image.content_type or not image.content_type.startswith("image"):
|
||||
raise HTTPException(status_code=415, detail="Not an image")
|
||||
|
||||
contents = await image.read()
|
||||
try:
|
||||
pil_image = Image.open(io.BytesIO(contents))
|
||||
except Exception:
|
||||
ApiDependencies.invoker.services.logger.error(traceback.format_exc())
|
||||
raise HTTPException(status_code=415, detail="Failed to read image")
|
||||
|
||||
try:
|
||||
ApiDependencies.invoker.services.workflow_thumbnails.save(
|
||||
workflow_id=new_workflow.workflow_id, image=pil_image
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=409, detail=str(e))
|
||||
|
||||
thumbnail_url = ApiDependencies.invoker.services.workflow_thumbnails.get_url(new_workflow.workflow_id)
|
||||
new_workflow.thumbnail_url = thumbnail_url
|
||||
return new_workflow
|
||||
return ApiDependencies.invoker.services.workflow_records.create(workflow=workflow)
|
||||
|
||||
|
||||
@workflows_router.get(
|
||||
"/",
|
||||
operation_id="list_workflows",
|
||||
responses={
|
||||
200: {"model": PaginatedResults[WorkflowRecordListItemDTO]},
|
||||
200: {"model": PaginatedResults[WorkflowRecordListItemWithThumbnailDTO]},
|
||||
},
|
||||
)
|
||||
async def list_workflows(
|
||||
@@ -162,37 +98,111 @@ async def list_workflows(
|
||||
direction: SQLiteDirection = Query(default=SQLiteDirection.Ascending, description="The direction to order by"),
|
||||
category: WorkflowCategory = Query(default=WorkflowCategory.User, description="The category of workflow to get"),
|
||||
query: Optional[str] = Query(default=None, description="The text to query by (matches name and description)"),
|
||||
) -> PaginatedResults[WorkflowRecordListItemDTO]:
|
||||
) -> PaginatedResults[WorkflowRecordListItemWithThumbnailDTO]:
|
||||
"""Gets a page of workflows"""
|
||||
workflows_with_thumbnails: list[WorkflowRecordListItemWithThumbnailDTO] = []
|
||||
workflows = ApiDependencies.invoker.services.workflow_records.get_many(
|
||||
order_by=order_by, direction=direction, page=page, per_page=per_page, query=query, category=category
|
||||
)
|
||||
for workflow in workflows.items:
|
||||
workflow.thumbnail_url = ApiDependencies.invoker.services.workflow_thumbnails.get_url(workflow.workflow_id)
|
||||
return workflows
|
||||
workflows_with_thumbnails.append(
|
||||
WorkflowRecordListItemWithThumbnailDTO(
|
||||
thumbnail_url=ApiDependencies.invoker.services.workflow_thumbnails.get_url(workflow.workflow_id),
|
||||
**workflow.model_dump(),
|
||||
)
|
||||
)
|
||||
return PaginatedResults[WorkflowRecordListItemWithThumbnailDTO](
|
||||
items=workflows_with_thumbnails,
|
||||
total=workflows.total,
|
||||
page=workflows.page,
|
||||
pages=workflows.pages,
|
||||
per_page=workflows.per_page,
|
||||
)
|
||||
|
||||
|
||||
@workflows_router.put(
|
||||
"/i/{workflow_id}/thumbnail",
|
||||
operation_id="set_workflow_thumbnail",
|
||||
responses={
|
||||
200: {"model": WorkflowRecordDTO},
|
||||
},
|
||||
)
|
||||
async def set_workflow_thumbnail(
|
||||
workflow_id: str = Path(description="The workflow to update"),
|
||||
image: UploadFile = File(description="The image file to upload"),
|
||||
):
|
||||
"""Sets a workflow's thumbnail image"""
|
||||
try:
|
||||
ApiDependencies.invoker.services.workflow_records.get(workflow_id)
|
||||
except WorkflowNotFoundError:
|
||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
||||
|
||||
if not image.content_type or not image.content_type.startswith("image"):
|
||||
raise HTTPException(status_code=415, detail="Not an image")
|
||||
|
||||
contents = await image.read()
|
||||
try:
|
||||
pil_image = Image.open(io.BytesIO(contents))
|
||||
|
||||
except Exception:
|
||||
ApiDependencies.invoker.services.logger.error(traceback.format_exc())
|
||||
raise HTTPException(status_code=415, detail="Failed to read image")
|
||||
|
||||
try:
|
||||
ApiDependencies.invoker.services.workflow_thumbnails.save(workflow_id, pil_image)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@workflows_router.delete(
|
||||
"/i/{workflow_id}/thumbnail",
|
||||
operation_id="delete_workflow_thumbnail",
|
||||
responses={
|
||||
200: {"model": WorkflowRecordDTO},
|
||||
},
|
||||
)
|
||||
async def delete_workflow_thumbnail(
|
||||
workflow_id: str = Path(description="The workflow to update"),
|
||||
):
|
||||
"""Removes a workflow's thumbnail image"""
|
||||
try:
|
||||
ApiDependencies.invoker.services.workflow_records.get(workflow_id)
|
||||
except WorkflowNotFoundError:
|
||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
||||
|
||||
try:
|
||||
ApiDependencies.invoker.services.workflow_thumbnails.delete(workflow_id)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@workflows_router.get(
|
||||
"i/{workflow_id}/thumbnail",
|
||||
"/i/{workflow_id}/thumbnail",
|
||||
operation_id="get_workflow_thumbnail",
|
||||
responses={
|
||||
200: {"description": "Thumbnail retrieved successfully"},
|
||||
404: {"description": "Thumbnail not found"},
|
||||
200: {
|
||||
"description": "The workflow thumbnail was fetched successfully",
|
||||
},
|
||||
400: {"description": "Bad request"},
|
||||
404: {"description": "The workflow thumbnail could not be found"},
|
||||
},
|
||||
status_code=200,
|
||||
)
|
||||
async def get_workflow_thumbnail(
|
||||
workflow_id: str,
|
||||
workflow_id: str = Path(description="The id of the workflow thumbnail to get"),
|
||||
) -> FileResponse:
|
||||
"""Gets the thumbnail for a workflow"""
|
||||
"""Gets a workflow's thumbnail image"""
|
||||
|
||||
try:
|
||||
path = ApiDependencies.invoker.services.workflow_thumbnails.get_path(workflow_id)
|
||||
|
||||
response = FileResponse(
|
||||
path,
|
||||
media_type="image/png",
|
||||
filename=f"{workflow_id}.png",
|
||||
filename=workflow_id + ".png",
|
||||
content_disposition_type="inline",
|
||||
)
|
||||
response.headers["Cache-Control"] = f"max-age={IMAGE_MAX_AGE}"
|
||||
return response
|
||||
except WorkflowThumbnailFileNotFoundException:
|
||||
raise HTTPException(status_code=404, detail="Thumbnail not found")
|
||||
except Exception:
|
||||
raise HTTPException(status_code=404)
|
||||
@@ -101,7 +101,6 @@ class WorkflowRecordDTOBase(BaseModel):
|
||||
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.")
|
||||
thumbnail_url: str | None = Field(default=None, description="The URL of the workflow thumbnail.")
|
||||
|
||||
|
||||
class WorkflowRecordDTO(WorkflowRecordDTOBase):
|
||||
@@ -122,3 +121,11 @@ class WorkflowRecordListItemDTO(WorkflowRecordDTOBase):
|
||||
|
||||
|
||||
WorkflowRecordListItemDTOValidator = TypeAdapter(WorkflowRecordListItemDTO)
|
||||
|
||||
|
||||
class WorkflowRecordWithThumbnailDTO(WorkflowRecordDTO):
|
||||
thumbnail_url: str | None = Field(default=None, description="The URL of the workflow thumbnail.")
|
||||
|
||||
|
||||
class WorkflowRecordListItemWithThumbnailDTO(WorkflowRecordListItemDTO):
|
||||
thumbnail_url: str | None = Field(default=None, description="The URL of the workflow thumbnail.")
|
||||
|
||||
@@ -34,7 +34,7 @@ class WorkflowThumbnailFileStorageDisk(WorkflowThumbnailServiceBase):
|
||||
try:
|
||||
self._validate_storage_folders()
|
||||
image_path = self._workflow_thumbnail_folder / (workflow_id + ".webp")
|
||||
thumbnail = make_thumbnail(image, 512)
|
||||
thumbnail = make_thumbnail(image, 256)
|
||||
thumbnail.save(image_path, format="webp")
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -149,6 +149,7 @@
|
||||
"safetensors": "Safetensors",
|
||||
"save": "Save",
|
||||
"saveAs": "Save As",
|
||||
"saveChanges": "Save Changes",
|
||||
"settingsLabel": "Settings",
|
||||
"simple": "Simple",
|
||||
"somethingWentWrong": "Something went wrong",
|
||||
@@ -1721,6 +1722,8 @@
|
||||
"copyShareLinkForWorkflow": "Copy Share Link for Workflow",
|
||||
"delete": "Delete",
|
||||
"openLibrary": "Open Library",
|
||||
"workflowThumbnail": "Workflow Thumbnail",
|
||||
"saveChanges": "Save Changes",
|
||||
"builder": {
|
||||
"deleteAllElements": "Delete All Form Elements",
|
||||
"resetAllNodeFields": "Reset All Node Fields",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { FormControlProps } from '@invoke-ai/ui-library';
|
||||
import { Flex, FormControl, FormControlGroup, FormLabel, Input, Textarea } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
@@ -11,19 +12,20 @@ import {
|
||||
workflowNameChanged,
|
||||
workflowNotesChanged,
|
||||
workflowTagsChanged,
|
||||
workflowThumbnailChanged,
|
||||
workflowVersionChanged,
|
||||
} from 'features/nodes/store/workflowSlice';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetWorkflowQuery } from 'services/api/endpoints/workflows';
|
||||
|
||||
import { WorkflowThumbnailField } from './WorkflowThumbnailField';
|
||||
import { WorkflowThumbnailEditor } from './WorkflowThumbnail/WorkflowThumbnailEditor';
|
||||
|
||||
const selector = createMemoizedSelector(selectWorkflowSlice, (workflow) => {
|
||||
const { author, name, description, tags, version, contact, notes, thumbnail } = workflow;
|
||||
const { id, author, name, description, tags, version, contact, notes } = workflow;
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
author,
|
||||
description,
|
||||
@@ -31,14 +33,15 @@ const selector = createMemoizedSelector(selectWorkflowSlice, (workflow) => {
|
||||
version,
|
||||
contact,
|
||||
notes,
|
||||
thumbnail,
|
||||
};
|
||||
});
|
||||
|
||||
const WorkflowGeneralTab = () => {
|
||||
const { author, name, description, tags, version, contact, notes, thumbnail } = useAppSelector(selector);
|
||||
const { id, author, name, description, tags, version, contact, notes } = useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { data } = useGetWorkflowQuery(id ?? skipToken);
|
||||
|
||||
const handleChangeName = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(workflowNameChanged(e.target.value));
|
||||
@@ -83,13 +86,6 @@ const WorkflowGeneralTab = () => {
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleChangeThumbnail = useCallback(
|
||||
(localImageUrl: string | null) => {
|
||||
dispatch(workflowThumbnailChanged(localImageUrl));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -100,15 +96,14 @@ const WorkflowGeneralTab = () => {
|
||||
<FormLabel>{t('nodes.workflowName')}</FormLabel>
|
||||
<Input variant="darkFilled" value={name} onChange={handleChangeName} />
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>{t('workflows.workflowThumbnail')}</FormLabel>
|
||||
<WorkflowThumbnailEditor thumbnailUrl={data?.thumbnail_url || null} workflowId={id} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>{t('nodes.workflowVersion')}</FormLabel>
|
||||
<Input variant="darkFilled" value={version} onChange={handleChangeVersion} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>Thumbnail</FormLabel>
|
||||
<WorkflowThumbnailField imageUrl={thumbnail} onChange={handleChangeThumbnail} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>{t('nodes.workflowAuthor')}</FormLabel>
|
||||
<Input variant="darkFilled" value={author} onChange={handleChangeAuthor} />
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Button, Flex } from '@invoke-ai/ui-library';
|
||||
import { convertImageUrlToBlob } from 'common/util/convertImageUrlToBlob';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDeleteWorkflowThumbnailMutation, useSetWorkflowThumbnailMutation } from 'services/api/endpoints/workflows';
|
||||
|
||||
import { WorkflowThumbnailField } from './WorkflowThumbnailField';
|
||||
|
||||
export const WorkflowThumbnailEditor = ({
|
||||
workflowId,
|
||||
thumbnailUrl,
|
||||
}: {
|
||||
workflowId?: string;
|
||||
thumbnailUrl: string | null;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [localThumbnailUrl, setLocalThumbnailUrl] = useState<string | null>(null);
|
||||
const [canSaveChanges, setCanSaveChanges] = useState(false);
|
||||
|
||||
const [setThumbnail, { isLoading }] = useSetWorkflowThumbnailMutation();
|
||||
const [deleteThumbnail, { isLoading: isDeleting }] = useDeleteWorkflowThumbnailMutation();
|
||||
|
||||
const handleLocalThumbnailUrlChange = useCallback((url: string | null) => {
|
||||
setLocalThumbnailUrl(url);
|
||||
setCanSaveChanges(true);
|
||||
}, []);
|
||||
|
||||
const handleSaveChanges = useCallback(async () => {
|
||||
if (!workflowId) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (localThumbnailUrl) {
|
||||
const blob = await convertImageUrlToBlob(localThumbnailUrl);
|
||||
if (!blob) {
|
||||
return;
|
||||
}
|
||||
const file = new File([blob], 'workflow_thumbnail.png', { type: 'image/png' });
|
||||
await setThumbnail({ workflow_id: workflowId, image: file }).unwrap();
|
||||
} else {
|
||||
await deleteThumbnail(workflowId).unwrap();
|
||||
}
|
||||
|
||||
setCanSaveChanges(false);
|
||||
toast({ status: 'success', title: 'Workflow thumbnail updated' });
|
||||
} catch (error) {
|
||||
toast({ status: 'error', title: 'Failed to update thumbnail' });
|
||||
}
|
||||
}, [deleteThumbnail, setThumbnail, workflowId, localThumbnailUrl]);
|
||||
|
||||
return (
|
||||
<Flex alignItems="center" gap={4}>
|
||||
<WorkflowThumbnailField imageUrl={thumbnailUrl} onChange={handleLocalThumbnailUrlChange} />
|
||||
|
||||
<Button size="sm" isLoading={isLoading || isDeleting} onClick={handleSaveChanges} isDisabled={!canSaveChanges}>
|
||||
{t('common.saveChanges')}
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -10,7 +10,7 @@ export const WorkflowThumbnailField = ({
|
||||
onChange,
|
||||
}: {
|
||||
imageUrl: string | null;
|
||||
onChange: (localImageUrl: string | null) => void;
|
||||
onChange: (localThumbnailUrl: string | null) => void;
|
||||
}) => {
|
||||
const [thumbnail, setThumbnail] = useState<File | null>(null);
|
||||
|
||||
@@ -48,7 +48,8 @@ export const WorkflowThumbnailField = ({
|
||||
|
||||
const handleResetImage = useCallback(() => {
|
||||
setThumbnail(null);
|
||||
}, []);
|
||||
onChange(null);
|
||||
}, [onChange]);
|
||||
|
||||
const { getInputProps, getRootProps } = useDropzone({
|
||||
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
|
||||
@@ -64,9 +65,8 @@ export const WorkflowThumbnailField = ({
|
||||
src={URL.createObjectURL(thumbnail)}
|
||||
objectFit="cover"
|
||||
objectPosition="50% 50%"
|
||||
w={65}
|
||||
h={65}
|
||||
minWidth={65}
|
||||
w={100}
|
||||
h={100}
|
||||
borderRadius="base"
|
||||
/>
|
||||
<IconButton
|
||||
@@ -89,8 +89,8 @@ export const WorkflowThumbnailField = ({
|
||||
<Tooltip label={t('stylePresets.uploadImage')}>
|
||||
<Flex
|
||||
as={Button}
|
||||
w={65}
|
||||
h={65}
|
||||
w={100}
|
||||
h={100}
|
||||
opacity={0.3}
|
||||
borderRadius="base"
|
||||
alignItems="center"
|
||||
@@ -109,10 +109,6 @@ export const workflowSlice = createSlice({
|
||||
state.name = action.payload;
|
||||
state.isTouched = true;
|
||||
},
|
||||
workflowThumbnailChanged: (state, action: PayloadAction<string | null>) => {
|
||||
state.thumbnail = action.payload;
|
||||
state.isTouched = true;
|
||||
},
|
||||
workflowCategoryChanged: (state, action: PayloadAction<WorkflowCategory | undefined>) => {
|
||||
if (action.payload) {
|
||||
state.meta.category = action.payload;
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import type { ToastId } from '@invoke-ai/ui-library';
|
||||
import { useToast } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { convertImageUrlToBlob } from 'common/util/convertImageUrlToBlob';
|
||||
import { $builtWorkflow } from 'features/nodes/hooks/useWorkflowWatcher';
|
||||
import {
|
||||
formFieldInitialValuesChanged,
|
||||
selectWorkflowThumbnail,
|
||||
workflowIDChanged,
|
||||
workflowSaved,
|
||||
} from 'features/nodes/store/workflowSlice';
|
||||
import { formFieldInitialValuesChanged, workflowIDChanged, workflowSaved } from 'features/nodes/store/workflowSlice';
|
||||
import type { WorkflowV3 } from 'features/nodes/types/workflow';
|
||||
import { useGetFormFieldInitialValues } from 'features/workflowLibrary/hooks/useGetFormInitialValues';
|
||||
import { workflowUpdated } from 'features/workflowLibrary/store/actions';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useCreateWorkflowMutation, useUpdateWorkflowMutation, workflowsApi } from 'services/api/endpoints/workflows';
|
||||
import type { SetRequired } from 'type-fest';
|
||||
|
||||
@@ -33,7 +26,6 @@ export const useSaveLibraryWorkflow: UseSaveLibraryWorkflow = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const getFormFieldInitialValues = useGetFormFieldInitialValues();
|
||||
const thumbnail = useSelector(selectWorkflowThumbnail);
|
||||
const [updateWorkflow, updateWorkflowResult] = useUpdateWorkflowMutation();
|
||||
const [createWorkflow, createWorkflowResult] = useCreateWorkflowMutation();
|
||||
const toast = useToast();
|
||||
@@ -50,13 +42,11 @@ export const useSaveLibraryWorkflow: UseSaveLibraryWorkflow = () => {
|
||||
isClosable: false,
|
||||
});
|
||||
try {
|
||||
const blob = thumbnail ? await convertImageUrlToBlob(thumbnail) : null;
|
||||
const image = blob ? new File([blob], 'thumbnail.png', { type: 'image/png' }) : null;
|
||||
if (isWorkflowWithID(workflow)) {
|
||||
await updateWorkflow({ workflow, image }).unwrap();
|
||||
await updateWorkflow(workflow).unwrap();
|
||||
dispatch(workflowUpdated());
|
||||
} else {
|
||||
const data = await createWorkflow({ workflow, image }).unwrap();
|
||||
const data = await createWorkflow(workflow).unwrap();
|
||||
dispatch(workflowIDChanged(data.workflow.id));
|
||||
}
|
||||
dispatch(workflowSaved());
|
||||
@@ -83,7 +73,7 @@ export const useSaveLibraryWorkflow: UseSaveLibraryWorkflow = () => {
|
||||
toast.close(toastRef.current);
|
||||
}
|
||||
}
|
||||
}, [toast, t, dispatch, getFormFieldInitialValues, updateWorkflow, createWorkflow, thumbnail]);
|
||||
}, [toast, t, dispatch, getFormFieldInitialValues, updateWorkflow, createWorkflow]);
|
||||
return {
|
||||
saveWorkflow,
|
||||
isLoading: updateWorkflowResult.isLoading || createWorkflowResult.isLoading,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import type { ToastId } from '@invoke-ai/ui-library';
|
||||
import { useToast } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { convertImageUrlToBlob } from 'common/util/convertImageUrlToBlob';
|
||||
import { $builtWorkflow } from 'features/nodes/hooks/useWorkflowWatcher';
|
||||
import {
|
||||
formFieldInitialValuesChanged,
|
||||
selectWorkflowThumbnail,
|
||||
workflowCategoryChanged,
|
||||
workflowIDChanged,
|
||||
workflowNameChanged,
|
||||
@@ -16,7 +14,6 @@ import { useGetFormFieldInitialValues } from 'features/workflowLibrary/hooks/use
|
||||
import { newWorkflowSaved } from 'features/workflowLibrary/store/actions';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useCreateWorkflowMutation, workflowsApi } from 'services/api/endpoints/workflows';
|
||||
|
||||
type SaveWorkflowAsArg = {
|
||||
@@ -40,7 +37,6 @@ export const useSaveWorkflowAs: UseSaveWorkflowAs = () => {
|
||||
const [createWorkflow, createWorkflowResult] = useCreateWorkflowMutation();
|
||||
const getFormFieldInitialValues = useGetFormFieldInitialValues();
|
||||
|
||||
const thumbnail = useSelector(selectWorkflowThumbnail);
|
||||
const toast = useToast();
|
||||
const toastRef = useRef<ToastId | undefined>();
|
||||
const saveWorkflowAs = useCallback(
|
||||
@@ -59,10 +55,8 @@ export const useSaveWorkflowAs: UseSaveWorkflowAs = () => {
|
||||
workflow.id = undefined;
|
||||
workflow.name = newName;
|
||||
workflow.meta.category = category;
|
||||
const blob = thumbnail ? await convertImageUrlToBlob(thumbnail) : null;
|
||||
const image = blob ? new File([blob], 'thumbnail.png', { type: 'image/png' }) : null;
|
||||
|
||||
const data = await createWorkflow({ workflow, image }).unwrap();
|
||||
const data = await createWorkflow(workflow).unwrap();
|
||||
dispatch(workflowIDChanged(data.workflow.id));
|
||||
dispatch(workflowNameChanged(data.workflow.name));
|
||||
dispatch(workflowCategoryChanged(data.workflow.meta.category));
|
||||
@@ -92,7 +86,7 @@ export const useSaveWorkflowAs: UseSaveWorkflowAs = () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
[toast, t, createWorkflow, dispatch, getFormFieldInitialValues, thumbnail]
|
||||
[toast, t, createWorkflow, dispatch, getFormFieldInitialValues]
|
||||
);
|
||||
return {
|
||||
saveWorkflowAs,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { paths } from 'services/api/schema';
|
||||
|
||||
import { api, buildV1Url, LIST_TAG } from '..';
|
||||
import { Workflow, WorkflowWithoutID } from '../types';
|
||||
|
||||
/**
|
||||
* Builds an endpoint URL for the workflows router
|
||||
@@ -42,22 +41,13 @@ export const workflowsApi = api.injectEndpoints({
|
||||
}),
|
||||
createWorkflow: build.mutation<
|
||||
paths['/api/v1/workflows/']['post']['responses']['200']['content']['application/json'],
|
||||
{ workflow: WorkflowWithoutID; image: File | null }
|
||||
paths['/api/v1/workflows/']['post']['requestBody']['content']['application/json']['workflow']
|
||||
>({
|
||||
query: ({ workflow, image }) => {
|
||||
const formData = new FormData();
|
||||
if (image) {
|
||||
formData.append('image', image);
|
||||
}
|
||||
|
||||
formData.append('workflow', JSON.stringify(workflow));
|
||||
|
||||
return {
|
||||
url: buildWorkflowsUrl(),
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
};
|
||||
},
|
||||
query: (workflow) => ({
|
||||
url: buildWorkflowsUrl(),
|
||||
method: 'POST',
|
||||
body: { workflow },
|
||||
}),
|
||||
invalidatesTags: [
|
||||
{ type: 'Workflow', id: LIST_TAG },
|
||||
{ type: 'WorkflowsRecent', id: LIST_TAG },
|
||||
@@ -65,22 +55,14 @@ export const workflowsApi = api.injectEndpoints({
|
||||
}),
|
||||
updateWorkflow: build.mutation<
|
||||
paths['/api/v1/workflows/i/{workflow_id}']['patch']['responses']['200']['content']['application/json'],
|
||||
{ workflow: Workflow; image: File | null }
|
||||
paths['/api/v1/workflows/i/{workflow_id}']['patch']['requestBody']['content']['application/json']['workflow']
|
||||
>({
|
||||
query: ({ workflow, image }) => {
|
||||
const formData = new FormData();
|
||||
if (image) {
|
||||
formData.append('image', image);
|
||||
}
|
||||
formData.append('workflow', JSON.stringify(workflow));
|
||||
|
||||
return {
|
||||
url: buildWorkflowsUrl(`i/${workflow.id}`),
|
||||
method: 'PATCH',
|
||||
body: formData,
|
||||
};
|
||||
},
|
||||
invalidatesTags: (response, error, { workflow }) => [
|
||||
query: (workflow) => ({
|
||||
url: buildWorkflowsUrl(`i/${workflow.id}`),
|
||||
method: 'PATCH',
|
||||
body: { workflow },
|
||||
}),
|
||||
invalidatesTags: (response, error, workflow) => [
|
||||
{ type: 'WorkflowsRecent', id: LIST_TAG },
|
||||
{ type: 'Workflow', id: LIST_TAG },
|
||||
{ type: 'Workflow', id: workflow.id },
|
||||
@@ -96,13 +78,41 @@ export const workflowsApi = api.injectEndpoints({
|
||||
}),
|
||||
providesTags: ['FetchOnReconnect', { type: 'Workflow', id: LIST_TAG }],
|
||||
}),
|
||||
setWorkflowThumbnail: build.mutation<void, { workflow_id: string; image: File }>({
|
||||
query: ({ workflow_id, image }) => {
|
||||
const formData = new FormData();
|
||||
formData.append('image', image);
|
||||
return {
|
||||
url: buildWorkflowsUrl(`i/${workflow_id}/thumbnail`),
|
||||
method: 'PUT',
|
||||
body: formData,
|
||||
};
|
||||
},
|
||||
invalidatesTags: (result, error, { workflow_id }) => [
|
||||
{ type: 'Workflow', id: workflow_id },
|
||||
{ type: 'WorkflowsRecent', id: LIST_TAG },
|
||||
],
|
||||
}),
|
||||
deleteWorkflowThumbnail: build.mutation<void, string>({
|
||||
query: (workflow_id) => ({
|
||||
url: buildWorkflowsUrl(`i/${workflow_id}/thumbnail`),
|
||||
method: 'DELETE',
|
||||
}),
|
||||
invalidatesTags: (result, error, workflow_id) => [
|
||||
{ type: 'Workflow', id: workflow_id },
|
||||
{ type: 'WorkflowsRecent', id: LIST_TAG },
|
||||
],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useLazyGetWorkflowQuery,
|
||||
useGetWorkflowQuery,
|
||||
useCreateWorkflowMutation,
|
||||
useDeleteWorkflowMutation,
|
||||
useUpdateWorkflowMutation,
|
||||
useListWorkflowsQuery,
|
||||
useSetWorkflowThumbnailMutation,
|
||||
useDeleteWorkflowThumbnailMutation,
|
||||
} = workflowsApi;
|
||||
|
||||
@@ -1410,7 +1410,7 @@ export type paths = {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/workflows/{workflow_id}/thumbnail": {
|
||||
"/api/v1/workflows/i/{workflow_id}/thumbnail": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
@@ -1418,33 +1418,17 @@ export type paths = {
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* Upload Workflow Thumbnail
|
||||
* @description Uploads a thumbnail for a workflow
|
||||
* Set Workflow Thumbnail
|
||||
* @description Sets a workflow's thumbnail image
|
||||
*/
|
||||
post: operations["upload_workflow_thumbnail"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/workflowsi/{workflow_id}/thumbnail": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* Get Workflow Thumbnail
|
||||
* @description Gets the thumbnail for a workflow
|
||||
*/
|
||||
get: operations["get_workflow_thumbnail"];
|
||||
put?: never;
|
||||
put: operations["set_workflow_thumbnail"];
|
||||
post?: never;
|
||||
delete?: never;
|
||||
/**
|
||||
* Delete Workflow Thumbnail
|
||||
* @description Removes a workflow's thumbnail image
|
||||
*/
|
||||
delete: operations["delete_workflow_thumbnail"];
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
@@ -2251,16 +2235,8 @@ export type components = {
|
||||
};
|
||||
/** Body_create_workflow */
|
||||
Body_create_workflow: {
|
||||
/**
|
||||
* Workflow
|
||||
* @description The workflow to create
|
||||
*/
|
||||
workflow: string;
|
||||
/**
|
||||
* Image
|
||||
* @description The image file to upload
|
||||
*/
|
||||
image?: Blob | null;
|
||||
/** @description The workflow to create */
|
||||
workflow: components["schemas"]["WorkflowWithoutID"];
|
||||
};
|
||||
/** Body_delete_images_from_list */
|
||||
Body_delete_images_from_list: {
|
||||
@@ -2377,6 +2353,15 @@ export type components = {
|
||||
*/
|
||||
image_names: string[];
|
||||
};
|
||||
/** Body_set_workflow_thumbnail */
|
||||
Body_set_workflow_thumbnail: {
|
||||
/**
|
||||
* Image
|
||||
* Format: binary
|
||||
* @description The image file to upload
|
||||
*/
|
||||
image: Blob;
|
||||
};
|
||||
/** Body_star_images_in_list */
|
||||
Body_star_images_in_list: {
|
||||
/**
|
||||
@@ -2416,16 +2401,8 @@ export type components = {
|
||||
};
|
||||
/** Body_update_workflow */
|
||||
Body_update_workflow: {
|
||||
/**
|
||||
* Workflow
|
||||
* @description The updated workflow
|
||||
*/
|
||||
workflow: string;
|
||||
/**
|
||||
* Image
|
||||
* @description The image file to upload
|
||||
*/
|
||||
image?: Blob | null;
|
||||
/** @description The updated workflow */
|
||||
workflow: components["schemas"]["Workflow"];
|
||||
};
|
||||
/** Body_upload_image */
|
||||
Body_upload_image: {
|
||||
@@ -2437,15 +2414,6 @@ export type components = {
|
||||
/** @description The metadata to associate with the image */
|
||||
metadata?: components["schemas"]["JsonValue"] | null;
|
||||
};
|
||||
/** Body_upload_workflow_thumbnail */
|
||||
Body_upload_workflow_thumbnail: {
|
||||
/**
|
||||
* File
|
||||
* Format: binary
|
||||
* @description The image file to upload
|
||||
*/
|
||||
file: Blob;
|
||||
};
|
||||
/**
|
||||
* Boolean Collection Primitive
|
||||
* @description A collection of boolean primitive values
|
||||
@@ -16367,8 +16335,8 @@ export type components = {
|
||||
/** Ui Order */
|
||||
ui_order: number | null;
|
||||
};
|
||||
/** PaginatedResults[WorkflowRecordListItemDTO] */
|
||||
PaginatedResults_WorkflowRecordListItemDTO_: {
|
||||
/** PaginatedResults[WorkflowRecordListItemWithThumbnailDTO] */
|
||||
PaginatedResults_WorkflowRecordListItemWithThumbnailDTO_: {
|
||||
/**
|
||||
* Page
|
||||
* @description Current Page
|
||||
@@ -16393,7 +16361,7 @@ export type components = {
|
||||
* Items
|
||||
* @description Items
|
||||
*/
|
||||
items: components["schemas"]["WorkflowRecordListItemDTO"][];
|
||||
items: components["schemas"]["WorkflowRecordListItemWithThumbnailDTO"][];
|
||||
};
|
||||
/**
|
||||
* Pair Tile with Image
|
||||
@@ -21152,16 +21120,11 @@ export type components = {
|
||||
* @description The opened timestamp of the workflow.
|
||||
*/
|
||||
opened_at: string;
|
||||
/**
|
||||
* Thumbnail Url
|
||||
* @description The URL of the workflow thumbnail.
|
||||
*/
|
||||
thumbnail_url?: string | null;
|
||||
/** @description The workflow. */
|
||||
workflow: components["schemas"]["Workflow"];
|
||||
};
|
||||
/** WorkflowRecordListItemDTO */
|
||||
WorkflowRecordListItemDTO: {
|
||||
/** WorkflowRecordListItemWithThumbnailDTO */
|
||||
WorkflowRecordListItemWithThumbnailDTO: {
|
||||
/**
|
||||
* Workflow Id
|
||||
* @description The id of the workflow.
|
||||
@@ -21187,11 +21150,6 @@ export type components = {
|
||||
* @description The opened timestamp of the workflow.
|
||||
*/
|
||||
opened_at: string;
|
||||
/**
|
||||
* Thumbnail Url
|
||||
* @description The URL of the workflow thumbnail.
|
||||
*/
|
||||
thumbnail_url?: string | null;
|
||||
/**
|
||||
* Description
|
||||
* @description The description of the workflow.
|
||||
@@ -21199,6 +21157,11 @@ export type components = {
|
||||
description: string;
|
||||
/** @description The description of the workflow. */
|
||||
category: components["schemas"]["WorkflowCategory"];
|
||||
/**
|
||||
* Thumbnail Url
|
||||
* @description The URL of the workflow thumbnail.
|
||||
*/
|
||||
thumbnail_url?: string | null;
|
||||
};
|
||||
/**
|
||||
* WorkflowRecordOrderBy
|
||||
@@ -21206,6 +21169,41 @@ export type components = {
|
||||
* @enum {string}
|
||||
*/
|
||||
WorkflowRecordOrderBy: "created_at" | "updated_at" | "opened_at" | "name";
|
||||
/** WorkflowRecordWithThumbnailDTO */
|
||||
WorkflowRecordWithThumbnailDTO: {
|
||||
/**
|
||||
* Workflow Id
|
||||
* @description The id of the workflow.
|
||||
*/
|
||||
workflow_id: string;
|
||||
/**
|
||||
* Name
|
||||
* @description The name of the workflow.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Created At
|
||||
* @description The created timestamp of the workflow.
|
||||
*/
|
||||
created_at: string;
|
||||
/**
|
||||
* Updated At
|
||||
* @description The updated timestamp of the workflow.
|
||||
*/
|
||||
updated_at: string;
|
||||
/**
|
||||
* Opened At
|
||||
* @description The opened timestamp of the workflow.
|
||||
*/
|
||||
opened_at: string;
|
||||
/** @description The workflow. */
|
||||
workflow: components["schemas"]["Workflow"];
|
||||
/**
|
||||
* Thumbnail Url
|
||||
* @description The URL of the workflow thumbnail.
|
||||
*/
|
||||
thumbnail_url?: string | null;
|
||||
};
|
||||
/** WorkflowWithoutID */
|
||||
WorkflowWithoutID: {
|
||||
/**
|
||||
@@ -24163,7 +24161,7 @@ export interface operations {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["WorkflowRecordDTO"];
|
||||
"application/json": components["schemas"]["WorkflowRecordWithThumbnailDTO"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
@@ -24218,7 +24216,7 @@ export interface operations {
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"multipart/form-data": components["schemas"]["Body_update_workflow"];
|
||||
"application/json": components["schemas"]["Body_update_workflow"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
@@ -24270,7 +24268,7 @@ export interface operations {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["PaginatedResults_WorkflowRecordListItemDTO_"];
|
||||
"application/json": components["schemas"]["PaginatedResults_WorkflowRecordListItemWithThumbnailDTO_"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
@@ -24293,7 +24291,7 @@ export interface operations {
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"multipart/form-data": components["schemas"]["Body_create_workflow"];
|
||||
"application/json": components["schemas"]["Body_create_workflow"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
@@ -24317,37 +24315,31 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
upload_workflow_thumbnail: {
|
||||
set_workflow_thumbnail: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description The workflow to update */
|
||||
workflow_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"multipart/form-data": components["schemas"]["Body_upload_workflow_thumbnail"];
|
||||
"multipart/form-data": components["schemas"]["Body_set_workflow_thumbnail"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Thumbnail uploaded successfully */
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": unknown;
|
||||
"application/json": components["schemas"]["WorkflowRecordDTO"];
|
||||
};
|
||||
};
|
||||
/** @description Invalid image format */
|
||||
415: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
@@ -24359,33 +24351,27 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
get_workflow_thumbnail: {
|
||||
delete_workflow_thumbnail: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description The workflow to update */
|
||||
workflow_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Thumbnail retrieved successfully */
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": unknown;
|
||||
"application/json": components["schemas"]["WorkflowRecordDTO"];
|
||||
};
|
||||
};
|
||||
/** @description Thumbnail not found */
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
|
||||
@@ -20,9 +20,6 @@ export type UpdateBoardArg = paths['/api/v1/boards/{board_id}']['patch']['parame
|
||||
export type GraphAndWorkflowResponse =
|
||||
paths['/api/v1/images/i/{image_name}/workflow']['get']['responses']['200']['content']['application/json'];
|
||||
|
||||
export type WorkflowWithoutID = S['WorkflowWithoutID'];
|
||||
export type Workflow = S['Workflow'];
|
||||
|
||||
export type BatchConfig =
|
||||
paths['/api/v1/queue/{queue_id}/enqueue_batch']['post']['requestBody']['content']['application/json'];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user