feat(ui): port all toasts to use new util

This commit is contained in:
psychedelicious 2024-05-21 19:33:44 +10:00
parent 683ec8e5f2
commit a66b3497e0
74 changed files with 733 additions and 1110 deletions

View File

@ -1073,8 +1073,9 @@
}, },
"toast": { "toast": {
"addedToBoard": "Added to board", "addedToBoard": "Added to board",
"baseModelChangedCleared_one": "Base model changed, cleared or disabled {{count}} incompatible submodel", "baseModelChanged": "Base Model Changed",
"baseModelChangedCleared_other": "Base model changed, cleared or disabled {{count}} incompatible submodels", "baseModelChangedCleared_one": "Cleared or disabled {{count}} incompatible submodel",
"baseModelChangedCleared_other": "Cleared or disabled {{count}} incompatible submodels",
"canceled": "Processing Canceled", "canceled": "Processing Canceled",
"canvasCopiedClipboard": "Canvas Copied to Clipboard", "canvasCopiedClipboard": "Canvas Copied to Clipboard",
"canvasDownloaded": "Canvas Downloaded", "canvasDownloaded": "Canvas Downloaded",
@ -1095,10 +1096,17 @@
"metadataLoadFailed": "Failed to load metadata", "metadataLoadFailed": "Failed to load metadata",
"modelAddedSimple": "Model Added to Queue", "modelAddedSimple": "Model Added to Queue",
"modelImportCanceled": "Model Import Canceled", "modelImportCanceled": "Model Import Canceled",
"outOfMemoryError": "Out of Memory Error",
"outOfMemoryErrorDesc": "Your current generation settings exceed system capacity. Please adjust your settings and try again.",
"parameters": "Parameters", "parameters": "Parameters",
"parameterNotSet": "{{parameter}} not set", "parameterSet": "Parameter Recalled",
"parameterSet": "{{parameter}} set", "parameterSetDesc": "Recalled {{parameter}}",
"parametersNotSet": "Parameters Not Set", "parameterNotSet": "Parameter Recalled",
"parameterNotSetDesc": "Unable to recall {{parameter}}",
"parameterNotSetDescWithMessage": "Unable to recall {{parameter}}: {{message}}",
"parametersSet": "Parameters Recalled",
"parametersNotSet": "Parameters Not Recalled",
"errorCopied": "Error Copied",
"problemCopyingCanvas": "Problem Copying Canvas", "problemCopyingCanvas": "Problem Copying Canvas",
"problemCopyingCanvasDesc": "Unable to export base layer", "problemCopyingCanvasDesc": "Unable to export base layer",
"problemCopyingImage": "Unable to Copy Image", "problemCopyingImage": "Unable to Copy Image",
@ -1118,11 +1126,13 @@
"sentToImageToImage": "Sent To Image To Image", "sentToImageToImage": "Sent To Image To Image",
"sentToUnifiedCanvas": "Sent to Unified Canvas", "sentToUnifiedCanvas": "Sent to Unified Canvas",
"serverError": "Server Error", "serverError": "Server Error",
"sessionRef": "Session: {{sessionId}}",
"setAsCanvasInitialImage": "Set as canvas initial image", "setAsCanvasInitialImage": "Set as canvas initial image",
"setCanvasInitialImage": "Set canvas initial image", "setCanvasInitialImage": "Set canvas initial image",
"setControlImage": "Set as control image", "setControlImage": "Set as control image",
"setInitialImage": "Set as initial image", "setInitialImage": "Set as initial image",
"setNodeField": "Set as node field", "setNodeField": "Set as node field",
"somethingWentWrong": "Something Went Wrong",
"uploadFailed": "Upload failed", "uploadFailed": "Upload failed",
"uploadFailedInvalidUploadDesc": "Must be single PNG or JPEG image", "uploadFailedInvalidUploadDesc": "Must be single PNG or JPEG image",
"uploadInitialImage": "Upload Initial Image", "uploadInitialImage": "Upload Initial Image",

View File

@ -25,7 +25,6 @@ import { useGetOpenAPISchemaQuery } from 'services/api/endpoints/appInfo';
import AppErrorBoundaryFallback from './AppErrorBoundaryFallback'; import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
import PreselectedImage from './PreselectedImage'; import PreselectedImage from './PreselectedImage';
import Toaster from './Toaster';
const DEFAULT_CONFIG = {}; const DEFAULT_CONFIG = {};
@ -96,7 +95,6 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
<DeleteImageModal /> <DeleteImageModal />
<ChangeBoardModal /> <ChangeBoardModal />
<DynamicPromptsModal /> <DynamicPromptsModal />
<Toaster />
<PreselectedImage selectedImage={selectedImage} /> <PreselectedImage selectedImage={selectedImage} />
</ErrorBoundary> </ErrorBoundary>
); );

View File

@ -1,4 +1,5 @@
import { Button, Flex, Heading, Link, Text, useToast } from '@invoke-ai/ui-library'; import { Button, Flex, Heading, Link, Text } from '@invoke-ai/ui-library';
import { toast } from 'features/toast/toast';
import newGithubIssueUrl from 'new-github-issue-url'; import newGithubIssueUrl from 'new-github-issue-url';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -11,16 +12,16 @@ type Props = {
}; };
const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => { const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
const toast = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const handleCopy = useCallback(() => { const handleCopy = useCallback(() => {
const text = JSON.stringify(serializeError(error), null, 2); const text = JSON.stringify(serializeError(error), null, 2);
navigator.clipboard.writeText(`\`\`\`\n${text}\n\`\`\``); navigator.clipboard.writeText(`\`\`\`\n${text}\n\`\`\``);
toast({ toast({
title: 'Error Copied', id: 'ERROR_COPIED',
title: t('toast.errorCopied'),
}); });
}, [error, toast]); }, [error, t]);
const url = useMemo( const url = useMemo(
() => () =>

View File

@ -1,44 +0,0 @@
import { useToast } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { addToast, clearToastQueue } from 'features/system/store/systemSlice';
import type { MakeToastArg } from 'features/system/util/makeToast';
import { makeToast } from 'features/system/util/makeToast';
import { memo, useCallback, useEffect } from 'react';
/**
* Logical component. Watches the toast queue and makes toasts when the queue is not empty.
* @returns null
*/
const Toaster = () => {
const dispatch = useAppDispatch();
const toastQueue = useAppSelector((s) => s.system.toastQueue);
const toast = useToast();
useEffect(() => {
toastQueue.forEach((t) => {
toast(t);
});
toastQueue.length > 0 && dispatch(clearToastQueue());
}, [dispatch, toast, toastQueue]);
return null;
};
/**
* Returns a function that can be used to make a toast.
* @example
* const toaster = useAppToaster();
* toaster('Hello world!');
* toaster({ title: 'Hello world!', status: 'success' });
* @returns A function that can be used to make a toast.
* @see makeToast
* @see MakeToastArg
* @see UseToastOptions
*/
export const useAppToaster = () => {
const dispatch = useAppDispatch();
const toaster = useCallback((arg: MakeToastArg) => dispatch(addToast(makeToast(arg))), [dispatch]);
return toaster;
};
export default memo(Toaster);

View File

@ -41,12 +41,10 @@ import { addGeneratorProgressEventListener } from 'app/store/middleware/listener
import { addGraphExecutionStateCompleteEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketGraphExecutionStateComplete'; import { addGraphExecutionStateCompleteEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketGraphExecutionStateComplete';
import { addInvocationCompleteEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete'; import { addInvocationCompleteEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete';
import { addInvocationErrorEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationError'; import { addInvocationErrorEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationError';
import { addInvocationRetrievalErrorEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationRetrievalError';
import { addInvocationStartedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationStarted'; import { addInvocationStartedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationStarted';
import { addModelInstallEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelInstall'; import { addModelInstallEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelInstall';
import { addModelLoadEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelLoad'; import { addModelLoadEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelLoad';
import { addSocketQueueItemStatusChangedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged'; import { addSocketQueueItemStatusChangedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged';
import { addSessionRetrievalErrorEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketSessionRetrievalError';
import { addSocketSubscribedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketSubscribed'; import { addSocketSubscribedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketSubscribed';
import { addSocketUnsubscribedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketUnsubscribed'; import { addSocketUnsubscribedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketUnsubscribed';
import { addStagingAreaImageSavedListener } from 'app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved'; import { addStagingAreaImageSavedListener } from 'app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved';
@ -114,8 +112,6 @@ addSocketSubscribedEventListener(startAppListening);
addSocketUnsubscribedEventListener(startAppListening); addSocketUnsubscribedEventListener(startAppListening);
addModelLoadEventListener(startAppListening); addModelLoadEventListener(startAppListening);
addModelInstallEventListener(startAppListening); addModelInstallEventListener(startAppListening);
addSessionRetrievalErrorEventListener(startAppListening);
addInvocationRetrievalErrorEventListener(startAppListening);
addSocketQueueItemStatusChangedEventListener(startAppListening); addSocketQueueItemStatusChangedEventListener(startAppListening);
addBulkDownloadListeners(startAppListening); addBulkDownloadListeners(startAppListening);

View File

@ -8,7 +8,7 @@ import {
resetCanvas, resetCanvas,
setInitialCanvasImage, setInitialCanvasImage,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
@ -30,22 +30,20 @@ export const addCommitStagingAreaImageListener = (startAppListening: AppStartLis
req.reset(); req.reset();
if (canceled > 0) { if (canceled > 0) {
log.debug(`Canceled ${canceled} canvas batches`); log.debug(`Canceled ${canceled} canvas batches`);
dispatch( toast({
addToast({ id: 'CANCEL_BATCH_SUCCEEDED',
title: t('queue.cancelBatchSucceeded'), title: t('queue.cancelBatchSucceeded'),
status: 'success', status: 'success',
}) });
);
} }
dispatch(canvasBatchIdsReset()); dispatch(canvasBatchIdsReset());
} catch { } catch {
log.error('Failed to cancel canvas batches'); log.error('Failed to cancel canvas batches');
dispatch( toast({
addToast({ id: 'CANCEL_BATCH_FAILED',
title: t('queue.cancelBatchFailed'), title: t('queue.cancelBatchFailed'),
status: 'error', status: 'error',
}) });
);
} }
}, },
}); });

View File

@ -1,8 +1,8 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { parseify } from 'common/util/serialize'; import { parseify } from 'common/util/serialize';
import { toast } from 'common/util/toast';
import { zPydanticValidationError } from 'features/system/store/zodSchemas'; import { zPydanticValidationError } from 'features/system/store/zodSchemas';
import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { truncate, upperFirst } from 'lodash-es'; import { truncate, upperFirst } from 'lodash-es';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
@ -16,18 +16,15 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
const arg = action.meta.arg.originalArgs; const arg = action.meta.arg.originalArgs;
logger('queue').debug({ enqueueResult: parseify(response) }, 'Batch enqueued'); logger('queue').debug({ enqueueResult: parseify(response) }, 'Batch enqueued');
if (!toast.isActive('batch-queued')) { toast({
toast({ id: 'QUEUE_BATCH_SUCCEEDED',
id: 'batch-queued', title: t('queue.batchQueued'),
title: t('queue.batchQueued'), status: 'success',
description: t('queue.batchQueuedDesc', { description: t('queue.batchQueuedDesc', {
count: response.enqueued, count: response.enqueued,
direction: arg.prepend ? t('queue.front') : t('queue.back'), direction: arg.prepend ? t('queue.front') : t('queue.back'),
}), }),
duration: 1000, });
status: 'success',
});
}
}, },
}); });
@ -40,9 +37,10 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
if (!response) { if (!response) {
toast({ toast({
id: 'QUEUE_BATCH_FAILED',
title: t('queue.batchFailedToQueue'), title: t('queue.batchFailedToQueue'),
status: 'error', status: 'error',
description: 'Unknown Error', description: t('common.unknownError'),
}); });
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue')); logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
return; return;
@ -52,7 +50,7 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
if (result.success) { if (result.success) {
result.data.data.detail.map((e) => { result.data.data.detail.map((e) => {
toast({ toast({
id: 'batch-failed-to-queue', id: 'QUEUE_BATCH_FAILED',
title: truncate(upperFirst(e.msg), { length: 128 }), title: truncate(upperFirst(e.msg), { length: 128 }),
status: 'error', status: 'error',
description: truncate( description: truncate(
@ -64,9 +62,10 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
}); });
} else if (response.status !== 403) { } else if (response.status !== 403) {
toast({ toast({
id: 'QUEUE_BATCH_FAILED',
title: t('queue.batchFailedToQueue'), title: t('queue.batchFailedToQueue'),
description: t('common.unknownError'),
status: 'error', status: 'error',
description: t('common.unknownError'),
}); });
} }
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue')); logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));

View File

@ -1,8 +1,7 @@
import type { UseToastOptions } from '@invoke-ai/ui-library';
import { ExternalLink } from '@invoke-ai/ui-library'; import { ExternalLink } from '@invoke-ai/ui-library';
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { toast } from 'common/util/toast'; import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { import {
@ -28,7 +27,6 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
// Show the response message if it exists, otherwise show the default message // Show the response message if it exists, otherwise show the default message
description: action.payload.response || t('gallery.bulkDownloadRequestedDesc'), description: action.payload.response || t('gallery.bulkDownloadRequestedDesc'),
duration: null, duration: null,
isClosable: true,
}); });
}, },
}); });
@ -40,9 +38,9 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
// There isn't any toast to update if we get this event. // There isn't any toast to update if we get this event.
toast({ toast({
id: 'BULK_DOWNLOAD_REQUEST_FAILED',
title: t('gallery.bulkDownloadRequestFailed'), title: t('gallery.bulkDownloadRequestFailed'),
status: 'success', status: 'error',
isClosable: true,
}); });
}, },
}); });
@ -65,7 +63,7 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
// TODO(psyche): This URL may break in in some environments (e.g. Nvidia workbench) but we need to test it first // TODO(psyche): This URL may break in in some environments (e.g. Nvidia workbench) but we need to test it first
const url = `/api/v1/images/download/${bulk_download_item_name}`; const url = `/api/v1/images/download/${bulk_download_item_name}`;
const toastOptions: UseToastOptions = { toast({
id: bulk_download_item_name, id: bulk_download_item_name,
title: t('gallery.bulkDownloadReady', 'Download ready'), title: t('gallery.bulkDownloadReady', 'Download ready'),
status: 'success', status: 'success',
@ -77,14 +75,7 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
/> />
), ),
duration: null, duration: null,
isClosable: true, });
};
if (toast.isActive(bulk_download_item_name)) {
toast.update(bulk_download_item_name, toastOptions);
} else {
toast(toastOptions);
}
}, },
}); });
@ -95,20 +86,13 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
const { bulk_download_item_name } = action.payload.data; const { bulk_download_item_name } = action.payload.data;
const toastOptions: UseToastOptions = { toast({
id: bulk_download_item_name, id: bulk_download_item_name,
title: t('gallery.bulkDownloadFailed'), title: t('gallery.bulkDownloadFailed'),
status: 'error', status: 'error',
description: action.payload.data.error, description: action.payload.data.error,
duration: null, duration: null,
isClosable: true, });
};
if (toast.isActive(bulk_download_item_name)) {
toast.update(bulk_download_item_name, toastOptions);
} else {
toast(toastOptions);
}
}, },
}); });
}; };

View File

