mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): create component to upload CSV of style presets to import
This commit is contained in:
parent
2d58754789
commit
76b0380b5f
@ -1141,6 +1141,8 @@
|
|||||||
"imageSavingFailed": "Image Saving Failed",
|
"imageSavingFailed": "Image Saving Failed",
|
||||||
"imageUploaded": "Image Uploaded",
|
"imageUploaded": "Image Uploaded",
|
||||||
"imageUploadFailed": "Image Upload Failed",
|
"imageUploadFailed": "Image Upload Failed",
|
||||||
|
"importFailed": "Import Failed",
|
||||||
|
"importSuccessful": "Import Successful",
|
||||||
"invalidUpload": "Invalid Upload",
|
"invalidUpload": "Invalid Upload",
|
||||||
"loadedWithWarnings": "Workflow Loaded with Warnings",
|
"loadedWithWarnings": "Workflow Loaded with Warnings",
|
||||||
"maskSavedAssets": "Mask Saved to Assets",
|
"maskSavedAssets": "Mask Saved to Assets",
|
||||||
@ -1701,6 +1703,8 @@
|
|||||||
"deleteTemplate2": "Are you sure you want to delete this template? This cannot be undone.",
|
"deleteTemplate2": "Are you sure you want to delete this template? This cannot be undone.",
|
||||||
"editTemplate": "Edit Template",
|
"editTemplate": "Edit Template",
|
||||||
"flatten": "Flatten selected template into current prompt",
|
"flatten": "Flatten selected template into current prompt",
|
||||||
|
"importTemplates": "Import Prompt Templates",
|
||||||
|
"importTemplatesDesc": "Format must be csv with columns: 'name', 'prompt', and 'negative_prompt' included",
|
||||||
"insertPlaceholder": "Insert placeholder",
|
"insertPlaceholder": "Insert placeholder",
|
||||||
"myTemplates": "My Templates",
|
"myTemplates": "My Templates",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||||
|
import { IconButton, spinAnimation, Text } from '@invoke-ai/ui-library';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import type { ChangeEvent } from 'react';
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiSpinner, PiUploadBold } from 'react-icons/pi';
|
||||||
|
import { useImportStylePresetsMutation } from 'services/api/endpoints/stylePresets';
|
||||||
|
|
||||||
|
const loadingStyles: SystemStyleObject = {
|
||||||
|
svg: { animation: spinAnimation },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StylePresetImport = () => {
|
||||||
|
const [importStylePresets, { isLoading }] = useImportStylePresetsMutation();
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleClickUpload = useCallback(() => {
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.click();
|
||||||
|
}
|
||||||
|
}, [fileInputRef]);
|
||||||
|
|
||||||
|
const handleFileChange = useCallback(
|
||||||
|
async (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.files) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await importStylePresets(file).unwrap();
|
||||||
|
toast({
|
||||||
|
status: 'success',
|
||||||
|
title: t('toast.importSuccessful'),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
status: 'error',
|
||||||
|
title: t('toast.importFailed'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[importStylePresets, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<input type="file" accept=".csv" onChange={handleFileChange} hidden ref={fileInputRef} />
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
icon={!isLoading ? <PiUploadBold /> : <PiSpinner />}
|
||||||
|
tooltip={
|
||||||
|
<>
|
||||||
|
<Text fontWeight="semibold">{t('stylePresets.importTemplates')}</Text>
|
||||||
|
<Text>{t('stylePresets.importTemplatesDesc')}</Text>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
aria-label={t('stylePresets.importTemplates')}
|
||||||
|
onClick={handleClickUpload}
|
||||||
|
size="md"
|
||||||
|
variant="link"
|
||||||
|
w={8}
|
||||||
|
h={8}
|
||||||
|
sx={isLoading ? loadingStyles : undefined}
|
||||||
|
isDisabled={isLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -8,6 +8,7 @@ import { PiPlusBold } from 'react-icons/pi';
|
|||||||
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||||
import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
|
import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
|
||||||
|
|
||||||
|
import { StylePresetImport } from './StylePresetImport';
|
||||||
import { StylePresetList } from './StylePresetList';
|
import { StylePresetList } from './StylePresetList';
|
||||||
import StylePresetSearch from './StylePresetSearch';
|
import StylePresetSearch from './StylePresetSearch';
|
||||||
|
|
||||||
@ -60,16 +61,20 @@ export const StylePresetMenu = () => {
|
|||||||
<Flex flexDir="column" gap={2} padding={3} layerStyle="second" borderRadius="base">
|
<Flex flexDir="column" gap={2} padding={3} layerStyle="second" borderRadius="base">
|
||||||
<Flex alignItems="center" gap={2} w="full" justifyContent="space-between">
|
<Flex alignItems="center" gap={2} w="full" justifyContent="space-between">
|
||||||
<StylePresetSearch />
|
<StylePresetSearch />
|
||||||
<IconButton
|
<Flex alignItems="center" justifyContent="space-between">
|
||||||
icon={<PiPlusBold />}
|
<StylePresetImport />
|
||||||
tooltip={t('stylePresets.createPromptTemplate')}
|
|
||||||
aria-label={t('stylePresets.createPromptTemplate')}
|
<IconButton
|
||||||
onClick={handleClickAddNew}
|
icon={<PiPlusBold />}
|
||||||
size="md"
|
tooltip={t('stylePresets.createPromptTemplate')}
|
||||||
variant="link"
|
aria-label={t('stylePresets.createPromptTemplate')}
|
||||||
w={8}
|
onClick={handleClickAddNew}
|
||||||
h={8}
|
size="md"
|
||||||
/>
|
variant="link"
|
||||||
|
w={8}
|
||||||
|
h={8}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{data.presets.length === 0 && data.defaultPresets.length === 0 && (
|
{data.presets.length === 0 && data.defaultPresets.length === 0 && (
|
||||||
|
@ -92,6 +92,23 @@ export const stylePresetsApi = api.injectEndpoints({
|
|||||||
}),
|
}),
|
||||||
providesTags: ['FetchOnReconnect', { type: 'StylePreset', id: LIST_TAG }],
|
providesTags: ['FetchOnReconnect', { type: 'StylePreset', id: LIST_TAG }],
|
||||||
}),
|
}),
|
||||||
|
importStylePresets: build.mutation<
|
||||||
|
paths['/api/v1/style_presets/import']['post']['responses']['200']['content']['application/json'],
|
||||||
|
paths['/api/v1/style_presets/import']['post']['requestBody']['content']['multipart/form-data']['file']
|
||||||
|
>({
|
||||||
|
query: (file) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: buildStylePresetsUrl('import'),
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
invalidatesTags: [{ type: 'StylePreset', id: LIST_TAG }],
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -100,4 +117,5 @@ export const {
|
|||||||
useDeleteStylePresetMutation,
|
useDeleteStylePresetMutation,
|
||||||
useUpdateStylePresetMutation,
|
useUpdateStylePresetMutation,
|
||||||
useListStylePresetsQuery,
|
useListStylePresetsQuery,
|
||||||
|
useImportStylePresetsMutation,
|
||||||
} = stylePresetsApi;
|
} = stylePresetsApi;
|
||||||
|
@ -1344,6 +1344,23 @@ export type paths = {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/api/v1/style_presets/import": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
/** Import Style Presets */
|
||||||
|
post: operations["import_style_presets"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
export type webhooks = Record<string, never>;
|
export type webhooks = Record<string, never>;
|
||||||
export type components = {
|
export type components = {
|
||||||
@ -1998,6 +2015,15 @@ export type components = {
|
|||||||
*/
|
*/
|
||||||
prepend?: boolean;
|
prepend?: boolean;
|
||||||
};
|
};
|
||||||
|
/** Body_import_style_presets */
|
||||||
|
Body_import_style_presets: {
|
||||||
|
/**
|
||||||
|
* File
|
||||||
|
* Format: binary
|
||||||
|
* @description The file to import
|
||||||
|
*/
|
||||||
|
file: Blob;
|
||||||
|
};
|
||||||
/** Body_parse_dynamicprompts */
|
/** Body_parse_dynamicprompts */
|
||||||
Body_parse_dynamicprompts: {
|
Body_parse_dynamicprompts: {
|
||||||
/**
|
/**
|
||||||
@ -18083,4 +18109,37 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
import_style_presets: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"multipart/form-data": components["schemas"]["Body_import_style_presets"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user