mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge remote-tracking branch 'origin/main' into maryhipp/export-presets
This commit is contained in:
@ -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 either a CSV with columns: 'name', 'prompt' or 'positive_prompt', and 'negative_prompt' included, or a JSON file with keys 'name', 'prompt' or 'positive_prompt', and 'negative_prompt",
|
||||
"insertPlaceholder": "Insert placeholder",
|
||||
"myTemplates": "My Templates",
|
||||
"name": "Name",
|
||||
|
@ -19,6 +19,7 @@ import { languageSelector } from 'features/system/store/systemSelectors';
|
||||
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
||||
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||
import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import i18n from 'i18n';
|
||||
import { size } from 'lodash-es';
|
||||
@ -37,10 +38,11 @@ interface Props {
|
||||
imageName: string;
|
||||
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
||||
};
|
||||
selectedWorkflowId?: string;
|
||||
destination?: InvokeTabName | undefined;
|
||||
}
|
||||
|
||||
const App = ({ config = DEFAULT_CONFIG, selectedImage, destination }: Props) => {
|
||||
const App = ({ config = DEFAULT_CONFIG, selectedImage, selectedWorkflowId, destination }: Props) => {
|
||||
const language = useAppSelector(languageSelector);
|
||||
const logger = useLogger('system');
|
||||
const dispatch = useAppDispatch();
|
||||
@ -71,6 +73,14 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage, destination }: Props) =>
|
||||
}
|
||||
}, [dispatch, config, logger]);
|
||||
|
||||
const { getAndLoadWorkflow } = useGetAndLoadLibraryWorkflow();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedWorkflowId) {
|
||||
getAndLoadWorkflow(selectedWorkflowId);
|
||||
}
|
||||
}, [selectedWorkflowId, getAndLoadWorkflow]);
|
||||
|
||||
useEffect(() => {
|
||||
if (destination) {
|
||||
dispatch(setActiveTab(destination));
|
||||
|
@ -44,6 +44,7 @@ interface Props extends PropsWithChildren {
|
||||
imageName: string;
|
||||
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
||||
};
|
||||
selectedWorkflowId?: string;
|
||||
destination?: InvokeTabName;
|
||||
customStarUi?: CustomStarUi;
|
||||
socketOptions?: Partial<ManagerOptions & SocketOptions>;
|
||||
@ -64,6 +65,7 @@ const InvokeAIUI = ({
|
||||
projectUrl,
|
||||
queueId,
|
||||
selectedImage,
|
||||
selectedWorkflowId,
|
||||
destination,
|
||||
customStarUi,
|
||||
socketOptions,
|
||||
@ -221,7 +223,12 @@ const InvokeAIUI = ({
|
||||
<React.Suspense fallback={<Loading />}>
|
||||
<ThemeLocaleProvider>
|
||||
<AppDndContext>
|
||||
<App config={config} selectedImage={selectedImage} destination={destination} />
|
||||
<App
|
||||
config={config}
|
||||
selectedImage={selectedImage}
|
||||
selectedWorkflowId={selectedWorkflowId}
|
||||
destination={destination}
|
||||
/>
|
||||
</AppDndContext>
|
||||
</ThemeLocaleProvider>
|
||||
</React.Suspense>
|
||||
|
@ -0,0 +1,69 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { IconButton, spinAnimation, Text } from '@invoke-ai/ui-library';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback } from 'react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
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 { t } = useTranslation();
|
||||
|
||||
const onDropAccepted = useCallback(
|
||||
async (files: File[]) => {
|
||||
const file = 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]
|
||||
);
|
||||
|
||||
const { getInputProps, getRootProps } = useDropzone({
|
||||
accept: { 'text/csv': ['.csv'], 'application/json': ['.json'] },
|
||||
onDropAccepted,
|
||||
noDrag: true,
|
||||
multiple: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
icon={!isLoading ? <PiUploadBold /> : <PiSpinner />}
|
||||
tooltip={
|
||||
<>
|
||||
<Text fontWeight="semibold">{t('stylePresets.importTemplates')}</Text>
|
||||
<Text>{t('stylePresets.importTemplatesDesc')}</Text>
|
||||
</>
|
||||
}
|
||||
aria-label={t('stylePresets.importTemplates')}
|
||||
size="md"
|
||||
variant="link"
|
||||
w={8}
|
||||
h={8}
|
||||
sx={isLoading ? loadingStyles : undefined}
|
||||
isDisabled={isLoading}
|
||||
{...getRootProps()}
|
||||
/>
|
||||
<input {...getInputProps()} />
|
||||
</>
|
||||
);
|
||||
};
|
@ -8,6 +8,7 @@ import { PiDownloadBold, PiPlusBold } from 'react-icons/pi';
|
||||
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||
import { useLazyExportStylePresetsQuery, useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
|
||||
|
||||
import { StylePresetImport } from './StylePresetImport';
|
||||
import { StylePresetList } from './StylePresetList';
|
||||
import StylePresetSearch from './StylePresetSearch';
|
||||
import { toast } from '../../toast/toast';
|
||||
@ -100,27 +101,31 @@ 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={<PiDownloadBold />}
|
||||
tooltip={t('stylePresets.createPromptTemplate')}
|
||||
aria-label={t('stylePresets.createPromptTemplate')}
|
||||
onClick={handleClickDownloadCsv}
|
||||
size="md"
|
||||
variant="link"
|
||||
w={8}
|
||||
h={8}
|
||||
isDisabled={isLoading}
|
||||
/>
|
||||
<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={<PiDownloadBold />}
|
||||
tooltip={t('stylePresets.createPromptTemplate')}
|
||||
aria-label={t('stylePresets.createPromptTemplate')}
|
||||
onClick={handleClickDownloadCsv}
|
||||
size="md"
|
||||
variant="link"
|
||||
w={8}
|
||||
h={8}
|
||||
isDisabled={isLoading}
|
||||
/>
|
||||
|
||||
<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 && (
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Flex, IconButton } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { $isMenuOpen } from 'features/stylePresets/store/isMenuOpen';
|
||||
@ -7,6 +8,10 @@ import { PiCaretDownBold } from 'react-icons/pi';
|
||||
|
||||
import { ActiveStylePreset } from './ActiveStylePreset';
|
||||
|
||||
const _hover: SystemStyleObject = {
|
||||
bg: 'base.750',
|
||||
};
|
||||
|
||||
export const StylePresetMenuTrigger = () => {
|
||||
const isMenuOpen = useStore($isMenuOpen);
|
||||
const { t } = useTranslation();
|
||||
@ -26,6 +31,9 @@ export const StylePresetMenuTrigger = () => {
|
||||
borderRadius="base"
|
||||
gap={1}
|
||||
role="button"
|
||||
_hover={_hover}
|
||||
transitionProperty="background-color"
|
||||
transitionDuration="normal"
|
||||
>
|
||||
<ActiveStylePreset />
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
|
||||
|
||||
export const PRESET_PLACEHOLDER = `[prompt]`;
|
||||
export const PRESET_PLACEHOLDER = '{prompt}';
|
||||
|
||||
export const buildPresetModifiedPrompt = (presetPrompt: string, currentPrompt: string) => {
|
||||
return presetPrompt.includes(PRESET_PLACEHOLDER)
|
||||
|
@ -15,9 +15,9 @@ type UseGetAndLoadLibraryWorkflowReturn = {
|
||||
getAndLoadWorkflowResult: ReturnType<typeof useLazyGetWorkflowQuery>[1];
|
||||
};
|
||||
|
||||
type UseGetAndLoadLibraryWorkflow = (arg: UseGetAndLoadLibraryWorkflowOptions) => UseGetAndLoadLibraryWorkflowReturn;
|
||||
type UseGetAndLoadLibraryWorkflow = (arg?: UseGetAndLoadLibraryWorkflowOptions) => UseGetAndLoadLibraryWorkflowReturn;
|
||||
|
||||
export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = ({ onSuccess, onError }) => {
|
||||
export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = (arg) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const toast = useToast();
|
||||
const { t } = useTranslation();
|
||||
@ -29,17 +29,17 @@ export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = ({ onS
|
||||
// This action expects a stringified workflow, instead of updating the routes and services we will just stringify it here
|
||||
dispatch(workflowLoadRequested({ data: { workflow: JSON.stringify(workflow), graph: null }, asCopy: false }));
|
||||
// No toast - the listener for this action does that after the workflow is loaded
|
||||
onSuccess && onSuccess();
|
||||
arg?.onSuccess && arg.onSuccess();
|
||||
} catch {
|
||||
toast({
|
||||
id: `AUTH_ERROR_TOAST_${workflowsApi.endpoints.getWorkflow.name}`,
|
||||
title: t('toast.problemRetrievingWorkflow'),
|
||||
status: 'error',
|
||||
});
|
||||
onError && onError();
|
||||
arg?.onError && arg.onError();
|
||||
}
|
||||
},
|
||||
[_getAndLoadWorkflow, dispatch, onSuccess, t, onError, toast]
|
||||
[_getAndLoadWorkflow, dispatch, arg, t, toast]
|
||||
);
|
||||
|
||||
return { getAndLoadWorkflow, getAndLoadWorkflowResult };
|
||||
|
@ -102,6 +102,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 }],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -110,5 +127,6 @@ export const {
|
||||
useDeleteStylePresetMutation,
|
||||
useUpdateStylePresetMutation,
|
||||
useListStylePresetsQuery,
|
||||
useLazyExportStylePresetsQuery
|
||||
useLazyExportStylePresetsQuery,
|
||||
useImportStylePresetsMutation,
|
||||
} = stylePresetsApi;
|
||||
|
@ -1361,6 +1361,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 = {
|
||||
@ -2015,6 +2032,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: {
|
||||
/**
|
||||
@ -18121,4 +18147,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"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user