@ -2,14 +2,14 @@ import { $logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { canvasCopiedToClipboard } from 'features/canvas/store/actions'; import { canvasCopiedToClipboard } from 'features/canvas/store/actions';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice';
import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard'; import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard';
import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
export const addCanvasCopiedToClipboardListener = (startAppListening: AppStartListening) => { export const addCanvasCopiedToClipboardListener = (startAppListening: AppStartListening) => {
startAppListening({ startAppListening({
actionCreator: canvasCopiedToClipboard, actionCreator: canvasCopiedToClipboard,
effect: async (action, { dispatch, getState }) => { effect: async (action, { getState }) => {
const moduleLog = $logger.get().child({ namespace: 'canvasCopiedToClipboardListener' }); const moduleLog = $logger.get().child({ namespace: 'canvasCopiedToClipboardListener' });
const state = getState(); const state = getState();
@ -19,22 +19,20 @@ export const addCanvasCopiedToClipboardListener = (startAppListening: AppStartLi
copyBlobToClipboard(blob); copyBlobToClipboard(blob);
} catch (err) { } catch (err) {
moduleLog.error(String(err)); moduleLog.error(String(err));
dispatch( toast({
addToast({ id: 'CANVAS_COPY_FAILED',
title: t('toast.problemCopyingCanvas'), title: t('toast.problemCopyingCanvas'),
description: t('toast.problemCopyingCanvasDesc'), description: t('toast.problemCopyingCanvasDesc'),
status: 'error', status: 'error',
}) });
);
return; return;
} }
dispatch( toast({
addToast({ id: 'CANVAS_COPY_SUCCEEDED',
title: t('toast.canvasCopiedClipboard'), title: t('toast.canvasCopiedClipboard'),
status: 'success', status: 'success',
}) });
);
}, },
}); });
}; };

View File

@ -3,13 +3,13 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
import { canvasDownloadedAsImage } from 'features/canvas/store/actions'; import { canvasDownloadedAsImage } from 'features/canvas/store/actions';
import { downloadBlob } from 'features/canvas/util/downloadBlob'; import { downloadBlob } from 'features/canvas/util/downloadBlob';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
export const addCanvasDownloadedAsImageListener = (startAppListening: AppStartListening) => { export const addCanvasDownloadedAsImageListener = (startAppListening: AppStartListening) => {
startAppListening({ startAppListening({
actionCreator: canvasDownloadedAsImage, actionCreator: canvasDownloadedAsImage,
effect: async (action, { dispatch, getState }) => { effect: async (action, { getState }) => {
const moduleLog = $logger.get().child({ namespace: 'canvasSavedToGalleryListener' }); const moduleLog = $logger.get().child({ namespace: 'canvasSavedToGalleryListener' });
const state = getState(); const state = getState();
@ -18,18 +18,17 @@ export const addCanvasDownloadedAsImageListener = (startAppListening: AppStartLi
blob = await getBaseLayerBlob(state); blob = await getBaseLayerBlob(state);
} catch (err) { } catch (err) {
moduleLog.error(String(err)); moduleLog.error(String(err));
dispatch( toast({
addToast({ id: 'CANVAS_DOWNLOAD_FAILED',
title: t('toast.problemDownloadingCanvas'), title: t('toast.problemDownloadingCanvas'),
description: t('toast.problemDownloadingCanvasDesc'), description: t('toast.problemDownloadingCanvasDesc'),
status: 'error', status: 'error',
}) });
);
return; return;
} }
downloadBlob(blob, 'canvas.png'); downloadBlob(blob, 'canvas.png');
dispatch(addToast({ title: t('toast.canvasDownloaded'), status: 'success' })); toast({ id: 'CANVAS_DOWNLOAD_SUCCEEDED', title: t('toast.canvasDownloaded'), status: 'success' });
}, },
}); });
}; };

View File

@ -3,7 +3,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
import { canvasImageToControlAdapter } from 'features/canvas/store/actions'; import { canvasImageToControlAdapter } from 'features/canvas/store/actions';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice'; import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
@ -20,13 +20,12 @@ export const addCanvasImageToControlNetListener = (startAppListening: AppStartLi
blob = await getBaseLayerBlob(state, true); blob = await getBaseLayerBlob(state, true);
} catch (err) { } catch (err) {
log.error(String(err)); log.error(String(err));
dispatch( toast({
addToast({ id: 'PROBLEM_SAVING_CANVAS',
title: t('toast.problemSavingCanvas'), title: t('toast.problemSavingCanvas'),
description: t('toast.problemSavingCanvasDesc'), description: t('toast.problemSavingCanvasDesc'),
status: 'error', status: 'error',
}) });
);
return; return;
} }
@ -43,7 +42,7 @@ export const addCanvasImageToControlNetListener = (startAppListening: AppStartLi
crop_visible: false, crop_visible: false,
postUploadAction: { postUploadAction: {
type: 'TOAST', type: 'TOAST',
toastOptions: { title: t('toast.canvasSentControlnetAssets') }, title: t('toast.canvasSentControlnetAssets'),
}, },
}) })
).unwrap(); ).unwrap();

View File

@ -2,7 +2,7 @@ import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { canvasMaskSavedToGallery } from 'features/canvas/store/actions'; import { canvasMaskSavedToGallery } from 'features/canvas/store/actions';
import { getCanvasData } from 'features/canvas/util/getCanvasData'; import { getCanvasData } from 'features/canvas/util/getCanvasData';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
@ -29,13 +29,12 @@ export const addCanvasMaskSavedToGalleryListener = (startAppListening: AppStartL
if (!maskBlob) { if (!maskBlob) {
log.error('Problem getting mask layer blob'); log.error('Problem getting mask layer blob');
dispatch( toast({
addToast({ id: 'PROBLEM_SAVING_MASK',
title: t('toast.problemSavingMask'), title: t('toast.problemSavingMask'),
description: t('toast.problemSavingMaskDesc'), description: t('toast.problemSavingMaskDesc'),
status: 'error', status: 'error',
}) });
);
return; return;
} }
@ -52,7 +51,7 @@ export const addCanvasMaskSavedToGalleryListener = (startAppListening: AppStartL
crop_visible: true, crop_visible: true,
postUploadAction: { postUploadAction: {
type: 'TOAST', type: 'TOAST',
toastOptions: { title: t('toast.maskSavedAssets') }, title: t('toast.maskSavedAssets'),
}, },
}) })
); );

View File

@ -3,7 +3,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
import { canvasMaskToControlAdapter } from 'features/canvas/store/actions'; import { canvasMaskToControlAdapter } from 'features/canvas/store/actions';
import { getCanvasData } from 'features/canvas/util/getCanvasData'; import { getCanvasData } from 'features/canvas/util/getCanvasData';
import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice'; import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
@ -30,13 +30,12 @@ export const addCanvasMaskToControlNetListener = (startAppListening: AppStartLis
if (!maskBlob) { if (!maskBlob) {
log.error('Problem getting mask layer blob'); log.error('Problem getting mask layer blob');
dispatch( toast({
addToast({ id: 'PROBLEM_IMPORTING_MASK',
title: t('toast.problemImportingMask'), title: t('toast.problemImportingMask'),
description: t('toast.problemImportingMaskDesc'), description: t('toast.problemImportingMaskDesc'),
status: 'error', status: 'error',
}) });
);
return; return;
} }
@ -53,7 +52,7 @@ export const addCanvasMaskToControlNetListener = (startAppListening: AppStartLis
crop_visible: false, crop_visible: false,
postUploadAction: { postUploadAction: {
type: 'TOAST', type: 'TOAST',
toastOptions: { title: t('toast.maskSentControlnetAssets') }, title: t('toast.maskSentControlnetAssets'),
}, },
}) })
).unwrap(); ).unwrap();

View File

@ -4,7 +4,7 @@ import { canvasMerged } from 'features/canvas/store/actions';
import { $canvasBaseLayer } from 'features/canvas/store/canvasNanostore'; import { $canvasBaseLayer } from 'features/canvas/store/canvasNanostore';
import { setMergedCanvas } from 'features/canvas/store/canvasSlice'; import { setMergedCanvas } from 'features/canvas/store/canvasSlice';
import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob'; import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
@ -17,13 +17,12 @@ export const addCanvasMergedListener = (startAppListening: AppStartListening) =>
if (!blob) { if (!blob) {
moduleLog.error('Problem getting base layer blob'); moduleLog.error('Problem getting base layer blob');
dispatch( toast({
addToast({ id: 'PROBLEM_MERGING_CANVAS',
title: t('toast.problemMergingCanvas'), title: t('toast.problemMergingCanvas'),
description: t('toast.problemMergingCanvasDesc'), description: t('toast.problemMergingCanvasDesc'),
status: 'error', status: 'error',
}) });
);
return; return;
} }
@ -31,13 +30,12 @@ export const addCanvasMergedListener = (startAppListening: AppStartListening) =>
if (!canvasBaseLayer) { if (!canvasBaseLayer) {
moduleLog.error('Problem getting canvas base layer'); moduleLog.error('Problem getting canvas base layer');
dispatch( toast({
addToast({ id: 'PROBLEM_MERGING_CANVAS',
title: t('toast.problemMergingCanvas'), title: t('toast.problemMergingCanvas'),
description: t('toast.problemMergingCanvasDesc'), description: t('toast.problemMergingCanvasDesc'),
status: 'error', status: 'error',
}) });
);
return; return;
} }
@ -54,7 +52,7 @@ export const addCanvasMergedListener = (startAppListening: AppStartListening) =>
is_intermediate: true, is_intermediate: true,
postUploadAction: { postUploadAction: {
type: 'TOAST', type: 'TOAST',
toastOptions: { title: t('toast.canvasMerged') }, title: t('toast.canvasMerged'),
}, },
}) })
).unwrap(); ).unwrap();

View File

@ -3,7 +3,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
import { parseify } from 'common/util/serialize'; import { parseify } from 'common/util/serialize';
import { canvasSavedToGallery } from 'features/canvas/store/actions'; import { canvasSavedToGallery } from 'features/canvas/store/actions';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
@ -19,13 +19,12 @@ export const addCanvasSavedToGalleryListener = (startAppListening: AppStartListe
blob = await getBaseLayerBlob(state); blob = await getBaseLayerBlob(state);
} catch (err) { } catch (err) {
log.error(String(err)); log.error(String(err));
dispatch( toast({
addToast({ id: 'CANVAS_SAVE_FAILED',
title: t('toast.problemSavingCanvas'), title: t('toast.problemSavingCanvas'),
description: t('toast.problemSavingCanvasDesc'), description: t('toast.problemSavingCanvasDesc'),
status: 'error', status: 'error',
}) });
);
return; return;
} }
@ -42,7 +41,7 @@ export const addCanvasSavedToGalleryListener = (startAppListening: AppStartListe
crop_visible: true, crop_visible: true,
postUploadAction: { postUploadAction: {
type: 'TOAST', type: 'TOAST',
toastOptions: { title: t('toast.canvasSavedGallery') }, title: t('toast.canvasSavedGallery'),
}, },
metadata: { metadata: {
_canvas_objects: parseify(state.canvas.layerState.objects), _canvas_objects: parseify(state.canvas.layerState.objects),

View File

@ -14,7 +14,7 @@ import {
} from 'features/controlLayers/store/controlLayersSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters'; import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
import { isImageOutput } from 'features/nodes/types/common'; import { isImageOutput } from 'features/nodes/types/common';
import { addToast } from 'features/system/store/systemSlice'; import { toast, ToastID } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { getImageDTO } from 'services/api/endpoints/images'; import { getImageDTO } from 'services/api/endpoints/images';
@ -174,12 +174,11 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
} }
} }
dispatch( toast({
addToast({ id: ToastID.GRAPH_QUEUE_FAILED,
title: t('queue.graphFailedToQueue'), title: t('queue.graphFailedToQueue'),
status: 'error', status: 'error',
}) });
);
} }
} finally { } finally {
req.reset(); req.reset();

View File

@ -10,7 +10,7 @@ import {
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { isImageOutput } from 'features/nodes/types/common'; import { isImageOutput } from 'features/nodes/types/common';
import { addToast } from 'features/system/store/systemSlice'; import { toast, ToastID } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
@ -108,12 +108,11 @@ export const addControlNetImageProcessedListener = (startAppListening: AppStartL
} }
} }
dispatch( toast({
addToast({ id: ToastID.GRAPH_QUEUE_FAILED,
title: t('queue.graphFailedToQueue'), title: t('queue.graphFailedToQueue'),
status: 'error', status: 'error',
}) });
);
} }
}, },
}); });

View File

@ -1,4 +1,3 @@
import type { UseToastOptions } from '@invoke-ai/ui-library';
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
@ -14,7 +13,7 @@ import {
} from 'features/controlLayers/store/controlLayersSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
import { boardsApi } from 'services/api/endpoints/boards'; import { boardsApi } from 'services/api/endpoints/boards';
@ -42,16 +41,17 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
return; return;
} }
const DEFAULT_UPLOADED_TOAST: UseToastOptions = { const DEFAULT_UPLOADED_TOAST = {
id: 'IMAGE_UPLOADED',
title: t('toast.imageUploaded'), title: t('toast.imageUploaded'),
status: 'success', status: 'success',
}; } as const;
// default action - just upload and alert user // default action - just upload and alert user
if (postUploadAction?.type === 'TOAST') { if (postUploadAction?.type === 'TOAST') {
const { toastOptions } = postUploadAction;
if (!autoAddBoardId || autoAddBoardId === 'none') { if (!autoAddBoardId || autoAddBoardId === 'none') {
dispatch(addToast({ ...DEFAULT_UPLOADED_TOAST, ...toastOptions })); const title = postUploadAction.title || DEFAULT_UPLOADED_TOAST.title;
toast({ ...DEFAULT_UPLOADED_TOAST, title });
} else { } else {
// Add this image to the board // Add this image to the board
dispatch( dispatch(
@ -70,24 +70,20 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
? `${t('toast.addedToBoard')} ${board.board_name}` ? `${t('toast.addedToBoard')} ${board.board_name}`
: `${t('toast.addedToBoard')} ${autoAddBoardId}`; : `${t('toast.addedToBoard')} ${autoAddBoardId}`;
dispatch( toast({
addToast({ ...DEFAULT_UPLOADED_TOAST,
...DEFAULT_UPLOADED_TOAST, description,
description, });
})
);
} }
return; return;
} }
if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') { if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') {
dispatch(setInitialCanvasImage(imageDTO, selectOptimalDimension(state))); dispatch(setInitialCanvasImage(imageDTO, selectOptimalDimension(state)));
dispatch( toast({
addToast({ ...DEFAULT_UPLOADED_TOAST,
...DEFAULT_UPLOADED_TOAST, description: t('toast.setAsCanvasInitialImage'),
description: t('toast.setAsCanvasInitialImage'), });
})
);
return; return;
} }
@ -105,68 +101,56 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
controlImage: imageDTO.image_name, controlImage: imageDTO.image_name,
}) })
); );
dispatch( toast({
addToast({ ...DEFAULT_UPLOADED_TOAST,
...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage'),
description: t('toast.setControlImage'), });
})
);
return; return;
} }
if (postUploadAction?.type === 'SET_CA_LAYER_IMAGE') { if (postUploadAction?.type === 'SET_CA_LAYER_IMAGE') {
const { layerId } = postUploadAction; const { layerId } = postUploadAction;
dispatch(caLayerImageChanged({ layerId, imageDTO })); dispatch(caLayerImageChanged({ layerId, imageDTO }));
dispatch( toast({
addToast({ ...DEFAULT_UPLOADED_TOAST,
...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage'),
description: t('toast.setControlImage'), });
})
);
} }
if (postUploadAction?.type === 'SET_IPA_LAYER_IMAGE') { if (postUploadAction?.type === 'SET_IPA_LAYER_IMAGE') {
const { layerId } = postUploadAction; const { layerId } = postUploadAction;
dispatch(ipaLayerImageChanged({ layerId, imageDTO })); dispatch(ipaLayerImageChanged({ layerId, imageDTO }));
dispatch( toast({
addToast({ ...DEFAULT_UPLOADED_TOAST,
...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage'),
description: t('toast.setControlImage'), });
})
);
} }
if (postUploadAction?.type === 'SET_RG_LAYER_IP_ADAPTER_IMAGE') { if (postUploadAction?.type === 'SET_RG_LAYER_IP_ADAPTER_IMAGE') {
const { layerId, ipAdapterId } = postUploadAction; const { layerId, ipAdapterId } = postUploadAction;
dispatch(rgLayerIPAdapterImageChanged({ layerId, ipAdapterId, imageDTO })); dispatch(rgLayerIPAdapterImageChanged({ layerId, ipAdapterId, imageDTO }));
dispatch( toast({
addToast({ ...DEFAULT_UPLOADED_TOAST,
...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage'),
description: t('toast.setControlImage'), });
})
);
} }
if (postUploadAction?.type === 'SET_II_LAYER_IMAGE') { if (postUploadAction?.type === 'SET_II_LAYER_IMAGE') {
const { layerId } = postUploadAction; const { layerId } = postUploadAction;
dispatch(iiLayerImageChanged({ layerId, imageDTO })); dispatch(iiLayerImageChanged({ layerId, imageDTO }));
dispatch( toast({
addToast({ ...DEFAULT_UPLOADED_TOAST,
...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage'),
description: t('toast.setControlImage'), });
})
);
} }
if (postUploadAction?.type === 'SET_NODES_IMAGE') { if (postUploadAction?.type === 'SET_NODES_IMAGE') {
const { nodeId, fieldName } = postUploadAction; const { nodeId, fieldName } = postUploadAction;
dispatch(fieldImageValueChanged({ nodeId, fieldName, value: imageDTO })); dispatch(fieldImageValueChanged({ nodeId, fieldName, value: imageDTO }));
dispatch( toast({
addToast({ ...DEFAULT_UPLOADED_TOAST,
...DEFAULT_UPLOADED_TOAST, description: `${t('toast.setNodeField')} ${fieldName}`,
description: `${t('toast.setNodeField')} ${fieldName}`, });
})
);
return; return;
} }
}, },
@ -174,7 +158,7 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
startAppListening({ startAppListening({
matcher: imagesApi.endpoints.uploadImage.matchRejected, matcher: imagesApi.endpoints.uploadImage.matchRejected,
effect: (action, { dispatch }) => { effect: (action) => {
const log = logger('images'); const log = logger('images');
const sanitizedData = { const sanitizedData = {
arg: { arg: {
@ -183,13 +167,11 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
}, },
}; };
log.error({ ...sanitizedData }, 'Image upload failed'); log.error({ ...sanitizedData }, 'Image upload failed');
dispatch( toast({
addToast({ title: t('toast.imageUploadFailed'),
title: t('toast.imageUploadFailed'), description: action.error.message,
description: action.error.message, status: 'error',
status: 'error', });
})
);
}, },
}); });
}; };

