refactor workflow thumbnails to be separate flow/endpoints

This commit is contained in:
Mary Hipp
2025-02-24 16:12:43 -05:00
committed by psychedelicious
parent d4423aa16f
commit ab4433da2f
14 changed files with 323 additions and 272 deletions

View File

@@ -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} />

View File

@@ -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>
);
};

View File

@@ -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"

View File

@@ -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;

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;

View File

@@ -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: {

View File

@@ -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'];