feat(ui): create component to upload CSV of style presets to import

This commit is contained in:
Mary Hipp 2024-08-14 11:09:53 -04:00 committed by Mary Hipp Rogers
parent 2d58754789
commit 76b0380b5f
5 changed files with 169 additions and 10 deletions

View File

@ -1141,6 +1141,8 @@
"imageSavingFailed": "Image Saving Failed",
"imageUploaded": "Image Uploaded",
"imageUploadFailed": "Image Upload Failed",
"importFailed": "Import Failed",
"importSuccessful": "Import Successful",
"invalidUpload": "Invalid Upload",
"loadedWithWarnings": "Workflow Loaded with Warnings",
"maskSavedAssets": "Mask Saved to Assets",
@ -1701,6 +1703,8 @@
"deleteTemplate2": "Are you sure you want to delete this template? This cannot be undone.",
"editTemplate": "Edit Template",
"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",
"myTemplates": "My Templates",
"name": "Name",

View File

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

View File

@ -8,6 +8,7 @@ import { PiPlusBold } from 'react-icons/pi';
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
import { StylePresetImport } from './StylePresetImport';
import { StylePresetList } from './StylePresetList';
import StylePresetSearch from './StylePresetSearch';
@ -60,16 +61,20 @@ export const StylePresetMenu = () => {
<Flex flexDir="column" gap={2} padding={3} layerStyle="second" borderRadius="base">
<Flex alignItems="center" gap={2} w="full" justifyContent="space-between">
<StylePresetSearch />
<IconButton
icon={<PiPlusBold />}
tooltip={t('stylePresets.createPromptTemplate')}
aria-label={t('stylePresets.createPromptTemplate')}
onClick={handleClickAddNew}
size="md"
variant="link"
w={8}
h={8}
/>
<Flex alignItems="center" justifyContent="space-between">
<StylePresetImport />
<IconButton
icon={<PiPlusBold />}
tooltip={t('stylePresets.createPromptTemplate')}
aria-label={t('stylePresets.createPromptTemplate')}
onClick={handleClickAddNew}
size="md"
variant="link"
w={8}
h={8}
/>
</Flex>
</Flex>
{data.presets.length === 0 && data.defaultPresets.length === 0 && (

View File

@ -92,6 +92,23 @@ export const stylePresetsApi = api.injectEndpoints({
}),
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,
useUpdateStylePresetMutation,
useListStylePresetsQuery,
useImportStylePresetsMutation,
} = stylePresetsApi;

View File

@ -1344,6 +1344,23 @@ export type paths = {
patch?: 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 components = {
@ -1998,6 +2015,15 @@ export type components = {
*/
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: {
/**
@ -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"];
};
};
};
};
}