View File

@ -8,8 +8,7 @@ import { loraRemoved } from 'features/lora/store/loraSlice';
import { modelSelected } from 'features/parameters/store/actions'; import { modelSelected } from 'features/parameters/store/actions';
import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice'; import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice';
import { zParameterModel } from 'features/parameters/types/parameterSchemas'; import { zParameterModel } from 'features/parameters/types/parameterSchemas';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next'; import { t } from 'i18next';
import { forEach } from 'lodash-es'; import { forEach } from 'lodash-es';
@ -60,16 +59,14 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
}); });
if (modelsCleared > 0) { if (modelsCleared > 0) {
dispatch( toast({
addToast( id: 'BASE_MODEL_CHANGED',
makeToast({ title: t('toast.baseModelChanged'),
title: t('toast.baseModelChangedCleared', { description: t('toast.baseModelChangedCleared', {
count: modelsCleared, count: modelsCleared,
}), }),
status: 'warning', status: 'warning',
}) });
)
);
} }
} }

View File

@ -19,8 +19,7 @@ import {
isParameterWidth, isParameterWidth,
zParameterVAEModel, zParameterVAEModel,
} from 'features/parameters/types/parameterSchemas'; } from 'features/parameters/types/parameterSchemas';
import { addToast } from 'features/system/store/systemSlice'; import { toast, ToastID } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next'; import { t } from 'i18next';
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models'; import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
import { isNonRefinerMainModelConfig } from 'services/api/types'; import { isNonRefinerMainModelConfig } from 'services/api/types';
@ -109,7 +108,7 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
} }
} }
dispatch(addToast(makeToast({ title: t('toast.parameterSet', { parameter: 'Default settings' }) }))); toast({ id: ToastID.PARAMETER_SET, title: t('toast.parameterSet', { parameter: 'Default settings' }) });
} }
}, },
}); });

View File

@ -3,6 +3,10 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState'; import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
import { zNodeStatus } from 'features/nodes/types/invocation'; import { zNodeStatus } from 'features/nodes/types/invocation';
import { toast } from 'features/toast/toast';
import ToastWithSessionRefDescription from 'features/toast/ToastWithSessionRefDescription';
import { t } from 'i18next';
import { startCase } from 'lodash-es';
import { socketInvocationError } from 'services/events/actions'; import { socketInvocationError } from 'services/events/actions';
const log = logger('socketio'); const log = logger('socketio');
@ -12,7 +16,7 @@ export const addInvocationErrorEventListener = (startAppListening: AppStartListe
actionCreator: socketInvocationError, actionCreator: socketInvocationError,
effect: (action) => { effect: (action) => {
log.error(action.payload, `Invocation error (${action.payload.data.node.type})`); log.error(action.payload, `Invocation error (${action.payload.data.node.type})`);
const { source_node_id } = action.payload.data; const { source_node_id, error_type } = action.payload.data;
const nes = deepClone($nodeExecutionStates.get()[source_node_id]); const nes = deepClone($nodeExecutionStates.get()[source_node_id]);
if (nes) { if (nes) {
nes.status = zNodeStatus.enum.FAILED; nes.status = zNodeStatus.enum.FAILED;
@ -21,6 +25,30 @@ export const addInvocationErrorEventListener = (startAppListening: AppStartListe
nes.progressImage = null; nes.progressImage = null;
upsertExecutionState(nes.nodeId, nes); upsertExecutionState(nes.nodeId, nes);
} }
const errorType = startCase(action.payload.data.error_type);
const sessionId = action.payload.data.graph_execution_state_id;
if (error_type === 'OutOfMemoryError') {
toast({
id: 'INVOCATION_ERROR',
title: t('toast.outOfMemoryError'),
status: 'error',
description: ToastWithSessionRefDescription({
message: t('toast.outOfMemoryDescription'),
sessionId,
}),
});
} else {
toast({
id: `INVOCATION_ERROR_${errorType}`,
title: t('toast.serverError'),
status: 'error',
description: ToastWithSessionRefDescription({
message: errorType,
sessionId,
}),
});
}
}, },
}); });
}; };

View File

@ -1,14 +0,0 @@
import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { socketInvocationRetrievalError } from 'services/events/actions';
const log = logger('socketio');
export const addInvocationRetrievalErrorEventListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: socketInvocationRetrievalError,
effect: (action) => {
log.error(action.payload, `Invocation retrieval error (${action.payload.data.graph_execution_state_id})`);
},
});
};

View File

@ -1,14 +0,0 @@
import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { socketSessionRetrievalError } from 'services/events/actions';
const log = logger('socketio');
export const addSessionRetrievalErrorEventListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: socketSessionRetrievalError,
effect: (action) => {
log.error(action.payload, `Session retrieval error (${action.payload.data.graph_execution_state_id})`);
},
});
};

View File

@ -1,6 +1,6 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { stagingAreaImageSaved } from 'features/canvas/store/actions'; import { stagingAreaImageSaved } from 'features/canvas/store/actions';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
@ -29,15 +29,14 @@ export const addStagingAreaImageSavedListener = (startAppListening: AppStartList
}) })
); );
} }
dispatch(addToast({ title: t('toast.imageSaved'), status: 'success' })); toast({ id: 'IMAGE_SAVED', title: t('toast.imageSaved'), status: 'success' });
} catch (error) { } catch (error) {
dispatch( toast({
addToast({ id: 'IMAGE_SAVE_FAILED',
title: t('toast.imageSavingFailed'), title: t('toast.imageSavingFailed'),
description: (error as Error)?.message, description: (error as Error)?.message,
status: 'error', status: 'error',
}) });
);
} }
}, },
}); });

View File

@ -5,8 +5,7 @@ import { $templates, nodesChanged } from 'features/nodes/store/nodesSlice';
import { NodeUpdateError } from 'features/nodes/types/error'; import { NodeUpdateError } from 'features/nodes/types/error';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';
import { getNeedsUpdate, updateNode } from 'features/nodes/util/node/nodeUpdate'; import { getNeedsUpdate, updateNode } from 'features/nodes/util/node/nodeUpdate';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next'; import { t } from 'i18next';
export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartListening) => { export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartListening) => {
@ -50,24 +49,18 @@ export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartLi
count: unableToUpdateCount, count: unableToUpdateCount,
}) })
); );
dispatch( toast({
addToast( id: 'UNABLE_TO_UPDATE_NODES',
makeToast({ title: t('nodes.unableToUpdateNodes', {
title: t('nodes.unableToUpdateNodes', { count: unableToUpdateCount,
count: unableToUpdateCount, }),
}), });
})
)
);
} else { } else {
dispatch( toast({
addToast( id: 'ALL_NODES_UPDATED',
makeToast({ title: t('nodes.allNodesUpdated'),
title: t('nodes.allNodesUpdated'), status: 'success',
status: 'success', });
})
)
);
} }
}, },
}); });

View File

@ -4,7 +4,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
import { parseify } from 'common/util/serialize'; import { parseify } from 'common/util/serialize';
import { buildAdHocUpscaleGraph } from 'features/nodes/util/graph/buildAdHocUpscaleGraph'; import { buildAdHocUpscaleGraph } from 'features/nodes/util/graph/buildAdHocUpscaleGraph';
import { createIsAllowedToUpscaleSelector } from 'features/parameters/hooks/useIsAllowedToUpscale'; import { createIsAllowedToUpscaleSelector } from 'features/parameters/hooks/useIsAllowedToUpscale';
import { addToast } from 'features/system/store/systemSlice'; import { toast, ToastID } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
import type { BatchConfig, ImageDTO } from 'services/api/types'; import type { BatchConfig, ImageDTO } from 'services/api/types';
@ -29,12 +29,11 @@ export const addUpscaleRequestedListener = (startAppListening: AppStartListening
{ imageDTO }, { imageDTO },
t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge') // should never coalesce t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge') // should never coalesce
); );
dispatch( toast({
addToast({ id: 'NOT_ALLOWED_TO_UPSCALE',
title: t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge'), // should never coalesce title: t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge'), // should never coalesce
status: 'error', status: 'error',
}) });
);
return; return;
} }
@ -65,12 +64,11 @@ export const addUpscaleRequestedListener = (startAppListening: AppStartListening
if (error instanceof Object && 'status' in error && error.status === 403) { if (error instanceof Object && 'status' in error && error.status === 403) {
return; return;
} else { } else {
dispatch( toast({
addToast({ id: ToastID.GRAPH_QUEUE_FAILED,
title: t('queue.graphFailedToQueue'), title: t('queue.graphFailedToQueue'),
status: 'error', status: 'error',
}) });
);
} }
} }
}, },

View File

@ -8,8 +8,7 @@ import type { Templates } from 'features/nodes/store/types';
import { WorkflowMigrationError, WorkflowVersionError } from 'features/nodes/types/error'; import { WorkflowMigrationError, WorkflowVersionError } from 'features/nodes/types/error';
import { graphToWorkflow } from 'features/nodes/util/workflow/graphToWorkflow'; import { graphToWorkflow } from 'features/nodes/util/workflow/graphToWorkflow';
import { validateWorkflow } from 'features/nodes/util/workflow/validateWorkflow'; import { validateWorkflow } from 'features/nodes/util/workflow/validateWorkflow';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next'; import { t } from 'i18next';
import { checkBoardAccess, checkImageAccess, checkModelAccess } from 'services/api/hooks/accessChecks'; import { checkBoardAccess, checkImageAccess, checkModelAccess } from 'services/api/hooks/accessChecks';
import type { GraphAndWorkflowResponse, NonNullableGraph } from 'services/api/types'; import type { GraphAndWorkflowResponse, NonNullableGraph } from 'services/api/types';
@ -49,23 +48,18 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
dispatch(workflowLoaded(workflow)); dispatch(workflowLoaded(workflow));
if (!warnings.length) { if (!warnings.length) {
dispatch( toast({
addToast( id: 'WORKFLOW_LOADED',
makeToast({ title: t('toast.workflowLoaded'),
title: t('toast.workflowLoaded'), status: 'success',
status: 'success', });
})
)
);
} else { } else {
dispatch( toast({
addToast( id: 'WORKFLOW_LOADED',
makeToast({ title: t('toast.loadedWithWarnings'),
title: t('toast.loadedWithWarnings'), status: 'warning',
status: 'warning', });
})
)
);
warnings.forEach(({ message, ...rest }) => { warnings.forEach(({ message, ...rest }) => {
log.warn(rest, message); log.warn(rest, message);
}); });
@ -78,54 +72,42 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
if (e instanceof WorkflowVersionError) { if (e instanceof WorkflowVersionError) {
// The workflow version was not recognized in the valid list of versions // The workflow version was not recognized in the valid list of versions
log.error({ error: parseify(e) }, e.message); log.error({ error: parseify(e) }, e.message);
dispatch( toast({
addToast( id: 'UNABLE_TO_VALIDATE_WORKFLOW',
makeToast({ title: t('nodes.unableToValidateWorkflow'),
title: t('nodes.unableToValidateWorkflow'), status: 'error',
status: 'error', description: e.message,
description: e.message, });
})
)
);
} else if (e instanceof WorkflowMigrationError) { } else if (e instanceof WorkflowMigrationError) {
// There was a problem migrating the workflow to the latest version // There was a problem migrating the workflow to the latest version
log.error({ error: parseify(e) }, e.message); log.error({ error: parseify(e) }, e.message);
dispatch( toast({
addToast( id: 'UNABLE_TO_VALIDATE_WORKFLOW',
makeToast({ title: t('nodes.unableToValidateWorkflow'),
title: t('nodes.unableToValidateWorkflow'), status: 'error',
status: 'error', description: e.message,
description: e.message, });
})
)
);
} else if (e instanceof z.ZodError) { } else if (e instanceof z.ZodError) {
// There was a problem validating the workflow itself // There was a problem validating the workflow itself
const { message } = fromZodError(e, { const { message } = fromZodError(e, {
prefix: t('nodes.workflowValidation'), prefix: t('nodes.workflowValidation'),
}); });
log.error({ error: parseify(e) }, message); log.error({ error: parseify(e) }, message);
dispatch( toast({
addToast( id: 'UNABLE_TO_VALIDATE_WORKFLOW',
makeToast({ title: t('nodes.unableToValidateWorkflow'),
title: t('nodes.unableToValidateWorkflow'), status: 'error',
status: 'error', description: message,
description: message, });
})
)
);
} else { } else {
// Some other error occurred // Some other error occurred
log.error({ error: parseify(e) }, t('nodes.unknownErrorValidatingWorkflow')); log.error({ error: parseify(e) }, t('nodes.unknownErrorValidatingWorkflow'));
dispatch( toast({
addToast( id: 'UNABLE_TO_VALIDATE_WORKFLOW',
makeToast({ title: t('nodes.unableToValidateWorkflow'),
title: t('nodes.unableToValidateWorkflow'), status: 'error',
status: 'error', description: t('nodes.unknownErrorValidatingWorkflow'),
description: t('nodes.unknownErrorValidatingWorkflow'), });
})
)
);
} }
} }
}, },

View File

@ -1,11 +1,10 @@
import { useAppToaster } from 'app/components/Toaster';
import { useImageUrlToBlob } from 'common/hooks/useImageUrlToBlob'; import { useImageUrlToBlob } from 'common/hooks/useImageUrlToBlob';
import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard'; import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard';
import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const useCopyImageToClipboard = () => { export const useCopyImageToClipboard = () => {
const toaster = useAppToaster();
const { t } = useTranslation(); const { t } = useTranslation();
const imageUrlToBlob = useImageUrlToBlob(); const imageUrlToBlob = useImageUrlToBlob();
@ -16,12 +15,11 @@ export const useCopyImageToClipboard = () => {
const copyImageToClipboard = useCallback( const copyImageToClipboard = useCallback(
async (image_url: string) => { async (image_url: string) => {
if (!isClipboardAPIAvailable) { if (!isClipboardAPIAvailable) {
toaster({ toast({
id: 'PROBLEM_COPYING_IMAGE',
title: t('toast.problemCopyingImage'), title: t('toast.problemCopyingImage'),
description: "Your browser doesn't support the Clipboard API.", description: "Your browser doesn't support the Clipboard API.",
status: 'error', status: 'error',
duration: 2500,
isClosable: true,
}); });
} }
try { try {
@ -33,23 +31,21 @@ export const useCopyImageToClipboard = () => {
copyBlobToClipboard(blob); copyBlobToClipboard(blob);
toaster({ toast({
id: 'IMAGE_COPIED',
title: t('toast.imageCopied'), title: t('toast.imageCopied'),
status: 'success', status: 'success',
duration: 2500,
isClosable: true,
}); });
} catch (err) { } catch (err) {
toaster({ toast({
id: 'PROBLEM_COPYING_IMAGE',
title: t('toast.problemCopyingImage'), title: t('toast.problemCopyingImage'),
description: String(err), description: String(err),
status: 'error', status: 'error',
duration: 2500,
isClosable: true,
}); });
} }
}, },
[imageUrlToBlob, isClipboardAPIAvailable, t, toaster] [imageUrlToBlob, isClipboardAPIAvailable, t]
); );
return { isClipboardAPIAvailable, copyImageToClipboard }; return { isClipboardAPIAvailable, copyImageToClipboard };

View File

@ -1,13 +1,12 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { useAppToaster } from 'app/components/Toaster';
import { $authToken } from 'app/store/nanostores/authToken'; import { $authToken } from 'app/store/nanostores/authToken';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { imageDownloaded } from 'features/gallery/store/actions'; import { imageDownloaded } from 'features/gallery/store/actions';
import { toast } from 'features/toast/toast';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const useDownloadImage = () => { export const useDownloadImage = () => {
const toaster = useAppToaster();
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const authToken = useStore($authToken); const authToken = useStore($authToken);
@ -37,16 +36,15 @@ export const useDownloadImage = () => {
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
dispatch(imageDownloaded()); dispatch(imageDownloaded());
} catch (err) { } catch (err) {
toaster({ toast({
id: 'PROBLEM_DOWNLOADING_IMAGE',
title: t('toast.problemDownloadingImage'), title: t('toast.problemDownloadingImage'),
description: String(err), description: String(err),
status: 'error', status: 'error',
duration: 2500,
isClosable: true,
}); });
} }
}, },
[t, toaster, dispatch, authToken] [t, dispatch, authToken]
); );
return { downloadImage }; return { downloadImage };

View File

@ -1,6 +1,6 @@
import { useAppToaster } from 'app/components/Toaster';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { toast } from 'features/toast/toast';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import type { Accept, FileRejection } from 'react-dropzone'; import type { Accept, FileRejection } from 'react-dropzone';
@ -26,7 +26,6 @@ const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (ac
export const useFullscreenDropzone = () => { export const useFullscreenDropzone = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const toaster = useAppToaster();
const postUploadAction = useAppSelector(selectPostUploadAction); const postUploadAction = useAppSelector(selectPostUploadAction);
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId); const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false); const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
@ -37,13 +36,14 @@ export const useFullscreenDropzone = () => {
(rejection: FileRejection) => { (rejection: FileRejection) => {
setIsHandlingUpload(true); setIsHandlingUpload(true);
toaster({ toast({
id: 'UPLOAD_FAILED',
title: t('toast.uploadFailed'), title: t('toast.uploadFailed'),
description: rejection.errors.map((error) => error.message).join('\n'), description: rejection.errors.map((error) => error.message).join('\n'),
status: 'error', status: 'error',
}); });
}, },
[t, toaster] [t]
); );
const fileAcceptedCallback = useCallback( const fileAcceptedCallback = useCallback(
@ -62,7 +62,8 @@ export const useFullscreenDropzone = () => {
const onDrop = useCallback( const onDrop = useCallback(
(acceptedFiles: Array<File>, fileRejections: Array<FileRejection>) => { (acceptedFiles: Array<File>, fileRejections: Array<FileRejection>) => {
if (fileRejections.length > 1) { if (fileRejections.length > 1) {
toaster({ toast({
id: 'UPLOAD_FAILED',
title: t('toast.uploadFailed'), title: t('toast.uploadFailed'),
description: t('toast.uploadFailedInvalidUploadDesc'), description: t('toast.uploadFailedInvalidUploadDesc'),
status: 'error', status: 'error',
@ -78,7 +79,7 @@ export const useFullscreenDropzone = () => {
fileAcceptedCallback(file); fileAcceptedCallback(file);
}); });
}, },
[t, toaster, fileAcceptedCallback, fileRejectionCallback] [t, fileAcceptedCallback, fileRejectionCallback]
); );
const onDragOver = useCallback(() => { const onDragOver = useCallback(() => {

View File

@ -1,6 +0,0 @@
import { createStandaloneToast, theme, TOAST_OPTIONS } from '@invoke-ai/ui-library';
export const { toast } = createStandaloneToast({
theme: theme,
defaultOptions: TOAST_OPTIONS.defaultOptions,
});

View File

@ -1,6 +1,5 @@
import { Flex, MenuDivider, MenuItem, Spinner } from '@invoke-ai/ui-library'; import { Flex, MenuDivider, MenuItem, Spinner } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { useAppToaster } from 'app/components/Toaster';
import { $customStarUI } from 'app/store/nanostores/customStarUI'; import { $customStarUI } from 'app/store/nanostores/customStarUI';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard'; import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
@ -14,6 +13,7 @@ import { sentImageToCanvas, sentImageToImg2Img } from 'features/gallery/store/ac
import { $templates } from 'features/nodes/store/nodesSlice'; import { $templates } from 'features/nodes/store/nodesSlice';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { toast } from 'features/toast/toast';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow'; import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow';
import { size } from 'lodash-es'; import { size } from 'lodash-es';
@ -46,7 +46,6 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const toaster = useAppToaster();
const isCanvasEnabled = useFeatureStatus('canvas'); const isCanvasEnabled = useFeatureStatus('canvas');
const customStarUi = useStore($customStarUI); const customStarUi = useStore($customStarUI);
const { downloadImage } = useDownloadImage(); const { downloadImage } = useDownloadImage();
@ -86,13 +85,12 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
}); });
dispatch(setInitialCanvasImage(imageDTO, optimalDimension)); dispatch(setInitialCanvasImage(imageDTO, optimalDimension));
toaster({ toast({
id: 'SENT_TO_CANVAS',
title: t('toast.sentToUnifiedCanvas'), title: t('toast.sentToUnifiedCanvas'),
status: 'success', status: 'success',
duration: 2500,
isClosable: true,
}); });
}, [dispatch, imageDTO, t, toaster, optimalDimension]); }, [dispatch, imageDTO, t, optimalDimension]);
const handleChangeBoard = useCallback(() => { const handleChangeBoard = useCallback(() => {
dispatch(imagesToChangeSelected([imageDTO])); dispatch(imagesToChangeSelected([imageDTO]));

View File

@ -1,6 +1,5 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query'; import { skipToken } from '@reduxjs/toolkit/query';
import { useAppToaster } from 'app/components/Toaster';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors'; import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
import { setShouldShowImageDetails } from 'features/ui/store/uiSlice'; import { setShouldShowImageDetails } from 'features/ui/store/uiSlice';
@ -14,7 +13,6 @@ export const ToggleMetadataViewerButton = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const shouldShowImageDetails = useAppSelector((s) => s.ui.shouldShowImageDetails); const shouldShowImageDetails = useAppSelector((s) => s.ui.shouldShowImageDetails);
const lastSelectedImage = useAppSelector(selectLastSelectedImage); const lastSelectedImage = useAppSelector(selectLastSelectedImage);
const toaster = useAppToaster();
const { t } = useTranslation(); const { t } = useTranslation();
const { currentData: imageDTO } = useGetImageDTOQuery(lastSelectedImage?.image_name ?? skipToken); const { currentData: imageDTO } = useGetImageDTOQuery(lastSelectedImage?.image_name ?? skipToken);
@ -24,7 +22,7 @@ export const ToggleMetadataViewerButton = memo(() => {
[dispatch, shouldShowImageDetails] [dispatch, shouldShowImageDetails]
); );
useHotkeys('i', toggleMetadataViewer, { enabled: Boolean(imageDTO) }, [imageDTO, shouldShowImageDetails, toaster]); useHotkeys('i', toggleMetadataViewer, { enabled: Boolean(imageDTO) }, [imageDTO, shouldShowImageDetails]);
return ( return (
<IconButton <IconButton

View File

@ -1,5 +1,4 @@
import { objectKeys } from 'common/util/objectKeys'; import { objectKeys } from 'common/util/objectKeys';
import { toast } from 'common/util/toast';
import type { Layer } from 'features/controlLayers/store/types'; import type { Layer } from 'features/controlLayers/store/types';
import type { LoRA } from 'features/lora/store/loraSlice'; import type { LoRA } from 'features/lora/store/loraSlice';
import type { import type {
@ -15,6 +14,7 @@ import type {
import { fetchModelConfig } from 'features/metadata/util/modelFetchingHelpers'; import { fetchModelConfig } from 'features/metadata/util/modelFetchingHelpers';
import { validators } from 'features/metadata/util/validators'; import { validators } from 'features/metadata/util/validators';
import type { ModelIdentifierField } from 'features/nodes/types/common'; import type { ModelIdentifierField } from 'features/nodes/types/common';
import { toast, ToastID } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -89,23 +89,23 @@ const renderLayersValue: MetadataRenderValueFunc<Layer[]> = async (layers) => {
return `${layers.length} ${t('controlLayers.layers', { count: layers.length })}`; return `${layers.length} ${t('controlLayers.layers', { count: layers.length })}`;
}; };
const parameterSetToast = (parameter: string, description?: string) => { const parameterSetToast = (parameter: string) => {
toast({ toast({
title: t('toast.parameterSet', { parameter }), id: ToastID.PARAMETER_SET,
description, title: t('toast.parameterSet'),
description: t('toast.parameterSetDesc', { parameter }),
status: 'info', status: 'info',
duration: 2500,
isClosable: true,
}); });
}; };
const parameterNotSetToast = (parameter: string, description?: string) => { const parameterNotSetToast = (parameter: string, message?: string) => {
toast({ toast({
title: t('toast.parameterNotSet', { parameter }), id: 'PARAMETER_NOT_SET',
description, title: t('toast.parameterNotSet'),
description: message
? t('toast.parameterNotSetDescWithMessage', { parameter, message })
: t('toast.parameterNotSetDesc', { parameter }),
status: 'warning', status: 'warning',
duration: 2500,
isClosable: true,
}); });
}; };
@ -458,7 +458,18 @@ export const parseAndRecallAllMetadata = async (
}); });
}) })
); );
if (results.some((result) => result.status === 'fulfilled')) { if (results.some((result) => result.status === 'fulfilled')) {
parameterSetToast(t('toast.parameters')); toast({
id: ToastID.PARAMETER_SET,
title: t('toast.parametersSet'),
status: 'info',
});
} else {
toast({
id: ToastID.PARAMETER_SET,
title: t('toast.parametersNotSet'),
status: 'warning',
});
} }
}; };

View File

@ -1,7 +1,5 @@
import { Button, Flex, FormControl, FormErrorMessage, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library'; import { Button, Flex, FormControl, FormErrorMessage, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { toast, ToastID } from 'features/toast/toast';
import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast';
import type { ChangeEventHandler } from 'react'; import type { ChangeEventHandler } from 'react';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -14,7 +12,6 @@ export const HuggingFaceForm = () => {
const [displayResults, setDisplayResults] = useState(false); const [displayResults, setDisplayResults] = useState(false);
const [errorMessage, setErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState('');
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const [_getHuggingFaceModels, { isLoading, data }] = useLazyGetHuggingFaceModelsQuery(); const [_getHuggingFaceModels, { isLoading, data }] = useLazyGetHuggingFaceModelsQuery();
const [installModel] = useInstallModelMutation(); const [installModel] = useInstallModelMutation();
@ -24,29 +21,23 @@ export const HuggingFaceForm = () => {
installModel({ source }) installModel({ source })
.unwrap() .unwrap()
.then((_) => { .then((_) => {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUED,
makeToast({ title: t('toast.modelAddedSimple'),
title: t('toast.modelAddedSimple'), status: 'success',
status: 'success', });
})
)
);
}) })
.catch((error) => { .catch((error) => {
if (error) { if (error) {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUE_FAILED,
makeToast({ title: `${error.data.detail} `,
title: `${error.data.detail} `, status: 'error',
status: 'error', });
})
)
);
} }
}); });
}, },
[installModel, dispatch, t] [installModel, t]
); );
const getModels = useCallback(async () => { const getModels = useCallback(async () => {

View File

@ -1,7 +1,5 @@
import { Flex, IconButton, Text } from '@invoke-ai/ui-library'; import { Flex, IconButton, Text } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { toast, ToastID } from 'features/toast/toast';
import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiPlusBold } from 'react-icons/pi'; import { PiPlusBold } from 'react-icons/pi';
@ -12,7 +10,6 @@ type Props = {
}; };
export const HuggingFaceResultItem = ({ result }: Props) => { export const HuggingFaceResultItem = ({ result }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const [installModel] = useInstallModelMutation(); const [installModel] = useInstallModelMutation();
@ -20,28 +17,22 @@ export const HuggingFaceResultItem = ({ result }: Props) => {
installModel({ source: result }) installModel({ source: result })
.unwrap() .unwrap()
.then((_) => { .then((_) => {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUED,
makeToast({ title: t('toast.modelAddedSimple'),
title: t('toast.modelAddedSimple'), status: 'success',
status: 'success', });
})
)
);
}) })
.catch((error) => { .catch((error) => {
if (error) { if (error) {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUE_FAILED,
makeToast({ title: `${error.data.detail} `,
title: `${error.data.detail} `, status: 'error',
status: 'error', });
})
)
);
} }
}); });
}, [installModel, result, dispatch, t]); }, [installModel, result, t]);
return ( return (
<Flex alignItems="center" justifyContent="space-between" w="100%" gap={3}> <Flex alignItems="center" justifyContent="space-between" w="100%" gap={3}>

View File

@ -8,10 +8,8 @@ import {
InputGroup, InputGroup,
InputRightElement, InputRightElement,
} from '@invoke-ai/ui-library'; } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { addToast } from 'features/system/store/systemSlice'; import { toast, ToastID } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import type { ChangeEventHandler } from 'react'; import type { ChangeEventHandler } from 'react';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -27,7 +25,6 @@ type HuggingFaceResultsProps = {
export const HuggingFaceResults = ({ results }: HuggingFaceResultsProps) => { export const HuggingFaceResults = ({ results }: HuggingFaceResultsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const dispatch = useAppDispatch();
const [installModel] = useInstallModelMutation(); const [installModel] = useInstallModelMutation();
@ -51,29 +48,23 @@ export const HuggingFaceResults = ({ results }: HuggingFaceResultsProps) => {
installModel({ source: result }) installModel({ source: result })
.unwrap() .unwrap()
.then((_) => { .then((_) => {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUED,
makeToast({ title: t('toast.modelAddedSimple'),
title: t('toast.modelAddedSimple'), status: 'success',
status: 'success', });
})
)
);
}) })
.catch((error) => { .catch((error) => {
if (error) { if (error) {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUE_FAILED,
makeToast({ title: `${error.data.detail} `,
title: `${error.data.detail} `, status: 'error',
status: 'error', });
})
)
);
} }
}); });
} }
}, [filteredResults, installModel, dispatch, t]); }, [filteredResults, installModel, t]);
return ( return (
<> <>

View File

@ -1,7 +1,5 @@
import { Button, Checkbox, Flex, FormControl, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library'; import { Button, Checkbox, Flex, FormControl, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { toast, ToastID } from 'features/toast/toast';
import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next'; import { t } from 'i18next';
import { useCallback } from 'react'; import { useCallback } from 'react';
import type { SubmitHandler } from 'react-hook-form'; import type { SubmitHandler } from 'react-hook-form';
@ -14,8 +12,6 @@ type SimpleImportModelConfig = {
}; };
export const InstallModelForm = () => { export const InstallModelForm = () => {
const dispatch = useAppDispatch();
const [installModel, { isLoading }] = useInstallModelMutation(); const [installModel, { isLoading }] = useInstallModelMutation();
const { register, handleSubmit, formState, reset } = useForm<SimpleImportModelConfig>({ const { register, handleSubmit, formState, reset } = useForm<SimpleImportModelConfig>({
@ -35,31 +31,25 @@ export const InstallModelForm = () => {
installModel({ source: values.location, inplace: values.inplace }) installModel({ source: values.location, inplace: values.inplace })
.unwrap() .unwrap()
.then((_) => { .then((_) => {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUED,
makeToast({ title: t('toast.modelAddedSimple'),
title: t('toast.modelAddedSimple'), status: 'success',
status: 'success', });
})
)
);
reset(undefined, { keepValues: true }); reset(undefined, { keepValues: true });
}) })
.catch((error) => { .catch((error) => {
reset(undefined, { keepValues: true }); reset(undefined, { keepValues: true });
if (error) { if (error) {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUE_FAILED,
makeToast({ title: `${error.data.detail} `,
title: `${error.data.detail} `, status: 'error',
status: 'error', });
})
)
);
} }
}); });
}, },
[dispatch, reset, installModel] [reset, installModel]
); );
return ( return (

View File

@ -1,8 +1,6 @@
import { Box, Button, Flex, Heading } from '@invoke-ai/ui-library'; import { Box, Button, Flex, Heading } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next'; import { t } from 'i18next';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useListModelInstallsQuery, usePruneCompletedModelInstallsMutation } from 'services/api/endpoints/models'; import { useListModelInstallsQuery, usePruneCompletedModelInstallsMutation } from 'services/api/endpoints/models';
@ -10,8 +8,6 @@ import { useListModelInstallsQuery, usePruneCompletedModelInstallsMutation } fro
import { ModelInstallQueueItem } from './ModelInstallQueueItem'; import { ModelInstallQueueItem } from './ModelInstallQueueItem';
export const ModelInstallQueue = () => { export const ModelInstallQueue = () => {
const dispatch = useAppDispatch();
const { data } = useListModelInstallsQuery(); const { data } = useListModelInstallsQuery();
const [_pruneCompletedModelInstalls] = usePruneCompletedModelInstallsMutation(); const [_pruneCompletedModelInstalls] = usePruneCompletedModelInstallsMutation();
@ -20,28 +16,22 @@ export const ModelInstallQueue = () => {
_pruneCompletedModelInstalls() _pruneCompletedModelInstalls()
.unwrap() .unwrap()
.then((_) => { .then((_) => {
dispatch( toast({
addToast( id: 'MODEL_INSTALL_QUEUE_PRUNED',
makeToast({ title: t('toast.prunedQueue'),
title: t('toast.prunedQueue'), status: 'success',
status: 'success', });
})
)
);
}) })
.catch((error) => { .catch((error) => {
if (error) { if (error) {
dispatch( toast({
addToast( id: 'MODEL_INSTALL_QUEUE_PRUNE_FAILED',
makeToast({ title: `${error.data.detail} `,
title: `${error.data.detail} `, status: 'error',
status: 'error', });
})
)
);
} }
}); });
}, [_pruneCompletedModelInstalls, dispatch]); }, [_pruneCompletedModelInstalls]);
const pruneAvailable = useMemo(() => { const pruneAvailable = useMemo(() => {
return data?.some( return data?.some(

View File

@ -1,7 +1,5 @@
import { Flex, IconButton, Progress, Text, Tooltip } from '@invoke-ai/ui-library'; import { Flex, IconButton, Progress, Text, Tooltip } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { toast } from 'features/toast/toast';
import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next'; import { t } from 'i18next';
import { isNil } from 'lodash-es'; import { isNil } from 'lodash-es';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
@ -29,7 +27,6 @@ const formatBytes = (bytes: number) => {
export const ModelInstallQueueItem = (props: ModelListItemProps) => { export const ModelInstallQueueItem = (props: ModelListItemProps) => {
const { installJob } = props; const { installJob } = props;
const dispatch = useAppDispatch();
const [deleteImportModel] = useCancelModelInstallMutation(); const [deleteImportModel] = useCancelModelInstallMutation();
@ -37,28 +34,22 @@ export const ModelInstallQueueItem = (props: ModelListItemProps) => {
deleteImportModel(installJob.id) deleteImportModel(installJob.id)
.unwrap() .unwrap()
.then((_) => { .then((_) => {
dispatch( toast({
addToast( id: 'MODEL_INSTALL_CANCELED',
makeToast({ title: t('toast.modelImportCanceled'),
title: t('toast.modelImportCanceled'), status: 'success',
status: 'success', });
})
)
);
}) })
.catch((error) => { .catch((error) => {
if (error) { if (error) {
dispatch( toast({
addToast( id: 'MODEL_INSTALL_CANCEL_FAILED',
makeToast({ title: `${error.data.detail} `,
title: `${error.data.detail} `, status: 'error',
status: 'error', });
})
)
);
} }
}); });
}, [deleteImportModel, installJob, dispatch]); }, [deleteImportModel, installJob]);
const sourceLocation = useMemo(() => { const sourceLocation = useMemo(() => {
switch (installJob.source.type) { switch (installJob.source.type) {

View File

@ -11,10 +11,8 @@ import {
InputGroup, InputGroup,
InputRightElement, InputRightElement,
} from '@invoke-ai/ui-library'; } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { addToast } from 'features/system/store/systemSlice'; import { toast, ToastID } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import type { ChangeEvent, ChangeEventHandler } from 'react'; import type { ChangeEvent, ChangeEventHandler } from 'react';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -30,7 +28,6 @@ type ScanModelResultsProps = {
export const ScanModelsResults = ({ results }: ScanModelResultsProps) => { export const ScanModelsResults = ({ results }: ScanModelResultsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const dispatch = useAppDispatch();
const [inplace, setInplace] = useState(true); const [inplace, setInplace] = useState(true);
const [installModel] = useInstallModelMutation(); const [installModel] = useInstallModelMutation();
@ -61,58 +58,46 @@ export const ScanModelsResults = ({ results }: ScanModelResultsProps) => {
installModel({ source: result.path, inplace }) installModel({ source: result.path, inplace })
.unwrap() .unwrap()
.then((_) => { .then((_) => {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUED,
makeToast({ title: t('toast.modelAddedSimple'),
title: t('toast.modelAddedSimple'), status: 'success',
status: 'success', });
})
)
);
}) })
.catch((error) => { .catch((error) => {
if (error) { if (error) {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUE_FAILED,
makeToast({ title: `${error.data.detail} `,
title: `${error.data.detail} `, status: 'error',
status: 'error', });
})
)
);
} }
}); });
} }
}, [filteredResults, installModel, inplace, dispatch, t]); }, [filteredResults, installModel, inplace, t]);
const handleInstallOne = useCallback( const handleInstallOne = useCallback(
(source: string) => { (source: string) => {
installModel({ source, inplace }) installModel({ source, inplace })
.unwrap() .unwrap()
.then((_) => { .then((_) => {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUED,
makeToast({ title: t('toast.modelAddedSimple'),
title: t('toast.modelAddedSimple'), status: 'success',
status: 'success', });
})
)
);
}) })
.catch((error) => { .catch((error) => {
if (error) { if (error) {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUE_FAILED,
makeToast({ title: `${error.data.detail} `,
title: `${error.data.detail} `, status: 'error',
status: 'error', });
})
)
);
} }
}); });
}, },
[installModel, inplace, dispatch, t] [installModel, inplace, t]
); );
return ( return (

View File

@ -1,8 +1,6 @@
import { Badge, Box, Flex, IconButton, Text } from '@invoke-ai/ui-library'; import { Badge, Box, Flex, IconButton, Text } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import ModelBaseBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelBaseBadge'; import ModelBaseBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelBaseBadge';
import { addToast } from 'features/system/store/systemSlice'; import { toast, ToastID } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiPlusBold } from 'react-icons/pi'; import { PiPlusBold } from 'react-icons/pi';
@ -14,7 +12,6 @@ type Props = {
}; };
export const StarterModelsResultItem = ({ result }: Props) => { export const StarterModelsResultItem = ({ result }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const allSources = useMemo(() => { const allSources = useMemo(() => {
const _allSources = [result.source]; const _allSources = [result.source];
if (result.dependencies) { if (result.dependencies) {
@ -29,29 +26,23 @@ export const StarterModelsResultItem = ({ result }: Props) => {
installModel({ source }) installModel({ source })
.unwrap() .unwrap()
.then((_) => { .then((_) => {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUED,
makeToast({ title: t('toast.modelAddedSimple'),
title: t('toast.modelAddedSimple'), status: 'success',
status: 'success', });
})
)
);
}) })
.catch((error) => { .catch((error) => {
if (error) { if (error) {
dispatch( toast({
addToast( id: ToastID.MODEL_INSTALL_QUEUE_FAILED,
makeToast({ title: `${error.data.detail} `,
title: `${error.data.detail} `, status: 'error',
status: 'error', });
})
)
);
} }
}); });
} }
}, [allSources, installModel, dispatch, t]); }, [allSources, installModel, t]);
return ( return (
<Flex alignItems="center" justifyContent="space-between" w="100%" gap={3}> <Flex alignItems="center" justifyContent="space-between" w="100%" gap={3}>

View File

@ -4,8 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { setSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice';
import ModelBaseBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelBaseBadge'; import ModelBaseBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelBaseBadge';
import ModelFormatBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelFormatBadge'; import ModelFormatBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelFormatBadge';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -53,25 +52,19 @@ const ModelListItem = (props: ModelListItemProps) => {
deleteModel({ key: model.key }) deleteModel({ key: model.key })
.unwrap() .unwrap()
.then((_) => { .then((_) => {
dispatch( toast({
addToast( id: 'MODEL_DELETED',
makeToast({ title: `${t('modelManager.modelDeleted')}: ${model.name}`,
title: `${t('modelManager.modelDeleted')}: ${model.name}`, status: 'success',
status: 'success', });
})
)
);
}) })
.catch((error) => { .catch((error) => {
if (error) { if (error) {
dispatch( toast({
addToast( id: 'MODEL_DELETE_FAILED',
makeToast({ title: `${t('modelManager.modelDeleteFailed')}: ${model.name}`,
title: `${t('modelManager.modelDeleteFailed')}: ${model.name}`, status: 'error',
status: 'error', });
})
)
);
} }
}); });
dispatch(setSelectedModelKey(null)); dispatch(setSelectedModelKey(null));

View File

@ -1,10 +1,9 @@
import { Button, Flex, Heading, SimpleGrid, Text } from '@invoke-ai/ui-library'; import { Button, Flex, Heading, SimpleGrid, Text } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useControlNetOrT2IAdapterDefaultSettings } from 'features/modelManagerV2/hooks/useControlNetOrT2IAdapterDefaultSettings'; import { useControlNetOrT2IAdapterDefaultSettings } from 'features/modelManagerV2/hooks/useControlNetOrT2IAdapterDefaultSettings';
import { DefaultPreprocessor } from 'features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/DefaultPreprocessor'; import { DefaultPreprocessor } from 'features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/DefaultPreprocessor';
import type { FormField } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings'; import type { FormField } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { useCallback } from 'react'; import { useCallback } from 'react';
import type { SubmitHandler } from 'react-hook-form'; import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@ -19,7 +18,6 @@ export type ControlNetOrT2IAdapterDefaultSettingsFormData = {
export const ControlNetOrT2IAdapterDefaultSettings = () => { export const ControlNetOrT2IAdapterDefaultSettings = () => {
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const { defaultSettingsDefaults, isLoading: isLoadingDefaultSettings } = const { defaultSettingsDefaults, isLoading: isLoadingDefaultSettings } =
useControlNetOrT2IAdapterDefaultSettings(selectedModelKey); useControlNetOrT2IAdapterDefaultSettings(selectedModelKey);
@ -46,30 +44,24 @@ export const ControlNetOrT2IAdapterDefaultSettings = () => {
}) })
.unwrap() .unwrap()
.then((_) => { .then((_) => {
dispatch( toast({
addToast( id: 'DEFAULT_SETTINGS_SAVED',
makeToast({ title: t('modelManager.defaultSettingsSaved'),
title: t('modelManager.defaultSettingsSaved'), status: 'success',
status: 'success', });
})
)
);
reset(data); reset(data);
}) })
.catch((error) => { .catch((error) => {
if (error) { if (error) {
dispatch( toast({
addToast( id: 'DEFAULT_SETTINGS_SAVE_FAILED',
makeToast({ title: `${error.data.detail} `,
title: `${error.data.detail} `, status: 'error',
status: 'error', });
})
)
);
} }
}); });
}, },
[selectedModelKey, dispatch, reset, updateModel, t] [selectedModelKey, reset, updateModel, t]
); );
if (isLoadingDefaultSettings) { if (isLoadingDefaultSettings) {

View File

@ -1,8 +1,6 @@
import { Box, Button, Flex, Icon, IconButton, Image, Tooltip } from '@invoke-ai/ui-library'; import { Box, Button, Flex, Icon, IconButton, Image, Tooltip } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { typedMemo } from 'common/util/typedMemo'; import { typedMemo } from 'common/util/typedMemo';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { useDropzone } from 'react-dropzone'; import { useDropzone } from 'react-dropzone';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -15,7 +13,6 @@ type Props = {
}; };
const ModelImageUpload = ({ model_key, model_image }: Props) => { const ModelImageUpload = ({ model_key, model_image }: Props) => {
const dispatch = useAppDispatch();
const [image, setImage] = useState<string | null>(model_image || null); const [image, setImage] = useState<string | null>(model_image || null);
const { t } = useTranslation(); const { t } = useTranslation();
@ -34,27 +31,21 @@ const ModelImageUpload = ({ model_key, model_image }: Props) => {
.unwrap() .unwrap()
.then(() => { .then(() => {
setImage(URL.createObjectURL(file)); setImage(URL.createObjectURL(file));
dispatch( toast({
addToast( id: 'MODEL_IMAGE_UPDATED',
makeToast({ title: t('modelManager.modelImageUpdated'),
title: t('modelManager.modelImageUpdated'), status: 'success',
status: 'success', });
})
)
);
}) })
.catch((_) => { .catch(() => {
dispatch( toast({
addToast( id: 'MODEL_IMAGE_UPDATE_FAILED',
makeToast({ title: t('modelManager.modelImageUpdateFailed'),
title: t('modelManager.modelImageUpdateFailed'), status: 'error',
status: 'error', });
})
)
);
}); });
}, },
[dispatch, model_key, t, updateModelImage] [model_key, t, updateModelImage]
); );
const handleResetImage = useCallback(() => { const handleResetImage = useCallback(() => {
@ -65,26 +56,20 @@ const ModelImageUpload = ({ model_key, model_image }: Props) => {
deleteModelImage(model_key) deleteModelImage(model_key)
.unwrap() .unwrap()
.then(() => { .then(() => {
dispatch( toast({
addToast( id: 'MODEL_IMAGE_DELETED',
makeToast({ title: t('modelManager.modelImageDeleted'),
title: t('modelManager.modelImageDeleted'), status: 'success',
status: 'success', });
})
)
);
}) })
.catch((_) => { .catch(() => {
dispatch( toast({
addToast( id: 'MODEL_IMAGE_DELETE_FAILED',
makeToast({ title: t('modelManager.modelImageDeleteFailed'),
title: t('modelManager.modelImageDeleteFailed'), status: 'error',
status: 'error', });
})
)
);
}); });
}, [dispatch, model_key, t, deleteModelImage]); }, [model_key, t, deleteModelImage]);
const { getInputProps, getRootProps } = useDropzone({ const { getInputProps, getRootProps } = useDropzone({
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] }, accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },

View File

@ -1,11 +1,10 @@
import { Button, Flex, Heading, SimpleGrid, Text } from '@invoke-ai/ui-library'; import { Button, Flex, Heading, SimpleGrid, Text } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useMainModelDefaultSettings } from 'features/modelManagerV2/hooks/useMainModelDefaultSettings'; import { useMainModelDefaultSettings } from 'features/modelManagerV2/hooks/useMainModelDefaultSettings';
import { DefaultHeight } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight'; import { DefaultHeight } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight';
import { DefaultWidth } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth'; import { DefaultWidth } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth';
import type { ParameterScheduler } from 'features/parameters/types/parameterSchemas'; import type { ParameterScheduler } from 'features/parameters/types/parameterSchemas';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { useCallback } from 'react'; import { useCallback } from 'react';
import type { SubmitHandler } from 'react-hook-form'; import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@ -39,7 +38,6 @@ export type MainModelDefaultSettingsFormData = {
export const MainModelDefaultSettings = () => { export const MainModelDefaultSettings = () => {
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const { const {
defaultSettingsDefaults, defaultSettingsDefaults,
@ -76,30 +74,24 @@ export const MainModelDefaultSettings = () => {
}) })
.unwrap() .unwrap()
.then((_) => { .then((_) => {
dispatch( toast({
addToast( id: 'DEFAULT_SETTINGS_SAVED',
makeToast({ title: t('modelManager.defaultSettingsSaved'),
title: t('modelManager.defaultSettingsSaved'), status: 'success',
status: 'success', });
})
)
);
reset(data); reset(data);
}) })
.catch((error) => { .catch((error) => {
if (error) { if (error) {
dispatch( toast({
addToast( id: 'DEFAULT_SETTINGS_SAVE_FAILED',
makeToast({ title: `${error.data.detail} `,
title: `${error.data.detail} `, status: 'error',
status: 'error', });
})
)
);
} }
}); });
}, },
[selectedModelKey, dispatch, reset, updateModel, t] [selectedModelKey, reset, updateModel, t]
); );
if (isLoadingDefaultSettings) { if (isLoadingDefaultSettings) {

View File

@ -4,8 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { setSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice';
import { ModelConvertButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton'; import { ModelConvertButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton';
import { ModelEditButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelEditButton'; import { ModelEditButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelEditButton';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { useCallback } from 'react'; import { useCallback } from 'react';
import type { SubmitHandler } from 'react-hook-form'; import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@ -47,25 +46,19 @@ export const Model = () => {
.then((payload) => { .then((payload) => {
form.reset(payload, { keepDefaultValues: true }); form.reset(payload, { keepDefaultValues: true });
dispatch(setSelectedModelMode('view')); dispatch(setSelectedModelMode('view'));
dispatch( toast({
addToast( id: 'MODEL_UPDATED',
makeToast({ title: t('modelManager.modelUpdated'),
title: t('modelManager.modelUpdated'), status: 'success',
status: 'success', });
})
)
);
}) })
.catch((_) => { .catch((_) => {
form.reset(); form.reset();
dispatch( toast({
addToast( id: 'MODEL_UPDATE_FAILED',
makeToast({ title: t('modelManager.modelUpdateFailed'),
title: t('modelManager.modelUpdateFailed'), status: 'error',
status: 'error', });
})
)
);
}); });
}, },
[dispatch, data?.key, form, t, updateModel] [dispatch, data?.key, form, t, updateModel]

View File

@ -9,9 +9,7 @@ import {
useDisclosure, useDisclosure,
} from '@invoke-ai/ui-library'; } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query'; import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch } from 'app/store/storeHooks'; import { toast } from 'features/toast/toast';
import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useConvertModelMutation, useGetModelConfigQuery } from 'services/api/endpoints/models'; import { useConvertModelMutation, useGetModelConfigQuery } from 'services/api/endpoints/models';
@ -22,7 +20,6 @@ interface ModelConvertProps {
export const ModelConvertButton = (props: ModelConvertProps) => { export const ModelConvertButton = (props: ModelConvertProps) => {
const { modelKey } = props; const { modelKey } = props;
const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const { data } = useGetModelConfigQuery(modelKey ?? skipToken); const { data } = useGetModelConfigQuery(modelKey ?? skipToken);
const [convertModel, { isLoading }] = useConvertModelMutation(); const [convertModel, { isLoading }] = useConvertModelMutation();
@ -33,38 +30,26 @@ export const ModelConvertButton = (props: ModelConvertProps) => {
return; return;
} }
dispatch( const toastId = `CONVERTING_MODEL_${data.key}`;
addToast( toast({
makeToast({ id: toastId,
title: `${t('modelManager.convertingModelBegin')}: ${data?.name}`, title: `${t('modelManager.convertingModelBegin')}: ${data?.name}`,
status: 'info', status: 'info',
}) });
)
);
convertModel(data?.key) convertModel(data?.key)
.unwrap() .unwrap()
.then(() => { .then(() => {
dispatch( toast({ id: toastId, title: `${t('modelManager.modelConverted')}: ${data?.name}`, status: 'success' });
addToast(
makeToast({
title: `${t('modelManager.modelConverted')}: ${data?.name}`,
status: 'success',
})
)
);
}) })
.catch(() => { .catch(() => {
dispatch( toast({
addToast( id: toastId,
makeToast({ title: `${t('modelManager.modelConversionFailed')}: ${data?.name}`,
title: `${t('modelManager.modelConversionFailed')}: ${data?.name}`, status: 'error',
status: 'error', });
})
)
);
}); });
}, [data, isLoading, dispatch, t, convertModel]); }, [data, isLoading, t, convertModel]);
if (data?.format !== 'checkpoint') { if (data?.format !== 'checkpoint') {
return; return;

View File

@ -3,7 +3,6 @@ import 'reactflow/dist/style.css';
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library'; import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, Flex, Popover, PopoverAnchor, PopoverBody, PopoverContent } from '@invoke-ai/ui-library'; import { Combobox, Flex, Popover, PopoverAnchor, PopoverBody, PopoverContent } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { useAppToaster } from 'app/components/Toaster';
import { useAppDispatch, useAppStore } from 'app/store/storeHooks'; import { useAppDispatch, useAppStore } from 'app/store/storeHooks';
import type { SelectInstance } from 'chakra-react-select'; import type { SelectInstance } from 'chakra-react-select';
import { useBuildNode } from 'features/nodes/hooks/useBuildNode'; import { useBuildNode } from 'features/nodes/hooks/useBuildNode';
@ -24,6 +23,7 @@ import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
import { validateConnectionTypes } from 'features/nodes/store/util/validateConnectionTypes'; import { validateConnectionTypes } from 'features/nodes/store/util/validateConnectionTypes';
import type { AnyNode } from 'features/nodes/types/invocation'; import type { AnyNode } from 'features/nodes/types/invocation';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';
import { toast } from 'features/toast/toast';
import { filter, map, memoize, some } from 'lodash-es'; import { filter, map, memoize, some } from 'lodash-es';
import { memo, useCallback, useMemo, useRef } from 'react'; import { memo, useCallback, useMemo, useRef } from 'react';
import { flushSync } from 'react-dom'; import { flushSync } from 'react-dom';
@ -60,7 +60,6 @@ const filterOption = memoize((option: FilterOptionOption<ComboboxOption>, inputV
const AddNodePopover = () => { const AddNodePopover = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const buildInvocation = useBuildNode(); const buildInvocation = useBuildNode();
const toaster = useAppToaster();
const { t } = useTranslation(); const { t } = useTranslation();
const selectRef = useRef<SelectInstance<ComboboxOption> | null>(null); const selectRef = useRef<SelectInstance<ComboboxOption> | null>(null);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
@ -127,7 +126,7 @@ const AddNodePopover = () => {
const errorMessage = t('nodes.unknownNode', { const errorMessage = t('nodes.unknownNode', {
nodeType: nodeType, nodeType: nodeType,
}); });
toaster({ toast({
status: 'error', status: 'error',
title: errorMessage, title: errorMessage,
}); });
@ -163,7 +162,7 @@ const AddNodePopover = () => {
} }
return node; return node;
}, },
[buildInvocation, store, dispatch, t, toaster] [buildInvocation, store, dispatch, t]
); );
const onChange = useCallback<ComboboxOnChange>( const onChange = useCallback<ComboboxOnChange>(

View File

@ -1,8 +1,7 @@
import { ConfirmationAlertDialog, Flex, IconButton, Text, useDisclosure } from '@invoke-ai/ui-library'; import { ConfirmationAlertDialog, Flex, IconButton, Text, useDisclosure } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiTrashSimpleFill } from 'react-icons/pi'; import { PiTrashSimpleFill } from 'react-icons/pi';
@ -16,14 +15,11 @@ const ClearFlowButton = () => {
const handleNewWorkflow = useCallback(() => { const handleNewWorkflow = useCallback(() => {
dispatch(nodeEditorReset()); dispatch(nodeEditorReset());
dispatch( toast({
addToast( id: 'WORKFLOW_CLEARED',
makeToast({ title: t('workflows.workflowCleared'),
title: t('workflows.workflowCleared'), status: 'success',
status: 'success', });
})
)
);
onClose(); onClose();
}, [dispatch, onClose, t]); }, [dispatch, onClose, t]);

View File

@ -1,10 +1,10 @@
import { skipToken } from '@reduxjs/toolkit/query'; import { skipToken } from '@reduxjs/toolkit/query';
import { useAppToaster } from 'app/components/Toaster';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice'; import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
import { parseAndRecallAllMetadata } from 'features/metadata/util/handlers'; import { parseAndRecallAllMetadata } from 'features/metadata/util/handlers';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { toast } from 'features/toast/toast';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
import { t } from 'i18next'; import { t } from 'i18next';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
@ -16,7 +16,6 @@ export const usePreselectedImage = (selectedImage?: {
}) => { }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const toaster = useAppToaster();
const { currentData: selectedImageDto } = useGetImageDTOQuery(selectedImage?.imageName ?? skipToken); const { currentData: selectedImageDto } = useGetImageDTOQuery(selectedImage?.imageName ?? skipToken);
@ -26,14 +25,13 @@ export const usePreselectedImage = (selectedImage?: {
if (selectedImageDto) { if (selectedImageDto) {
dispatch(setInitialCanvasImage(selectedImageDto, optimalDimension)); dispatch(setInitialCanvasImage(selectedImageDto, optimalDimension));
dispatch(setActiveTab('canvas')); dispatch(setActiveTab('canvas'));
toaster({ toast({
id: 'SENT_TO_CANVAS',
title: t('toast.sentToUnifiedCanvas'), title: t('toast.sentToUnifiedCanvas'),
status: 'info', status: 'info',
duration: 2500,
isClosable: true,
}); });
} }
}, [selectedImageDto, dispatch, optimalDimension, toaster]); }, [selectedImageDto, dispatch, optimalDimension]);
const handleSendToImg2Img = useCallback(() => { const handleSendToImg2Img = useCallback(() => {
if (selectedImageDto) { if (selectedImageDto) {

View File

@ -1,5 +1,5 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useCancelByBatchIdsMutation, useGetBatchStatusQuery } from 'services/api/endpoints/queue'; import { useCancelByBatchIdsMutation, useGetBatchStatusQuery } from 'services/api/endpoints/queue';
@ -23,7 +23,6 @@ export const useCancelBatch = (batch_id: string) => {
const [trigger, { isLoading }] = useCancelByBatchIdsMutation({ const [trigger, { isLoading }] = useCancelByBatchIdsMutation({
fixedCacheKey: 'cancelByBatchIds', fixedCacheKey: 'cancelByBatchIds',
}); });
const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const cancelBatch = useCallback(async () => { const cancelBatch = useCallback(async () => {
if (isCanceled) { if (isCanceled) {
@ -31,21 +30,19 @@ export const useCancelBatch = (batch_id: string) => {
} }
try { try {
await trigger({ batch_ids: [batch_id] }).unwrap(); await trigger({ batch_ids: [batch_id] }).unwrap();
dispatch( toast({
addToast({ id: 'CANCEL_BATCH_SUCCEEDED',
title: t('queue.cancelBatchSucceeded'), title: t('queue.cancelBatchSucceeded'),
status: 'success', status: 'success',
}) });
);
} catch { } catch {
dispatch( toast({
addToast({ id: 'CANCEL_BATCH_FAILED',
title: t('queue.cancelBatchFailed'), title: t('queue.cancelBatchFailed'),
status: 'error', status: 'error',
}) });
);
} }
}, [batch_id, dispatch, isCanceled, t, trigger]); }, [batch_id, isCanceled, t, trigger]);
return { cancelBatch, isLoading, isCanceled, isDisabled: !isConnected }; return { cancelBatch, isLoading, isCanceled, isDisabled: !isConnected };
}; };

View File

@ -1,5 +1,5 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { isNil } from 'lodash-es'; import { isNil } from 'lodash-es';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -9,7 +9,6 @@ export const useCancelCurrentQueueItem = () => {
const isConnected = useAppSelector((s) => s.system.isConnected); const isConnected = useAppSelector((s) => s.system.isConnected);
const { data: queueStatus } = useGetQueueStatusQuery(); const { data: queueStatus } = useGetQueueStatusQuery();
const [trigger, { isLoading }] = useCancelQueueItemMutation(); const [trigger, { isLoading }] = useCancelQueueItemMutation();
const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const currentQueueItemId = useMemo(() => queueStatus?.queue.item_id, [queueStatus?.queue.item_id]); const currentQueueItemId = useMemo(() => queueStatus?.queue.item_id, [queueStatus?.queue.item_id]);
const cancelQueueItem = useCallback(async () => { const cancelQueueItem = useCallback(async () => {
@ -18,21 +17,19 @@ export const useCancelCurrentQueueItem = () => {
} }
try { try {
await trigger(currentQueueItemId).unwrap(); await trigger(currentQueueItemId).unwrap();
dispatch( toast({
addToast({ id: 'QUEUE_CANCEL_SUCCEEDED',
title: t('queue.cancelSucceeded'), title: t('queue.cancelSucceeded'),
status: 'success', status: 'success',
}) });
);
} catch { } catch {
dispatch( toast({
addToast({ id: 'QUEUE_CANCEL_FAILED',
title: t('queue.cancelFailed'), title: t('queue.cancelFailed'),
status: 'error', status: 'error',
}) });
);
} }
}, [currentQueueItemId, dispatch, t, trigger]); }, [currentQueueItemId, t, trigger]);
const isDisabled = useMemo(() => !isConnected || isNil(currentQueueItemId), [isConnected, currentQueueItemId]); const isDisabled = useMemo(() => !isConnected || isNil(currentQueueItemId), [isConnected, currentQueueItemId]);

View File

@ -1,5 +1,5 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useCancelQueueItemMutation } from 'services/api/endpoints/queue'; import { useCancelQueueItemMutation } from 'services/api/endpoints/queue';
@ -7,26 +7,23 @@ import { useCancelQueueItemMutation } from 'services/api/endpoints/queue';
export const useCancelQueueItem = (item_id: number) => { export const useCancelQueueItem = (item_id: number) => {
const isConnected = useAppSelector((s) => s.system.isConnected); const isConnected = useAppSelector((s) => s.system.isConnected);
const [trigger, { isLoading }] = useCancelQueueItemMutation(); const [trigger, { isLoading }] = useCancelQueueItemMutation();
const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const cancelQueueItem = useCallback(async () => { const cancelQueueItem = useCallback(async () => {
try { try {
await trigger(item_id).unwrap(); await trigger(item_id).unwrap();
dispatch( toast({
addToast({ id: 'QUEUE_CANCEL_SUCCEEDED',
title: t('queue.cancelSucceeded'), title: t('queue.cancelSucceeded'),
status: 'success', status: 'success',
}) });
);
} catch { } catch {
dispatch( toast({
addToast({ id: 'QUEUE_CANCEL_FAILED',
title: t('queue.cancelFailed'), title: t('queue.cancelFailed'),
status: 'error', status: 'error',
}) });
);
} }
}, [dispatch, item_id, t, trigger]); }, [item_id, t, trigger]);
return { cancelQueueItem, isLoading, isDisabled: !isConnected }; return { cancelQueueItem, isLoading, isDisabled: !isConnected };
}; };

View File

@ -1,12 +1,11 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useClearInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo'; import { useClearInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo';
export const useClearInvocationCache = () => { export const useClearInvocationCache = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const { data: cacheStatus } = useGetInvocationCacheStatusQuery(); const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
const isConnected = useAppSelector((s) => s.system.isConnected); const isConnected = useAppSelector((s) => s.system.isConnected);
const [trigger, { isLoading }] = useClearInvocationCacheMutation({ const [trigger, { isLoading }] = useClearInvocationCacheMutation({
@ -22,21 +21,19 @@ export const useClearInvocationCache = () => {
try { try {
await trigger().unwrap(); await trigger().unwrap();
dispatch( toast({
addToast({ id: 'INVOCATION_CACHE_CLEAR_SUCCEEDED',
title: t('invocationCache.clearSucceeded'), title: t('invocationCache.clearSucceeded'),
status: 'success', status: 'success',
}) });
);
} catch { } catch {
dispatch( toast({
addToast({ id: 'INVOCATION_CACHE_CLEAR_FAILED',
title: t('invocationCache.clearFailed'), title: t('invocationCache.clearFailed'),
status: 'error', status: 'error',
}) });
);
} }
}, [isDisabled, trigger, dispatch, t]); }, [isDisabled, trigger, t]);
return { clearInvocationCache, isLoading, cacheStatus, isDisabled }; return { clearInvocationCache, isLoading, cacheStatus, isDisabled };
}; };

View File

@ -1,6 +1,6 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { listCursorChanged, listPriorityChanged } from 'features/queue/store/queueSlice'; import { listCursorChanged, listPriorityChanged } from 'features/queue/store/queueSlice';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useClearQueueMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue'; import { useClearQueueMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue';
@ -21,21 +21,19 @@ export const useClearQueue = () => {
try { try {
await trigger().unwrap(); await trigger().unwrap();
dispatch( toast({
addToast({ id: 'QUEUE_CLEAR_SUCCEEDED',
title: t('queue.clearSucceeded'), title: t('queue.clearSucceeded'),
status: 'success', status: 'success',
}) });
);
dispatch(listCursorChanged(undefined)); dispatch(listCursorChanged(undefined));
dispatch(listPriorityChanged(undefined)); dispatch(listPriorityChanged(undefined));
} catch { } catch {
dispatch( toast({
addToast({ id: 'QUEUE_CLEAR_FAILED',
title: t('queue.clearFailed'), title: t('queue.clearFailed'),
status: 'error', status: 'error',
}) });
);
} }
}, [queueStatus?.queue.total, trigger, dispatch, t]); }, [queueStatus?.queue.total, trigger, dispatch, t]);

View File

@ -1,12 +1,11 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDisableInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo'; import { useDisableInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo';
export const useDisableInvocationCache = () => { export const useDisableInvocationCache = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const { data: cacheStatus } = useGetInvocationCacheStatusQuery(); const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
const isConnected = useAppSelector((s) => s.system.isConnected); const isConnected = useAppSelector((s) => s.system.isConnected);
const [trigger, { isLoading }] = useDisableInvocationCacheMutation({ const [trigger, { isLoading }] = useDisableInvocationCacheMutation({
@ -25,21 +24,19 @@ export const useDisableInvocationCache = () => {
try { try {
await trigger().unwrap(); await trigger().unwrap();
dispatch( toast({
addToast({ id: 'INVOCATION_CACHE_DISABLE_SUCCEEDED',
title: t('invocationCache.disableSucceeded'), title: t('invocationCache.disableSucceeded'),
status: 'success', status: 'success',
}) });
);
} catch { } catch {
dispatch( toast({
addToast({ id: 'INVOCATION_CACHE_DISABLE_FAILED',
title: t('invocationCache.disableFailed'), title: t('invocationCache.disableFailed'),
status: 'error', status: 'error',
}) });
);
} }
}, [isDisabled, trigger, dispatch, t]); }, [isDisabled, trigger, t]);
return { disableInvocationCache, isLoading, cacheStatus, isDisabled }; return { disableInvocationCache, isLoading, cacheStatus, isDisabled };
}; };

View File

@ -1,12 +1,11 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useEnableInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo'; import { useEnableInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo';
export const useEnableInvocationCache = () => { export const useEnableInvocationCache = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const { data: cacheStatus } = useGetInvocationCacheStatusQuery(); const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
const isConnected = useAppSelector((s) => s.system.isConnected); const isConnected = useAppSelector((s) => s.system.isConnected);
const [trigger, { isLoading }] = useEnableInvocationCacheMutation({ const [trigger, { isLoading }] = useEnableInvocationCacheMutation({
@ -25,21 +24,19 @@ export const useEnableInvocationCache = () => {
try { try {
await trigger().unwrap(); await trigger().unwrap();
dispatch( toast({
addToast({ id: 'INVOCATION_CACHE_ENABLE_SUCCEEDED',
title: t('invocationCache.enableSucceeded'), title: t('invocationCache.enableSucceeded'),
status: 'success', status: 'success',
}) });
);
} catch { } catch {
dispatch( toast({
addToast({ id: 'INVOCATION_CACHE_ENABLE_FAILED',
title: t('invocationCache.enableFailed'), title: t('invocationCache.enableFailed'),
status: 'error', status: 'error',
}) });
);
} }
}, [isDisabled, trigger, dispatch, t]); }, [isDisabled, trigger, t]);
return { enableInvocationCache, isLoading, cacheStatus, isDisabled }; return { enableInvocationCache, isLoading, cacheStatus, isDisabled };
}; };

View File

@ -1,11 +1,10 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useGetQueueStatusQuery, usePauseProcessorMutation } from 'services/api/endpoints/queue'; import { useGetQueueStatusQuery, usePauseProcessorMutation } from 'services/api/endpoints/queue';
export const usePauseProcessor = () => { export const usePauseProcessor = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const isConnected = useAppSelector((s) => s.system.isConnected); const isConnected = useAppSelector((s) => s.system.isConnected);
const { data: queueStatus } = useGetQueueStatusQuery(); const { data: queueStatus } = useGetQueueStatusQuery();
@ -21,21 +20,19 @@ export const usePauseProcessor = () => {
} }
try { try {
await trigger().unwrap(); await trigger().unwrap();
dispatch( toast({
addToast({ id: 'PAUSE_SUCCEEDED',
title: t('queue.pauseSucceeded'), title: t('queue.pauseSucceeded'),
status: 'success', status: 'success',
}) });
);
} catch { } catch {
dispatch( toast({
addToast({ id: 'PAUSE_FAILED',
title: t('queue.pauseFailed'), title: t('queue.pauseFailed'),
status: 'error', status: 'error',
}) });
);
} }
}, [isStarted, trigger, dispatch, t]); }, [isStarted, trigger, t]);
const isDisabled = useMemo(() => !isConnected || !isStarted, [isConnected, isStarted]); const isDisabled = useMemo(() => !isConnected || !isStarted, [isConnected, isStarted]);

View File

@ -1,6 +1,6 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { listCursorChanged, listPriorityChanged } from 'features/queue/store/queueSlice'; import { listCursorChanged, listPriorityChanged } from 'features/queue/store/queueSlice';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useGetQueueStatusQuery, usePruneQueueMutation } from 'services/api/endpoints/queue'; import { useGetQueueStatusQuery, usePruneQueueMutation } from 'services/api/endpoints/queue';
@ -30,21 +30,19 @@ export const usePruneQueue = () => {
} }
try { try {
const data = await trigger().unwrap(); const data = await trigger().unwrap();
dispatch( toast({
addToast({ id: 'PRUNE_SUCCEEDED',
title: t('queue.pruneSucceeded', { item_count: data.deleted }), title: t('queue.pruneSucceeded', { item_count: data.deleted }),
status: 'success', status: 'success',
}) });
);
dispatch(listCursorChanged(undefined)); dispatch(listCursorChanged(undefined));
dispatch(listPriorityChanged(undefined)); dispatch(listPriorityChanged(undefined));
} catch { } catch {
dispatch( toast({
addToast({ id: 'PRUNE_FAILED',
title: t('queue.pruneFailed'), title: t('queue.pruneFailed'),
status: 'error', status: 'error',
}) });
);
} }
}, [finishedCount, trigger, dispatch, t]); }, [finishedCount, trigger, dispatch, t]);

View File

@ -1,11 +1,10 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useGetQueueStatusQuery, useResumeProcessorMutation } from 'services/api/endpoints/queue'; import { useGetQueueStatusQuery, useResumeProcessorMutation } from 'services/api/endpoints/queue';
export const useResumeProcessor = () => { export const useResumeProcessor = () => {
const dispatch = useAppDispatch();
const isConnected = useAppSelector((s) => s.system.isConnected); const isConnected = useAppSelector((s) => s.system.isConnected);
const { data: queueStatus } = useGetQueueStatusQuery(); const { data: queueStatus } = useGetQueueStatusQuery();
const { t } = useTranslation(); const { t } = useTranslation();
@ -21,21 +20,19 @@ export const useResumeProcessor = () => {
} }
try { try {
await trigger().unwrap(); await trigger().unwrap();
dispatch( toast({
addToast({ id: 'PROCESSOR_RESUMED',
title: t('queue.resumeSucceeded'), title: t('queue.resumeSucceeded'),
status: 'success', status: 'success',
}) });
);
} catch { } catch {
dispatch( toast({
addToast({ id: 'PROCESSOR_RESUME_FAILED',
title: t('queue.resumeFailed'), title: t('queue.resumeFailed'),
status: 'error', status: 'error',
}) });
);
} }
}, [isStarted, trigger, dispatch, t]); }, [isStarted, trigger, t]);
const isDisabled = useMemo(() => !isConnected || isStarted, [isConnected, isStarted]); const isDisabled = useMemo(() => !isConnected || isStarted, [isConnected, isStarted]);

View File

@ -1,7 +1,7 @@
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { resetCanvas } from 'features/canvas/store/canvasSlice'; import { resetCanvas } from 'features/canvas/store/canvasSlice';
import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice'; import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useClearIntermediatesMutation, useGetIntermediatesCountQuery } from 'services/api/endpoints/images'; import { useClearIntermediatesMutation, useGetIntermediatesCountQuery } from 'services/api/endpoints/images';
@ -42,20 +42,18 @@ export const useClearIntermediates = (shouldShowClearIntermediates: boolean): Us
.then((clearedCount) => { .then((clearedCount) => {
dispatch(controlAdaptersReset()); dispatch(controlAdaptersReset());
dispatch(resetCanvas()); dispatch(resetCanvas());
dispatch( toast({
addToast({ id: 'INTERMEDIATES_CLEARED',
title: t('settings.intermediatesCleared', { count: clearedCount }), title: t('settings.intermediatesCleared', { count: clearedCount }),
status: 'info', status: 'info',
}) });
);
}) })
.catch(() => { .catch(() => {
dispatch( toast({
addToast({ id: 'INTERMEDIATES_CLEAR_FAILED',
title: t('settings.intermediatesClearedFailed'), title: t('settings.intermediatesClearedFailed'),
status: 'error', status: 'error',
}) });
);
}); });
}, [t, _clearIntermediates, dispatch, hasPendingItems]); }, [t, _clearIntermediates, dispatch, hasPendingItems]);

View File

@ -1,11 +1,7 @@
import type { UseToastOptions } from '@invoke-ai/ui-library';
import type { PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice, isAnyOf } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store'; import type { PersistConfig, RootState } from 'app/store/store';
import { calculateStepPercentage } from 'features/system/util/calculateStepPercentage'; import { calculateStepPercentage } from 'features/system/util/calculateStepPercentage';
import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next';
import { startCase } from 'lodash-es';
import type { LogLevelName } from 'roarr'; import type { LogLevelName } from 'roarr';
import { import {
socketConnected, socketConnected,
@ -13,13 +9,10 @@ import {
socketGeneratorProgress, socketGeneratorProgress,
socketGraphExecutionStateComplete, socketGraphExecutionStateComplete,
socketInvocationComplete, socketInvocationComplete,
socketInvocationError,
socketInvocationRetrievalError,
socketInvocationStarted, socketInvocationStarted,
socketModelLoadCompleted, socketModelLoadCompleted,
socketModelLoadStarted, socketModelLoadStarted,
socketQueueItemStatusChanged, socketQueueItemStatusChanged,
socketSessionRetrievalError,
} from 'services/events/actions'; } from 'services/events/actions';
import type { Language, SystemState } from './types'; import type { Language, SystemState } from './types';
@ -29,7 +22,6 @@ const initialSystemState: SystemState = {
isConnected: false, isConnected: false,
shouldConfirmOnDelete: true, shouldConfirmOnDelete: true,
enableImageDebugging: false, enableImageDebugging: false,
toastQueue: [],
denoiseProgress: null, denoiseProgress: null,
shouldAntialiasProgressImage: false, shouldAntialiasProgressImage: false,
consoleLogLevel: 'debug', consoleLogLevel: 'debug',
@ -51,12 +43,6 @@ export const systemSlice = createSlice({
setEnableImageDebugging: (state, action: PayloadAction<boolean>) => { setEnableImageDebugging: (state, action: PayloadAction<boolean>) => {
state.enableImageDebugging = action.payload; state.enableImageDebugging = action.payload;
}, },
addToast: (state, action: PayloadAction<UseToastOptions>) => {
state.toastQueue.push(action.payload);
},
clearToastQueue: (state) => {
state.toastQueue = [];
},
consoleLogLevelChanged: (state, action: PayloadAction<LogLevelName>) => { consoleLogLevelChanged: (state, action: PayloadAction<LogLevelName>) => {
state.consoleLogLevel = action.payload; state.consoleLogLevel = action.payload;
}, },
@ -162,29 +148,12 @@ export const systemSlice = createSlice({
state.denoiseProgress = null; state.denoiseProgress = null;
} }
}); });
// *** Matchers - must be after all cases ***
/**
* Any server error
*/
builder.addMatcher(isAnyServerError, (state, action) => {
state.toastQueue.push(
makeToast({
title: t('toast.serverError'),
status: 'error',
description: startCase(action.payload.data.error_type),
})
);
});
}, },
}); });
export const { export const {
setShouldConfirmOnDelete, setShouldConfirmOnDelete,
setEnableImageDebugging, setEnableImageDebugging,
addToast,
clearToastQueue,
consoleLogLevelChanged, consoleLogLevelChanged,
shouldLogToConsoleChanged, shouldLogToConsoleChanged,
shouldAntialiasProgressImageChanged, shouldAntialiasProgressImageChanged,
@ -194,8 +163,6 @@ export const {
setShouldEnableInformationalPopovers, setShouldEnableInformationalPopovers,
} = systemSlice.actions; } = systemSlice.actions;
const isAnyServerError = isAnyOf(socketInvocationError, socketSessionRetrievalError, socketInvocationRetrievalError);
export const selectSystemSlice = (state: RootState) => state.system; export const selectSystemSlice = (state: RootState) => state.system;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */

View File

@ -1,4 +1,3 @@
import type { UseToastOptions } from '@invoke-ai/ui-library';
import type { LogLevel } from 'app/logging/logger'; import type { LogLevel } from 'app/logging/logger';
import type { ProgressImage } from 'services/events/types'; import type { ProgressImage } from 'services/events/types';
import { z } from 'zod'; import { z } from 'zod';
@ -47,7 +46,6 @@ export interface SystemState {
isConnected: boolean; isConnected: boolean;
shouldConfirmOnDelete: boolean; shouldConfirmOnDelete: boolean;
enableImageDebugging: boolean; enableImageDebugging: boolean;
toastQueue: UseToastOptions[];
denoiseProgress: DenoiseProgress | null; denoiseProgress: DenoiseProgress | null;
consoleLogLevel: LogLevel; consoleLogLevel: LogLevel;
shouldLogToConsole: boolean; shouldLogToConsole: boolean;

View File

@ -1,20 +0,0 @@
import type { UseToastOptions } from '@invoke-ai/ui-library';
export type MakeToastArg = string | UseToastOptions;
/**
* Makes a toast from a string or a UseToastOptions object.
* If a string is passed, the toast will have the status 'info' and will be closable with a duration of 2500ms.
*/
export const makeToast = (arg: MakeToastArg): UseToastOptions => {
if (typeof arg === 'string') {
return {
title: arg,
status: 'info',
isClosable: true,
duration: 2500,
};
}
return { status: 'info', isClosable: true, duration: 2500, ...arg };
};

View File

@ -2,8 +2,7 @@ import { ConfirmationAlertDialog, Flex, Text, useDisclosure } from '@invoke-ai/u
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
import { workflowModeChanged } from 'features/nodes/store/workflowSlice'; import { workflowModeChanged } from 'features/nodes/store/workflowSlice';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -21,14 +20,11 @@ export const NewWorkflowConfirmationAlertDialog = memo((props: Props) => {
dispatch(nodeEditorReset()); dispatch(nodeEditorReset());
dispatch(workflowModeChanged('edit')); dispatch(workflowModeChanged('edit'));
dispatch( toast({
addToast( id: 'NEW_WORKFLOW_CREATED',
makeToast({ title: t('workflows.newWorkflowCreated'),
title: t('workflows.newWorkflowCreated'), status: 'success',
status: 'success', });
})
)
);
onClose(); onClose();
}, [dispatch, onClose, t]); }, [dispatch, onClose, t]);

View File

@ -1,5 +1,4 @@
import { useToast } from '@invoke-ai/ui-library'; import { toast } from 'features/toast/toast';
import { useAppToaster } from 'app/components/Toaster';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDeleteWorkflowMutation, workflowsApi } from 'services/api/endpoints/workflows'; import { useDeleteWorkflowMutation, workflowsApi } from 'services/api/endpoints/workflows';
@ -17,8 +16,6 @@ type UseDeleteLibraryWorkflowReturn = {
type UseDeleteLibraryWorkflow = (arg: UseDeleteLibraryWorkflowOptions) => UseDeleteLibraryWorkflowReturn; type UseDeleteLibraryWorkflow = (arg: UseDeleteLibraryWorkflowOptions) => UseDeleteLibraryWorkflowReturn;
export const useDeleteLibraryWorkflow: UseDeleteLibraryWorkflow = ({ onSuccess, onError }) => { export const useDeleteLibraryWorkflow: UseDeleteLibraryWorkflow = ({ onSuccess, onError }) => {
const toaster = useAppToaster();
const toast = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const [_deleteWorkflow, deleteWorkflowResult] = useDeleteWorkflowMutation(); const [_deleteWorkflow, deleteWorkflowResult] = useDeleteWorkflowMutation();
@ -26,21 +23,21 @@ export const useDeleteLibraryWorkflow: UseDeleteLibraryWorkflow = ({ onSuccess,
async (workflow_id: string) => { async (workflow_id: string) => {
try { try {
await _deleteWorkflow(workflow_id).unwrap(); await _deleteWorkflow(workflow_id).unwrap();
toaster({ toast({
id: 'WORKFLOW_DELETED',
title: t('toast.workflowDeleted'), title: t('toast.workflowDeleted'),
}); });
onSuccess && onSuccess(); onSuccess && onSuccess();
} catch { } catch {
if (!toast.isActive(`auth-error-toast-${workflowsApi.endpoints.deleteWorkflow.name}`)) { toast({
toaster({ id: `AUTH_ERROR_TOAST_${workflowsApi.endpoints.deleteWorkflow.name}`,
title: t('toast.problemDeletingWorkflow'), title: t('toast.problemDeletingWorkflow'),
status: 'error', status: 'error',
}); });
}
onError && onError(); onError && onError();
} }
}, },
[_deleteWorkflow, toaster, t, onSuccess, onError, toast] [_deleteWorkflow, t, onSuccess, onError]
); );
return { deleteWorkflow, deleteWorkflowResult }; return { deleteWorkflow, deleteWorkflowResult };

View File

@ -1,6 +1,6 @@
import { useAppToaster } from 'app/components/Toaster';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { workflowLoadRequested } from 'features/nodes/store/actions'; import { workflowLoadRequested } from 'features/nodes/store/actions';
import { toast } from 'features/toast/toast';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useLazyGetImageWorkflowQuery } from 'services/api/endpoints/images'; import { useLazyGetImageWorkflowQuery } from 'services/api/endpoints/images';
@ -21,7 +21,6 @@ type UseGetAndLoadEmbeddedWorkflow = (
export const useGetAndLoadEmbeddedWorkflow: UseGetAndLoadEmbeddedWorkflow = ({ onSuccess, onError }) => { export const useGetAndLoadEmbeddedWorkflow: UseGetAndLoadEmbeddedWorkflow = ({ onSuccess, onError }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const toaster = useAppToaster();
const { t } = useTranslation(); const { t } = useTranslation();
const [_getAndLoadEmbeddedWorkflow, getAndLoadEmbeddedWorkflowResult] = useLazyGetImageWorkflowQuery(); const [_getAndLoadEmbeddedWorkflow, getAndLoadEmbeddedWorkflowResult] = useLazyGetImageWorkflowQuery();
const getAndLoadEmbeddedWorkflow = useCallback( const getAndLoadEmbeddedWorkflow = useCallback(
@ -33,20 +32,22 @@ export const useGetAndLoadEmbeddedWorkflow: UseGetAndLoadEmbeddedWorkflow = ({ o
// No toast - the listener for this action does that after the workflow is loaded // No toast - the listener for this action does that after the workflow is loaded
onSuccess && onSuccess(); onSuccess && onSuccess();
} else { } else {
toaster({ toast({
id: 'PROBLEM_RETRIEVING_WORKFLOW',
title: t('toast.problemRetrievingWorkflow'), title: t('toast.problemRetrievingWorkflow'),
status: 'error', status: 'error',
}); });
} }
} catch { } catch {
toaster({ toast({
id: 'PROBLEM_RETRIEVING_WORKFLOW',
title: t('toast.problemRetrievingWorkflow'), title: t('toast.problemRetrievingWorkflow'),
status: 'error', status: 'error',
}); });
onError && onError(); onError && onError();
} }
}, },
[_getAndLoadEmbeddedWorkflow, dispatch, onSuccess, toaster, t, onError] [_getAndLoadEmbeddedWorkflow, dispatch, onSuccess, t, onError]
); );
return { getAndLoadEmbeddedWorkflow, getAndLoadEmbeddedWorkflowResult }; return { getAndLoadEmbeddedWorkflow, getAndLoadEmbeddedWorkflowResult };

View File

@ -1,5 +1,4 @@
import { useToast } from '@invoke-ai/ui-library'; import { useToast } from '@invoke-ai/ui-library';
import { useAppToaster } from 'app/components/Toaster';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { workflowLoadRequested } from 'features/nodes/store/actions'; import { workflowLoadRequested } from 'features/nodes/store/actions';
import { useCallback } from 'react'; import { useCallback } from 'react';
@ -20,7 +19,6 @@ type UseGetAndLoadLibraryWorkflow = (arg: UseGetAndLoadLibraryWorkflowOptions) =
export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = ({ onSuccess, onError }) => { export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = ({ onSuccess, onError }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const toaster = useAppToaster();
const toast = useToast(); const toast = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const [_getAndLoadWorkflow, getAndLoadWorkflowResult] = useLazyGetWorkflowQuery(); const [_getAndLoadWorkflow, getAndLoadWorkflowResult] = useLazyGetWorkflowQuery();
@ -33,16 +31,15 @@ export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = ({ onS
// No toast - the listener for this action does that after the workflow is loaded // No toast - the listener for this action does that after the workflow is loaded
onSuccess && onSuccess(); onSuccess && onSuccess();
} catch { } catch {
if (!toast.isActive(`auth-error-toast-${workflowsApi.endpoints.getWorkflow.name}`)) { toast({
toaster({ id: `AUTH_ERROR_TOAST_${workflowsApi.endpoints.getWorkflow.name}`,
title: t('toast.problemRetrievingWorkflow'), title: t('toast.problemRetrievingWorkflow'),
status: 'error', status: 'error',
}); });
}
onError && onError(); onError && onError();
} }
}, },
[_getAndLoadWorkflow, dispatch, onSuccess, toaster, t, onError, toast] [_getAndLoadWorkflow, dispatch, onSuccess, t, onError, toast]
); );
return { getAndLoadWorkflow, getAndLoadWorkflowResult }; return { getAndLoadWorkflow, getAndLoadWorkflowResult };

View File

@ -1,8 +1,7 @@
import { useLogger } from 'app/logging/useLogger'; import { useLogger } from 'app/logging/useLogger';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { workflowLoadRequested } from 'features/nodes/store/actions'; import { workflowLoadRequested } from 'features/nodes/store/actions';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { workflowLoadedFromFile } from 'features/workflowLibrary/store/actions'; import { workflowLoadedFromFile } from 'features/workflowLibrary/store/actions';
import type { RefObject } from 'react'; import type { RefObject } from 'react';
import { useCallback } from 'react'; import { useCallback } from 'react';
@ -35,14 +34,11 @@ export const useLoadWorkflowFromFile: UseLoadWorkflowFromFile = ({ resetRef, onS
} catch (e) { } catch (e) {
// There was a problem reading the file // There was a problem reading the file
logger.error(t('nodes.unableToLoadWorkflow')); logger.error(t('nodes.unableToLoadWorkflow'));
dispatch( toast({
addToast( id: 'UNABLE_TO_LOAD_WORKFLOW',
makeToast({ title: t('nodes.unableToLoadWorkflow'),
title: t('nodes.unableToLoadWorkflow'), status: 'error',
status: 'error', });
})
)
);
reader.abort(); reader.abort();
} }
}; };

View File

@ -1,6 +1,6 @@
import type { Middleware, MiddlewareAPI } from '@reduxjs/toolkit'; import type { Middleware } from '@reduxjs/toolkit';
import { isRejectedWithValue } from '@reduxjs/toolkit'; import { isRejectedWithValue } from '@reduxjs/toolkit';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { z } from 'zod'; import { z } from 'zod';
@ -22,7 +22,7 @@ const zRejectedForbiddenAction = z.object({
.optional(), .optional(),
}); });
export const authToastMiddleware: Middleware = (api: MiddlewareAPI) => (next) => (action) => { export const authToastMiddleware: Middleware = () => (next) => (action) => {
if (isRejectedWithValue(action)) { if (isRejectedWithValue(action)) {
try { try {
const parsed = zRejectedForbiddenAction.parse(action); const parsed = zRejectedForbiddenAction.parse(action);
@ -32,16 +32,13 @@ export const authToastMiddleware: Middleware = (api: MiddlewareAPI) => (next) =>
return; return;
} }
const { dispatch } = api;
const customMessage = parsed.payload.data.detail !== 'Forbidden' ? parsed.payload.data.detail : undefined; const customMessage = parsed.payload.data.detail !== 'Forbidden' ? parsed.payload.data.detail : undefined;
dispatch( toast({
addToast({ id: `auth-error-toast-${endpointName}`,
id: `auth-error-toast-${endpointName}`, title: t('toast.somethingWentWrong'),
title: t('common.somethingWentWrong'), status: 'error',
status: 'error', description: customMessage,
description: customMessage, });
})
);
} catch (error) { } catch (error) {
// no-op // no-op
} }

View File

@ -4,7 +4,7 @@ import { getStore } from 'app/store/nanostores/store';
import type { JSONObject } from 'common/types'; import type { JSONObject } from 'common/types';
import type { BoardId } from 'features/gallery/store/types'; import type { BoardId } from 'features/gallery/store/types';
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES, IMAGE_LIMIT } from 'features/gallery/store/types'; import { ASSETS_CATEGORIES, IMAGE_CATEGORIES, IMAGE_LIMIT } from 'features/gallery/store/types';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { t } from 'i18next'; import { t } from 'i18next';
import { keyBy } from 'lodash-es'; import { keyBy } from 'lodash-es';
import type { components, paths } from 'services/api/schema'; import type { components, paths } from 'services/api/schema';
@ -206,13 +206,12 @@ export const imagesApi = api.injectEndpoints({
const { data } = await queryFulfilled; const { data } = await queryFulfilled;
if (data.deleted_images.length < imageDTOs.length) { if (data.deleted_images.length < imageDTOs.length) {
dispatch( toast({
addToast({ id: 'problem-deleting-images',
title: t('gallery.problemDeletingImages'), title: t('gallery.problemDeletingImages'),
description: t('gallery.problemDeletingImagesDesc'), description: t('gallery.problemDeletingImagesDesc'),
status: 'warning', status: 'warning',
}) });
);
} }
// convert to an object so we can access the successfully delete image DTOs by name // convert to an object so we can access the successfully delete image DTOs by name

View File

@ -1,4 +1,3 @@
import type { UseToastOptions } from '@invoke-ai/ui-library';
import type { EntityState } from '@reduxjs/toolkit'; import type { EntityState } from '@reduxjs/toolkit';
import type { components, paths } from 'services/api/schema'; import type { components, paths } from 'services/api/schema';
import type { O } from 'ts-toolbelt'; import type { O } from 'ts-toolbelt';
@ -200,7 +199,7 @@ type CanvasInitialImageAction = {
type ToastAction = { type ToastAction = {
type: 'TOAST'; type: 'TOAST';
toastOptions?: UseToastOptions; title?: string;
}; };
type AddToBatchAction = { type AddToBatchAction = {

View File

@ -2,8 +2,7 @@ import { $baseUrl } from 'app/store/nanostores/baseUrl';
import { $bulkDownloadId } from 'app/store/nanostores/bulkDownloadId'; import { $bulkDownloadId } from 'app/store/nanostores/bulkDownloadId';
import { $queueId } from 'app/store/nanostores/queueId'; import { $queueId } from 'app/store/nanostores/queueId';
import type { AppDispatch } from 'app/store/store'; import type { AppDispatch } from 'app/store/store';
import { addToast } from 'features/system/store/systemSlice'; import { toast } from 'features/toast/toast';
import { makeToast } from 'features/system/util/makeToast';
import { import {
socketBulkDownloadCompleted, socketBulkDownloadCompleted,
socketBulkDownloadFailed, socketBulkDownloadFailed,
@ -52,15 +51,12 @@ export const setEventListeners = (arg: SetEventListenersArg) => {
if (error && error.message) { if (error && error.message) {
const data: string | undefined = (error as unknown as { data: string | undefined }).data; const data: string | undefined = (error as unknown as { data: string | undefined }).data;
if (data === 'ERR_UNAUTHENTICATED') { if (data === 'ERR_UNAUTHENTICATED') {
dispatch( toast({
addToast( id: `connect-error-${error.message}`,
makeToast({ title: error.message,
title: error.message, status: 'error',
status: 'error', duration: 10000,
duration: 10000, });
})
)
);
} }
} }
}); });