diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index da8d69c91e..9c890304a4 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1073,8 +1073,9 @@ }, "toast": { "addedToBoard": "Added to board", - "baseModelChangedCleared_one": "Base model changed, cleared or disabled {{count}} incompatible submodel", - "baseModelChangedCleared_other": "Base model changed, cleared or disabled {{count}} incompatible submodels", + "baseModelChanged": "Base Model Changed", + "baseModelChangedCleared_one": "Cleared or disabled {{count}} incompatible submodel", + "baseModelChangedCleared_other": "Cleared or disabled {{count}} incompatible submodels", "canceled": "Processing Canceled", "canvasCopiedClipboard": "Canvas Copied to Clipboard", "canvasDownloaded": "Canvas Downloaded", @@ -1095,10 +1096,17 @@ "metadataLoadFailed": "Failed to load metadata", "modelAddedSimple": "Model Added to Queue", "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", - "parameterNotSet": "{{parameter}} not set", - "parameterSet": "{{parameter}} set", - "parametersNotSet": "Parameters Not Set", + "parameterSet": "Parameter Recalled", + "parameterSetDesc": "Recalled {{parameter}}", + "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", "problemCopyingCanvasDesc": "Unable to export base layer", "problemCopyingImage": "Unable to Copy Image", @@ -1118,11 +1126,13 @@ "sentToImageToImage": "Sent To Image To Image", "sentToUnifiedCanvas": "Sent to Unified Canvas", "serverError": "Server Error", + "sessionRef": "Session: {{sessionId}}", "setAsCanvasInitialImage": "Set as canvas initial image", "setCanvasInitialImage": "Set canvas initial image", "setControlImage": "Set as control image", "setInitialImage": "Set as initial image", "setNodeField": "Set as node field", + "somethingWentWrong": "Something Went Wrong", "uploadFailed": "Upload failed", "uploadFailedInvalidUploadDesc": "Must be single PNG or JPEG image", "uploadInitialImage": "Upload Initial Image", diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index 1ff093f348..2d878d96e7 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -25,7 +25,6 @@ import { useGetOpenAPISchemaQuery } from 'services/api/endpoints/appInfo'; import AppErrorBoundaryFallback from './AppErrorBoundaryFallback'; import PreselectedImage from './PreselectedImage'; -import Toaster from './Toaster'; const DEFAULT_CONFIG = {}; @@ -96,7 +95,6 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => { - ); diff --git a/invokeai/frontend/web/src/app/components/AppErrorBoundaryFallback.tsx b/invokeai/frontend/web/src/app/components/AppErrorBoundaryFallback.tsx index d2992a8cd9..7f1f825ba3 100644 --- a/invokeai/frontend/web/src/app/components/AppErrorBoundaryFallback.tsx +++ b/invokeai/frontend/web/src/app/components/AppErrorBoundaryFallback.tsx @@ -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 { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -11,16 +12,16 @@ type Props = { }; const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => { - const toast = useToast(); const { t } = useTranslation(); const handleCopy = useCallback(() => { const text = JSON.stringify(serializeError(error), null, 2); navigator.clipboard.writeText(`\`\`\`\n${text}\n\`\`\``); toast({ - title: 'Error Copied', + id: 'ERROR_COPIED', + title: t('toast.errorCopied'), }); - }, [error, toast]); + }, [error, t]); const url = useMemo( () => diff --git a/invokeai/frontend/web/src/app/components/Toaster.ts b/invokeai/frontend/web/src/app/components/Toaster.ts deleted file mode 100644 index c86fd5060d..0000000000 --- a/invokeai/frontend/web/src/app/components/Toaster.ts +++ /dev/null @@ -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); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 0c0c8ed2bc..77345be5d3 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -41,12 +41,10 @@ import { addGeneratorProgressEventListener } from 'app/store/middleware/listener import { addGraphExecutionStateCompleteEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketGraphExecutionStateComplete'; import { addInvocationCompleteEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete'; 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 { addModelInstallEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelInstall'; import { addModelLoadEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelLoad'; 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 { addSocketUnsubscribedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketUnsubscribed'; import { addStagingAreaImageSavedListener } from 'app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved'; @@ -114,8 +112,6 @@ addSocketSubscribedEventListener(startAppListening); addSocketUnsubscribedEventListener(startAppListening); addModelLoadEventListener(startAppListening); addModelInstallEventListener(startAppListening); -addSessionRetrievalErrorEventListener(startAppListening); -addInvocationRetrievalErrorEventListener(startAppListening); addSocketQueueItemStatusChangedEventListener(startAppListening); addBulkDownloadListeners(startAppListening); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts index ae26531722..9095a08431 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts @@ -8,7 +8,7 @@ import { resetCanvas, setInitialCanvasImage, } from 'features/canvas/store/canvasSlice'; -import { addToast } from 'features/system/store/systemSlice'; +import { toast } from 'features/toast/toast'; import { t } from 'i18next'; import { queueApi } from 'services/api/endpoints/queue'; @@ -30,22 +30,20 @@ export const addCommitStagingAreaImageListener = (startAppListening: AppStartLis req.reset(); if (canceled > 0) { log.debug(`Canceled ${canceled} canvas batches`); - dispatch( - addToast({ - title: t('queue.cancelBatchSucceeded'), - status: 'success', - }) - ); + toast({ + id: 'CANCEL_BATCH_SUCCEEDED', + title: t('queue.cancelBatchSucceeded'), + status: 'success', + }); } dispatch(canvasBatchIdsReset()); } catch { log.error('Failed to cancel canvas batches'); - dispatch( - addToast({ - title: t('queue.cancelBatchFailed'), - status: 'error', - }) - ); + toast({ + id: 'CANCEL_BATCH_FAILED', + title: t('queue.cancelBatchFailed'), + status: 'error', + }); } }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/batchEnqueued.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/batchEnqueued.ts index 68eda997b7..3f74bf9b61 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/batchEnqueued.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/batchEnqueued.ts @@ -1,8 +1,8 @@ import { logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { parseify } from 'common/util/serialize'; -import { toast } from 'common/util/toast'; import { zPydanticValidationError } from 'features/system/store/zodSchemas'; +import { toast } from 'features/toast/toast'; import { t } from 'i18next'; import { truncate, upperFirst } from 'lodash-es'; import { queueApi } from 'services/api/endpoints/queue'; @@ -16,18 +16,15 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) = const arg = action.meta.arg.originalArgs; logger('queue').debug({ enqueueResult: parseify(response) }, 'Batch enqueued'); - if (!toast.isActive('batch-queued')) { - toast({ - id: 'batch-queued', - title: t('queue.batchQueued'), - description: t('queue.batchQueuedDesc', { - count: response.enqueued, - direction: arg.prepend ? t('queue.front') : t('queue.back'), - }), - duration: 1000, - status: 'success', - }); - } + toast({ + id: 'QUEUE_BATCH_SUCCEEDED', + title: t('queue.batchQueued'), + status: 'success', + description: t('queue.batchQueuedDesc', { + count: response.enqueued, + direction: arg.prepend ? t('queue.front') : t('queue.back'), + }), + }); }, }); @@ -40,9 +37,10 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) = if (!response) { toast({ + id: 'QUEUE_BATCH_FAILED', title: t('queue.batchFailedToQueue'), status: 'error', - description: 'Unknown Error', + description: t('common.unknownError'), }); logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue')); return; @@ -52,7 +50,7 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) = if (result.success) { result.data.data.detail.map((e) => { toast({ - id: 'batch-failed-to-queue', + id: 'QUEUE_BATCH_FAILED', title: truncate(upperFirst(e.msg), { length: 128 }), status: 'error', description: truncate( @@ -64,9 +62,10 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) = }); } else if (response.status !== 403) { toast({ + id: 'QUEUE_BATCH_FAILED', title: t('queue.batchFailedToQueue'), - description: t('common.unknownError'), status: 'error', + description: t('common.unknownError'), }); } logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue')); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/bulkDownload.tsx b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/bulkDownload.tsx index 38a0fd7911..51945081ea 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/bulkDownload.tsx +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/bulkDownload.tsx @@ -1,8 +1,7 @@ -import type { UseToastOptions } from '@invoke-ai/ui-library'; import { ExternalLink } from '@invoke-ai/ui-library'; import { logger } from 'app/logging/logger'; 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 { imagesApi } from 'services/api/endpoints/images'; import { @@ -28,7 +27,6 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) = // Show the response message if it exists, otherwise show the default message description: action.payload.response || t('gallery.bulkDownloadRequestedDesc'), 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. toast({ + id: 'BULK_DOWNLOAD_REQUEST_FAILED', title: t('gallery.bulkDownloadRequestFailed'), - status: 'success', - isClosable: true, + status: 'error', }); }, }); @@ -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 const url = `/api/v1/images/download/${bulk_download_item_name}`; - const toastOptions: UseToastOptions = { + toast({ id: bulk_download_item_name, title: t('gallery.bulkDownloadReady', 'Download ready'), status: 'success', @@ -77,14 +75,7 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) = /> ), 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 toastOptions: UseToastOptions = { + toast({ id: bulk_download_item_name, title: t('gallery.bulkDownloadFailed'), status: 'error', description: action.payload.data.error, duration: null, - isClosable: true, - }; - - if (toast.isActive(bulk_download_item_name)) { - toast.update(bulk_download_item_name, toastOptions); - } else { - toast(toastOptions); - } + }); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasCopiedToClipboard.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasCopiedToClipboard.ts index e1f4804d56..311dda3e2e 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasCopiedToClipboard.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasCopiedToClipboard.ts @@ -2,14 +2,14 @@ import { $logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { canvasCopiedToClipboard } from 'features/canvas/store/actions'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; -import { addToast } from 'features/system/store/systemSlice'; import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard'; +import { toast } from 'features/toast/toast'; import { t } from 'i18next'; export const addCanvasCopiedToClipboardListener = (startAppListening: AppStartListening) => { startAppListening({ actionCreator: canvasCopiedToClipboard, - effect: async (action, { dispatch, getState }) => { + effect: async (action, { getState }) => { const moduleLog = $logger.get().child({ namespace: 'canvasCopiedToClipboardListener' }); const state = getState(); @@ -19,22 +19,20 @@ export const addCanvasCopiedToClipboardListener = (startAppListening: AppStartLi copyBlobToClipboard(blob); } catch (err) { moduleLog.error(String(err)); - dispatch( - addToast({ - title: t('toast.problemCopyingCanvas'), - description: t('toast.problemCopyingCanvasDesc'), - status: 'error', - }) - ); + toast({ + id: 'CANVAS_COPY_FAILED', + title: t('toast.problemCopyingCanvas'), + description: t('toast.problemCopyingCanvasDesc'), + status: 'error', + }); return; } - dispatch( - addToast({ - title: t('toast.canvasCopiedClipboard'), - status: 'success', - }) - ); + toast({ + id: 'CANVAS_COPY_SUCCEEDED', + title: t('toast.canvasCopiedClipboard'), + status: 'success', + }); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasDownloadedAsImage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasDownloadedAsImage.ts index 5b8150bd20..71e616b9ea 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasDownloadedAsImage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasDownloadedAsImage.ts @@ -3,13 +3,13 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware' import { canvasDownloadedAsImage } from 'features/canvas/store/actions'; import { downloadBlob } from 'features/canvas/util/downloadBlob'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; -import { addToast } from 'features/system/store/systemSlice'; +import { toast } from 'features/toast/toast'; import { t } from 'i18next'; export const addCanvasDownloadedAsImageListener = (startAppListening: AppStartListening) => { startAppListening({ actionCreator: canvasDownloadedAsImage, - effect: async (action, { dispatch, getState }) => { + effect: async (action, { getState }) => { const moduleLog = $logger.get().child({ namespace: 'canvasSavedToGalleryListener' }); const state = getState(); @@ -18,18 +18,17 @@ export const addCanvasDownloadedAsImageListener = (startAppListening: AppStartLi blob = await getBaseLayerBlob(state); } catch (err) { moduleLog.error(String(err)); - dispatch( - addToast({ - title: t('toast.problemDownloadingCanvas'), - description: t('toast.problemDownloadingCanvasDesc'), - status: 'error', - }) - ); + toast({ + id: 'CANVAS_DOWNLOAD_FAILED', + title: t('toast.problemDownloadingCanvas'), + description: t('toast.problemDownloadingCanvasDesc'), + status: 'error', + }); return; } downloadBlob(blob, 'canvas.png'); - dispatch(addToast({ title: t('toast.canvasDownloaded'), status: 'success' })); + toast({ id: 'CANVAS_DOWNLOAD_SUCCEEDED', title: t('toast.canvasDownloaded'), status: 'success' }); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasImageToControlNet.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasImageToControlNet.ts index 55392ebff4..2aa1f52d6c 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasImageToControlNet.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasImageToControlNet.ts @@ -3,7 +3,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware' import { canvasImageToControlAdapter } from 'features/canvas/store/actions'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; 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 { imagesApi } from 'services/api/endpoints/images'; @@ -20,13 +20,12 @@ export const addCanvasImageToControlNetListener = (startAppListening: AppStartLi blob = await getBaseLayerBlob(state, true); } catch (err) { log.error(String(err)); - dispatch( - addToast({ - title: t('toast.problemSavingCanvas'), - description: t('toast.problemSavingCanvasDesc'), - status: 'error', - }) - ); + toast({ + id: 'PROBLEM_SAVING_CANVAS', + title: t('toast.problemSavingCanvas'), + description: t('toast.problemSavingCanvasDesc'), + status: 'error', + }); return; } @@ -43,7 +42,7 @@ export const addCanvasImageToControlNetListener = (startAppListening: AppStartLi crop_visible: false, postUploadAction: { type: 'TOAST', - toastOptions: { title: t('toast.canvasSentControlnetAssets') }, + title: t('toast.canvasSentControlnetAssets'), }, }) ).unwrap(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMaskSavedToGallery.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMaskSavedToGallery.ts index af0c3878fc..454342b997 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMaskSavedToGallery.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMaskSavedToGallery.ts @@ -2,7 +2,7 @@ import { logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { canvasMaskSavedToGallery } from 'features/canvas/store/actions'; 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 { imagesApi } from 'services/api/endpoints/images'; @@ -29,13 +29,12 @@ export const addCanvasMaskSavedToGalleryListener = (startAppListening: AppStartL if (!maskBlob) { log.error('Problem getting mask layer blob'); - dispatch( - addToast({ - title: t('toast.problemSavingMask'), - description: t('toast.problemSavingMaskDesc'), - status: 'error', - }) - ); + toast({ + id: 'PROBLEM_SAVING_MASK', + title: t('toast.problemSavingMask'), + description: t('toast.problemSavingMaskDesc'), + status: 'error', + }); return; } @@ -52,7 +51,7 @@ export const addCanvasMaskSavedToGalleryListener = (startAppListening: AppStartL crop_visible: true, postUploadAction: { type: 'TOAST', - toastOptions: { title: t('toast.maskSavedAssets') }, + title: t('toast.maskSavedAssets'), }, }) ); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMaskToControlNet.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMaskToControlNet.ts index 569b4badc7..2e6ca61d8a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMaskToControlNet.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMaskToControlNet.ts @@ -3,7 +3,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware' import { canvasMaskToControlAdapter } from 'features/canvas/store/actions'; import { getCanvasData } from 'features/canvas/util/getCanvasData'; 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 { imagesApi } from 'services/api/endpoints/images'; @@ -30,13 +30,12 @@ export const addCanvasMaskToControlNetListener = (startAppListening: AppStartLis if (!maskBlob) { log.error('Problem getting mask layer blob'); - dispatch( - addToast({ - title: t('toast.problemImportingMask'), - description: t('toast.problemImportingMaskDesc'), - status: 'error', - }) - ); + toast({ + id: 'PROBLEM_IMPORTING_MASK', + title: t('toast.problemImportingMask'), + description: t('toast.problemImportingMaskDesc'), + status: 'error', + }); return; } @@ -53,7 +52,7 @@ export const addCanvasMaskToControlNetListener = (startAppListening: AppStartLis crop_visible: false, postUploadAction: { type: 'TOAST', - toastOptions: { title: t('toast.maskSentControlnetAssets') }, + title: t('toast.maskSentControlnetAssets'), }, }) ).unwrap(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMerged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMerged.ts index 71b0e62b44..9ae6de2e76 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMerged.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMerged.ts @@ -4,7 +4,7 @@ import { canvasMerged } from 'features/canvas/store/actions'; import { $canvasBaseLayer } from 'features/canvas/store/canvasNanostore'; import { setMergedCanvas } from 'features/canvas/store/canvasSlice'; 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 { imagesApi } from 'services/api/endpoints/images'; @@ -17,13 +17,12 @@ export const addCanvasMergedListener = (startAppListening: AppStartListening) => if (!blob) { moduleLog.error('Problem getting base layer blob'); - dispatch( - addToast({ - title: t('toast.problemMergingCanvas'), - description: t('toast.problemMergingCanvasDesc'), - status: 'error', - }) - ); + toast({ + id: 'PROBLEM_MERGING_CANVAS', + title: t('toast.problemMergingCanvas'), + description: t('toast.problemMergingCanvasDesc'), + status: 'error', + }); return; } @@ -31,13 +30,12 @@ export const addCanvasMergedListener = (startAppListening: AppStartListening) => if (!canvasBaseLayer) { moduleLog.error('Problem getting canvas base layer'); - dispatch( - addToast({ - title: t('toast.problemMergingCanvas'), - description: t('toast.problemMergingCanvasDesc'), - status: 'error', - }) - ); + toast({ + id: 'PROBLEM_MERGING_CANVAS', + title: t('toast.problemMergingCanvas'), + description: t('toast.problemMergingCanvasDesc'), + status: 'error', + }); return; } @@ -54,7 +52,7 @@ export const addCanvasMergedListener = (startAppListening: AppStartListening) => is_intermediate: true, postUploadAction: { type: 'TOAST', - toastOptions: { title: t('toast.canvasMerged') }, + title: t('toast.canvasMerged'), }, }) ).unwrap(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery.ts index 7f456e9a68..71586b5f6e 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery.ts @@ -3,7 +3,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware' import { parseify } from 'common/util/serialize'; import { canvasSavedToGallery } from 'features/canvas/store/actions'; 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 { imagesApi } from 'services/api/endpoints/images'; @@ -19,13 +19,12 @@ export const addCanvasSavedToGalleryListener = (startAppListening: AppStartListe blob = await getBaseLayerBlob(state); } catch (err) { log.error(String(err)); - dispatch( - addToast({ - title: t('toast.problemSavingCanvas'), - description: t('toast.problemSavingCanvasDesc'), - status: 'error', - }) - ); + toast({ + id: 'CANVAS_SAVE_FAILED', + title: t('toast.problemSavingCanvas'), + description: t('toast.problemSavingCanvasDesc'), + status: 'error', + }); return; } @@ -42,7 +41,7 @@ export const addCanvasSavedToGalleryListener = (startAppListening: AppStartListe crop_visible: true, postUploadAction: { type: 'TOAST', - toastOptions: { title: t('toast.canvasSavedGallery') }, + title: t('toast.canvasSavedGallery'), }, metadata: { _canvas_objects: parseify(state.canvas.layerState.objects), diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlAdapterPreprocessor.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlAdapterPreprocessor.ts index 3dc8db93f9..8d67f509d8 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlAdapterPreprocessor.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlAdapterPreprocessor.ts @@ -14,7 +14,7 @@ import { } from 'features/controlLayers/store/controlLayersSlice'; import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters'; 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 { isEqual } from 'lodash-es'; import { getImageDTO } from 'services/api/endpoints/images'; @@ -174,12 +174,11 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni } } - dispatch( - addToast({ - title: t('queue.graphFailedToQueue'), - status: 'error', - }) - ); + toast({ + id: ToastID.GRAPH_QUEUE_FAILED, + title: t('queue.graphFailedToQueue'), + status: 'error', + }); } } finally { req.reset(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts index 0055866aa7..e6c83badcf 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts @@ -10,7 +10,7 @@ import { } from 'features/controlAdapters/store/controlAdaptersSlice'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; 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 { imagesApi } from 'services/api/endpoints/images'; import { queueApi } from 'services/api/endpoints/queue'; @@ -108,12 +108,11 @@ export const addControlNetImageProcessedListener = (startAppListening: AppStartL } } - dispatch( - addToast({ - title: t('queue.graphFailedToQueue'), - status: 'error', - }) - ); + toast({ + id: ToastID.GRAPH_QUEUE_FAILED, + title: t('queue.graphFailedToQueue'), + status: 'error', + }); } }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts index d5d74bf668..cd5304c32b 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -1,4 +1,3 @@ -import type { UseToastOptions } from '@invoke-ai/ui-library'; import { logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; @@ -14,7 +13,7 @@ import { } from 'features/controlLayers/store/controlLayersSlice'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; 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 { omit } from 'lodash-es'; import { boardsApi } from 'services/api/endpoints/boards'; @@ -42,16 +41,17 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis return; } - const DEFAULT_UPLOADED_TOAST: UseToastOptions = { + const DEFAULT_UPLOADED_TOAST = { + id: 'IMAGE_UPLOADED', title: t('toast.imageUploaded'), status: 'success', - }; + } as const; // default action - just upload and alert user if (postUploadAction?.type === 'TOAST') { - const { toastOptions } = postUploadAction; if (!autoAddBoardId || autoAddBoardId === 'none') { - dispatch(addToast({ ...DEFAULT_UPLOADED_TOAST, ...toastOptions })); + const title = postUploadAction.title || DEFAULT_UPLOADED_TOAST.title; + toast({ ...DEFAULT_UPLOADED_TOAST, title }); } else { // Add this image to the board dispatch( @@ -70,24 +70,20 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis ? `${t('toast.addedToBoard')} ${board.board_name}` : `${t('toast.addedToBoard')} ${autoAddBoardId}`; - dispatch( - addToast({ - ...DEFAULT_UPLOADED_TOAST, - description, - }) - ); + toast({ + ...DEFAULT_UPLOADED_TOAST, + description, + }); } return; } if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') { dispatch(setInitialCanvasImage(imageDTO, selectOptimalDimension(state))); - dispatch( - addToast({ - ...DEFAULT_UPLOADED_TOAST, - description: t('toast.setAsCanvasInitialImage'), - }) - ); + toast({ + ...DEFAULT_UPLOADED_TOAST, + description: t('toast.setAsCanvasInitialImage'), + }); return; } @@ -105,68 +101,56 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis controlImage: imageDTO.image_name, }) ); - dispatch( - addToast({ - ...DEFAULT_UPLOADED_TOAST, - description: t('toast.setControlImage'), - }) - ); + toast({ + ...DEFAULT_UPLOADED_TOAST, + description: t('toast.setControlImage'), + }); return; } if (postUploadAction?.type === 'SET_CA_LAYER_IMAGE') { const { layerId } = postUploadAction; dispatch(caLayerImageChanged({ layerId, imageDTO })); - dispatch( - addToast({ - ...DEFAULT_UPLOADED_TOAST, - description: t('toast.setControlImage'), - }) - ); + toast({ + ...DEFAULT_UPLOADED_TOAST, + description: t('toast.setControlImage'), + }); } if (postUploadAction?.type === 'SET_IPA_LAYER_IMAGE') { const { layerId } = postUploadAction; dispatch(ipaLayerImageChanged({ layerId, imageDTO })); - dispatch( - addToast({ - ...DEFAULT_UPLOADED_TOAST, - description: t('toast.setControlImage'), - }) - ); + toast({ + ...DEFAULT_UPLOADED_TOAST, + description: t('toast.setControlImage'), + }); } if (postUploadAction?.type === 'SET_RG_LAYER_IP_ADAPTER_IMAGE') { const { layerId, ipAdapterId } = postUploadAction; dispatch(rgLayerIPAdapterImageChanged({ layerId, ipAdapterId, imageDTO })); - dispatch( - addToast({ - ...DEFAULT_UPLOADED_TOAST, - description: t('toast.setControlImage'), - }) - ); + toast({ + ...DEFAULT_UPLOADED_TOAST, + description: t('toast.setControlImage'), + }); } if (postUploadAction?.type === 'SET_II_LAYER_IMAGE') { const { layerId } = postUploadAction; dispatch(iiLayerImageChanged({ layerId, imageDTO })); - dispatch( - addToast({ - ...DEFAULT_UPLOADED_TOAST, - description: t('toast.setControlImage'), - }) - ); + toast({ + ...DEFAULT_UPLOADED_TOAST, + description: t('toast.setControlImage'), + }); } if (postUploadAction?.type === 'SET_NODES_IMAGE') { const { nodeId, fieldName } = postUploadAction; dispatch(fieldImageValueChanged({ nodeId, fieldName, value: imageDTO })); - dispatch( - addToast({ - ...DEFAULT_UPLOADED_TOAST, - description: `${t('toast.setNodeField')} ${fieldName}`, - }) - ); + toast({ + ...DEFAULT_UPLOADED_TOAST, + description: `${t('toast.setNodeField')} ${fieldName}`, + }); return; } }, @@ -174,7 +158,7 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis startAppListening({ matcher: imagesApi.endpoints.uploadImage.matchRejected, - effect: (action, { dispatch }) => { + effect: (action) => { const log = logger('images'); const sanitizedData = { arg: { @@ -183,13 +167,11 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis }, }; log.error({ ...sanitizedData }, 'Image upload failed'); - dispatch( - addToast({ - title: t('toast.imageUploadFailed'), - description: action.error.message, - status: 'error', - }) - ); + toast({ + title: t('toast.imageUploadFailed'), + description: action.error.message, + status: 'error', + }); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts index bc049cf498..239a5b863d 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts @@ -8,8 +8,7 @@ import { loraRemoved } from 'features/lora/store/loraSlice'; import { modelSelected } from 'features/parameters/store/actions'; import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice'; import { zParameterModel } from 'features/parameters/types/parameterSchemas'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { t } from 'i18next'; import { forEach } from 'lodash-es'; @@ -60,16 +59,14 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) = }); if (modelsCleared > 0) { - dispatch( - addToast( - makeToast({ - title: t('toast.baseModelChangedCleared', { - count: modelsCleared, - }), - status: 'warning', - }) - ) - ); + toast({ + id: 'BASE_MODEL_CHANGED', + title: t('toast.baseModelChanged'), + description: t('toast.baseModelChangedCleared', { + count: modelsCleared, + }), + status: 'warning', + }); } } diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts index 61a978d576..81ecfaddf8 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts @@ -19,8 +19,7 @@ import { isParameterWidth, zParameterVAEModel, } from 'features/parameters/types/parameterSchemas'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast, ToastID } from 'features/toast/toast'; import { t } from 'i18next'; import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models'; 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' }) }); } }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationError.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationError.ts index ce26c4dd7d..c2d85395e9 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationError.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationError.ts @@ -3,6 +3,10 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware' import { deepClone } from 'common/util/deepClone'; import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState'; 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'; const log = logger('socketio'); @@ -12,7 +16,7 @@ export const addInvocationErrorEventListener = (startAppListening: AppStartListe actionCreator: socketInvocationError, effect: (action) => { 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]); if (nes) { nes.status = zNodeStatus.enum.FAILED; @@ -21,6 +25,30 @@ export const addInvocationErrorEventListener = (startAppListening: AppStartListe nes.progressImage = null; 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, + }), + }); + } }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationRetrievalError.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationRetrievalError.ts deleted file mode 100644 index 44da4c0ddb..0000000000 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationRetrievalError.ts +++ /dev/null @@ -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})`); - }, - }); -}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketSessionRetrievalError.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketSessionRetrievalError.ts deleted file mode 100644 index a1a497dc08..0000000000 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketSessionRetrievalError.ts +++ /dev/null @@ -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})`); - }, - }); -}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved.ts index 6816e25bc1..6c4c2a9df1 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved.ts @@ -1,6 +1,6 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; 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 { 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) { - dispatch( - addToast({ - title: t('toast.imageSavingFailed'), - description: (error as Error)?.message, - status: 'error', - }) - ); + toast({ + id: 'IMAGE_SAVE_FAILED', + title: t('toast.imageSavingFailed'), + description: (error as Error)?.message, + status: 'error', + }); } }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateAllNodesRequested.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateAllNodesRequested.ts index 05cc2f8e83..07df2a4f42 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateAllNodesRequested.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/updateAllNodesRequested.ts @@ -5,8 +5,7 @@ import { $templates, nodesChanged } from 'features/nodes/store/nodesSlice'; import { NodeUpdateError } from 'features/nodes/types/error'; import { isInvocationNode } from 'features/nodes/types/invocation'; import { getNeedsUpdate, updateNode } from 'features/nodes/util/node/nodeUpdate'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { t } from 'i18next'; export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartListening) => { @@ -50,24 +49,18 @@ export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartLi count: unableToUpdateCount, }) ); - dispatch( - addToast( - makeToast({ - title: t('nodes.unableToUpdateNodes', { - count: unableToUpdateCount, - }), - }) - ) - ); + toast({ + id: 'UNABLE_TO_UPDATE_NODES', + title: t('nodes.unableToUpdateNodes', { + count: unableToUpdateCount, + }), + }); } else { - dispatch( - addToast( - makeToast({ - title: t('nodes.allNodesUpdated'), - status: 'success', - }) - ) - ); + toast({ + id: 'ALL_NODES_UPDATED', + title: t('nodes.allNodesUpdated'), + status: 'success', + }); } }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/upscaleRequested.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/upscaleRequested.ts index ff5d5f24be..67d016244d 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/upscaleRequested.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/upscaleRequested.ts @@ -4,7 +4,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware' import { parseify } from 'common/util/serialize'; import { buildAdHocUpscaleGraph } from 'features/nodes/util/graph/buildAdHocUpscaleGraph'; 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 { queueApi } from 'services/api/endpoints/queue'; import type { BatchConfig, ImageDTO } from 'services/api/types'; @@ -29,12 +29,11 @@ export const addUpscaleRequestedListener = (startAppListening: AppStartListening { imageDTO }, t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge') // should never coalesce ); - dispatch( - addToast({ - title: t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge'), // should never coalesce - status: 'error', - }) - ); + toast({ + id: 'NOT_ALLOWED_TO_UPSCALE', + title: t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge'), // should never coalesce + status: 'error', + }); return; } @@ -65,12 +64,11 @@ export const addUpscaleRequestedListener = (startAppListening: AppStartListening if (error instanceof Object && 'status' in error && error.status === 403) { return; } else { - dispatch( - addToast({ - title: t('queue.graphFailedToQueue'), - status: 'error', - }) - ); + toast({ + id: ToastID.GRAPH_QUEUE_FAILED, + title: t('queue.graphFailedToQueue'), + status: 'error', + }); } } }, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested.ts index 9f9e70fc01..e6fc5a526a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested.ts @@ -8,8 +8,7 @@ import type { Templates } from 'features/nodes/store/types'; import { WorkflowMigrationError, WorkflowVersionError } from 'features/nodes/types/error'; import { graphToWorkflow } from 'features/nodes/util/workflow/graphToWorkflow'; import { validateWorkflow } from 'features/nodes/util/workflow/validateWorkflow'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { t } from 'i18next'; import { checkBoardAccess, checkImageAccess, checkModelAccess } from 'services/api/hooks/accessChecks'; import type { GraphAndWorkflowResponse, NonNullableGraph } from 'services/api/types'; @@ -49,23 +48,18 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList dispatch(workflowLoaded(workflow)); if (!warnings.length) { - dispatch( - addToast( - makeToast({ - title: t('toast.workflowLoaded'), - status: 'success', - }) - ) - ); + toast({ + id: 'WORKFLOW_LOADED', + title: t('toast.workflowLoaded'), + status: 'success', + }); } else { - dispatch( - addToast( - makeToast({ - title: t('toast.loadedWithWarnings'), - status: 'warning', - }) - ) - ); + toast({ + id: 'WORKFLOW_LOADED', + title: t('toast.loadedWithWarnings'), + status: 'warning', + }); + warnings.forEach(({ message, ...rest }) => { log.warn(rest, message); }); @@ -78,54 +72,42 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList if (e instanceof WorkflowVersionError) { // The workflow version was not recognized in the valid list of versions log.error({ error: parseify(e) }, e.message); - dispatch( - addToast( - makeToast({ - title: t('nodes.unableToValidateWorkflow'), - status: 'error', - description: e.message, - }) - ) - ); + toast({ + id: 'UNABLE_TO_VALIDATE_WORKFLOW', + title: t('nodes.unableToValidateWorkflow'), + status: 'error', + description: e.message, + }); } else if (e instanceof WorkflowMigrationError) { // There was a problem migrating the workflow to the latest version log.error({ error: parseify(e) }, e.message); - dispatch( - addToast( - makeToast({ - title: t('nodes.unableToValidateWorkflow'), - status: 'error', - description: e.message, - }) - ) - ); + toast({ + id: 'UNABLE_TO_VALIDATE_WORKFLOW', + title: t('nodes.unableToValidateWorkflow'), + status: 'error', + description: e.message, + }); } else if (e instanceof z.ZodError) { // There was a problem validating the workflow itself const { message } = fromZodError(e, { prefix: t('nodes.workflowValidation'), }); log.error({ error: parseify(e) }, message); - dispatch( - addToast( - makeToast({ - title: t('nodes.unableToValidateWorkflow'), - status: 'error', - description: message, - }) - ) - ); + toast({ + id: 'UNABLE_TO_VALIDATE_WORKFLOW', + title: t('nodes.unableToValidateWorkflow'), + status: 'error', + description: message, + }); } else { // Some other error occurred log.error({ error: parseify(e) }, t('nodes.unknownErrorValidatingWorkflow')); - dispatch( - addToast( - makeToast({ - title: t('nodes.unableToValidateWorkflow'), - status: 'error', - description: t('nodes.unknownErrorValidatingWorkflow'), - }) - ) - ); + toast({ + id: 'UNABLE_TO_VALIDATE_WORKFLOW', + title: t('nodes.unableToValidateWorkflow'), + status: 'error', + description: t('nodes.unknownErrorValidatingWorkflow'), + }); } } }, diff --git a/invokeai/frontend/web/src/common/hooks/useCopyImageToClipboard.ts b/invokeai/frontend/web/src/common/hooks/useCopyImageToClipboard.ts index ef9db44a9d..233b841034 100644 --- a/invokeai/frontend/web/src/common/hooks/useCopyImageToClipboard.ts +++ b/invokeai/frontend/web/src/common/hooks/useCopyImageToClipboard.ts @@ -1,11 +1,10 @@ -import { useAppToaster } from 'app/components/Toaster'; import { useImageUrlToBlob } from 'common/hooks/useImageUrlToBlob'; import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard'; +import { toast } from 'features/toast/toast'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; export const useCopyImageToClipboard = () => { - const toaster = useAppToaster(); const { t } = useTranslation(); const imageUrlToBlob = useImageUrlToBlob(); @@ -16,12 +15,11 @@ export const useCopyImageToClipboard = () => { const copyImageToClipboard = useCallback( async (image_url: string) => { if (!isClipboardAPIAvailable) { - toaster({ + toast({ + id: 'PROBLEM_COPYING_IMAGE', title: t('toast.problemCopyingImage'), description: "Your browser doesn't support the Clipboard API.", status: 'error', - duration: 2500, - isClosable: true, }); } try { @@ -33,23 +31,21 @@ export const useCopyImageToClipboard = () => { copyBlobToClipboard(blob); - toaster({ + toast({ + id: 'IMAGE_COPIED', title: t('toast.imageCopied'), status: 'success', - duration: 2500, - isClosable: true, }); } catch (err) { - toaster({ + toast({ + id: 'PROBLEM_COPYING_IMAGE', title: t('toast.problemCopyingImage'), description: String(err), status: 'error', - duration: 2500, - isClosable: true, }); } }, - [imageUrlToBlob, isClipboardAPIAvailable, t, toaster] + [imageUrlToBlob, isClipboardAPIAvailable, t] ); return { isClipboardAPIAvailable, copyImageToClipboard }; diff --git a/invokeai/frontend/web/src/common/hooks/useDownloadImage.ts b/invokeai/frontend/web/src/common/hooks/useDownloadImage.ts index 26a17e1d0c..ede247b9fb 100644 --- a/invokeai/frontend/web/src/common/hooks/useDownloadImage.ts +++ b/invokeai/frontend/web/src/common/hooks/useDownloadImage.ts @@ -1,13 +1,12 @@ import { useStore } from '@nanostores/react'; -import { useAppToaster } from 'app/components/Toaster'; import { $authToken } from 'app/store/nanostores/authToken'; import { useAppDispatch } from 'app/store/storeHooks'; import { imageDownloaded } from 'features/gallery/store/actions'; +import { toast } from 'features/toast/toast'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; export const useDownloadImage = () => { - const toaster = useAppToaster(); const { t } = useTranslation(); const dispatch = useAppDispatch(); const authToken = useStore($authToken); @@ -37,16 +36,15 @@ export const useDownloadImage = () => { window.URL.revokeObjectURL(url); dispatch(imageDownloaded()); } catch (err) { - toaster({ + toast({ + id: 'PROBLEM_DOWNLOADING_IMAGE', title: t('toast.problemDownloadingImage'), description: String(err), status: 'error', - duration: 2500, - isClosable: true, }); } }, - [t, toaster, dispatch, authToken] + [t, dispatch, authToken] ); return { downloadImage }; diff --git a/invokeai/frontend/web/src/common/hooks/useFullscreenDropzone.ts b/invokeai/frontend/web/src/common/hooks/useFullscreenDropzone.ts index 0334294e98..5b1bf1f5b3 100644 --- a/invokeai/frontend/web/src/common/hooks/useFullscreenDropzone.ts +++ b/invokeai/frontend/web/src/common/hooks/useFullscreenDropzone.ts @@ -1,6 +1,6 @@ -import { useAppToaster } from 'app/components/Toaster'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; +import { toast } from 'features/toast/toast'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { useCallback, useEffect, useState } from 'react'; import type { Accept, FileRejection } from 'react-dropzone'; @@ -26,7 +26,6 @@ const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (ac export const useFullscreenDropzone = () => { const { t } = useTranslation(); - const toaster = useAppToaster(); const postUploadAction = useAppSelector(selectPostUploadAction); const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId); const [isHandlingUpload, setIsHandlingUpload] = useState(false); @@ -37,13 +36,14 @@ export const useFullscreenDropzone = () => { (rejection: FileRejection) => { setIsHandlingUpload(true); - toaster({ + toast({ + id: 'UPLOAD_FAILED', title: t('toast.uploadFailed'), description: rejection.errors.map((error) => error.message).join('\n'), status: 'error', }); }, - [t, toaster] + [t] ); const fileAcceptedCallback = useCallback( @@ -62,7 +62,8 @@ export const useFullscreenDropzone = () => { const onDrop = useCallback( (acceptedFiles: Array, fileRejections: Array) => { if (fileRejections.length > 1) { - toaster({ + toast({ + id: 'UPLOAD_FAILED', title: t('toast.uploadFailed'), description: t('toast.uploadFailedInvalidUploadDesc'), status: 'error', @@ -78,7 +79,7 @@ export const useFullscreenDropzone = () => { fileAcceptedCallback(file); }); }, - [t, toaster, fileAcceptedCallback, fileRejectionCallback] + [t, fileAcceptedCallback, fileRejectionCallback] ); const onDragOver = useCallback(() => { diff --git a/invokeai/frontend/web/src/common/util/toast.ts b/invokeai/frontend/web/src/common/util/toast.ts deleted file mode 100644 index ac61a4a12d..0000000000 --- a/invokeai/frontend/web/src/common/util/toast.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createStandaloneToast, theme, TOAST_OPTIONS } from '@invoke-ai/ui-library'; - -export const { toast } = createStandaloneToast({ - theme: theme, - defaultOptions: TOAST_OPTIONS.defaultOptions, -}); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx index f5063ea717..b3119aa8fa 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx @@ -1,6 +1,5 @@ import { Flex, MenuDivider, MenuItem, Spinner } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; -import { useAppToaster } from 'app/components/Toaster'; import { $customStarUI } from 'app/store/nanostores/customStarUI'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; 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 { selectOptimalDimension } from 'features/parameters/store/generationSlice'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; +import { toast } from 'features/toast/toast'; import { setActiveTab } from 'features/ui/store/uiSlice'; import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow'; import { size } from 'lodash-es'; @@ -46,7 +46,6 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { const optimalDimension = useAppSelector(selectOptimalDimension); const dispatch = useAppDispatch(); const { t } = useTranslation(); - const toaster = useAppToaster(); const isCanvasEnabled = useFeatureStatus('canvas'); const customStarUi = useStore($customStarUI); const { downloadImage } = useDownloadImage(); @@ -86,13 +85,12 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { }); dispatch(setInitialCanvasImage(imageDTO, optimalDimension)); - toaster({ + toast({ + id: 'SENT_TO_CANVAS', title: t('toast.sentToUnifiedCanvas'), status: 'success', - duration: 2500, - isClosable: true, }); - }, [dispatch, imageDTO, t, toaster, optimalDimension]); + }, [dispatch, imageDTO, t, optimalDimension]); const handleChangeBoard = useCallback(() => { dispatch(imagesToChangeSelected([imageDTO])); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ToggleMetadataViewerButton.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ToggleMetadataViewerButton.tsx index 4bf55116db..df3fbe2765 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ToggleMetadataViewerButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ToggleMetadataViewerButton.tsx @@ -1,6 +1,5 @@ import { IconButton } from '@invoke-ai/ui-library'; import { skipToken } from '@reduxjs/toolkit/query'; -import { useAppToaster } from 'app/components/Toaster'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors'; import { setShouldShowImageDetails } from 'features/ui/store/uiSlice'; @@ -14,7 +13,6 @@ export const ToggleMetadataViewerButton = memo(() => { const dispatch = useAppDispatch(); const shouldShowImageDetails = useAppSelector((s) => s.ui.shouldShowImageDetails); const lastSelectedImage = useAppSelector(selectLastSelectedImage); - const toaster = useAppToaster(); const { t } = useTranslation(); const { currentData: imageDTO } = useGetImageDTOQuery(lastSelectedImage?.image_name ?? skipToken); @@ -24,7 +22,7 @@ export const ToggleMetadataViewerButton = memo(() => { [dispatch, shouldShowImageDetails] ); - useHotkeys('i', toggleMetadataViewer, { enabled: Boolean(imageDTO) }, [imageDTO, shouldShowImageDetails, toaster]); + useHotkeys('i', toggleMetadataViewer, { enabled: Boolean(imageDTO) }, [imageDTO, shouldShowImageDetails]); return ( = async (layers) => { return `${layers.length} ${t('controlLayers.layers', { count: layers.length })}`; }; -const parameterSetToast = (parameter: string, description?: string) => { +const parameterSetToast = (parameter: string) => { toast({ - title: t('toast.parameterSet', { parameter }), - description, + id: ToastID.PARAMETER_SET, + title: t('toast.parameterSet'), + description: t('toast.parameterSetDesc', { parameter }), status: 'info', - duration: 2500, - isClosable: true, }); }; -const parameterNotSetToast = (parameter: string, description?: string) => { +const parameterNotSetToast = (parameter: string, message?: string) => { toast({ - title: t('toast.parameterNotSet', { parameter }), - description, + id: 'PARAMETER_NOT_SET', + title: t('toast.parameterNotSet'), + description: message + ? t('toast.parameterNotSetDescWithMessage', { parameter, message }) + : t('toast.parameterNotSetDesc', { parameter }), status: 'warning', - duration: 2500, - isClosable: true, }); }; @@ -458,7 +458,18 @@ export const parseAndRecallAllMetadata = async ( }); }) ); + 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', + }); } }; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceForm.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceForm.tsx index 184429478e..b3c456494f 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceForm.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceForm.tsx @@ -1,7 +1,5 @@ import { Button, Flex, FormControl, FormErrorMessage, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library'; -import { useAppDispatch } from 'app/store/storeHooks'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast, ToastID } from 'features/toast/toast'; import type { ChangeEventHandler } from 'react'; import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -14,7 +12,6 @@ export const HuggingFaceForm = () => { const [displayResults, setDisplayResults] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const { t } = useTranslation(); - const dispatch = useAppDispatch(); const [_getHuggingFaceModels, { isLoading, data }] = useLazyGetHuggingFaceModelsQuery(); const [installModel] = useInstallModelMutation(); @@ -24,29 +21,23 @@ export const HuggingFaceForm = () => { installModel({ source }) .unwrap() .then((_) => { - dispatch( - addToast( - makeToast({ - title: t('toast.modelAddedSimple'), - status: 'success', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUED, + title: t('toast.modelAddedSimple'), + status: 'success', + }); }) .catch((error) => { if (error) { - dispatch( - addToast( - makeToast({ - title: `${error.data.detail} `, - status: 'error', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUE_FAILED, + title: `${error.data.detail} `, + status: 'error', + }); } }); }, - [installModel, dispatch, t] + [installModel, t] ); const getModels = useCallback(async () => { diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResultItem.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResultItem.tsx index 1595a61147..225782aa3d 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResultItem.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResultItem.tsx @@ -1,7 +1,5 @@ import { Flex, IconButton, Text } from '@invoke-ai/ui-library'; -import { useAppDispatch } from 'app/store/storeHooks'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast, ToastID } from 'features/toast/toast'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; @@ -12,7 +10,6 @@ type Props = { }; export const HuggingFaceResultItem = ({ result }: Props) => { const { t } = useTranslation(); - const dispatch = useAppDispatch(); const [installModel] = useInstallModelMutation(); @@ -20,28 +17,22 @@ export const HuggingFaceResultItem = ({ result }: Props) => { installModel({ source: result }) .unwrap() .then((_) => { - dispatch( - addToast( - makeToast({ - title: t('toast.modelAddedSimple'), - status: 'success', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUED, + title: t('toast.modelAddedSimple'), + status: 'success', + }); }) .catch((error) => { if (error) { - dispatch( - addToast( - makeToast({ - title: `${error.data.detail} `, - status: 'error', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUE_FAILED, + title: `${error.data.detail} `, + status: 'error', + }); } }); - }, [installModel, result, dispatch, t]); + }, [installModel, result, t]); return ( diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResults.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResults.tsx index 8144accf3f..3d78e76b83 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResults.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceResults.tsx @@ -8,10 +8,8 @@ import { InputGroup, InputRightElement, } from '@invoke-ai/ui-library'; -import { useAppDispatch } from 'app/store/storeHooks'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast, ToastID } from 'features/toast/toast'; import type { ChangeEventHandler } from 'react'; import { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -27,7 +25,6 @@ type HuggingFaceResultsProps = { export const HuggingFaceResults = ({ results }: HuggingFaceResultsProps) => { const { t } = useTranslation(); const [searchTerm, setSearchTerm] = useState(''); - const dispatch = useAppDispatch(); const [installModel] = useInstallModelMutation(); @@ -51,29 +48,23 @@ export const HuggingFaceResults = ({ results }: HuggingFaceResultsProps) => { installModel({ source: result }) .unwrap() .then((_) => { - dispatch( - addToast( - makeToast({ - title: t('toast.modelAddedSimple'), - status: 'success', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUED, + title: t('toast.modelAddedSimple'), + status: 'success', + }); }) .catch((error) => { if (error) { - dispatch( - addToast( - makeToast({ - title: `${error.data.detail} `, - status: 'error', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUE_FAILED, + title: `${error.data.detail} `, + status: 'error', + }); } }); } - }, [filteredResults, installModel, dispatch, t]); + }, [filteredResults, installModel, t]); return ( <> diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/InstallModelForm.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/InstallModelForm.tsx index 282e07ee27..8f1a6c38b8 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/InstallModelForm.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/InstallModelForm.tsx @@ -1,7 +1,5 @@ import { Button, Checkbox, Flex, FormControl, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library'; -import { useAppDispatch } from 'app/store/storeHooks'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast, ToastID } from 'features/toast/toast'; import { t } from 'i18next'; import { useCallback } from 'react'; import type { SubmitHandler } from 'react-hook-form'; @@ -14,8 +12,6 @@ type SimpleImportModelConfig = { }; export const InstallModelForm = () => { - const dispatch = useAppDispatch(); - const [installModel, { isLoading }] = useInstallModelMutation(); const { register, handleSubmit, formState, reset } = useForm({ @@ -35,31 +31,25 @@ export const InstallModelForm = () => { installModel({ source: values.location, inplace: values.inplace }) .unwrap() .then((_) => { - dispatch( - addToast( - makeToast({ - title: t('toast.modelAddedSimple'), - status: 'success', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUED, + title: t('toast.modelAddedSimple'), + status: 'success', + }); reset(undefined, { keepValues: true }); }) .catch((error) => { reset(undefined, { keepValues: true }); if (error) { - dispatch( - addToast( - makeToast({ - title: `${error.data.detail} `, - status: 'error', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUE_FAILED, + title: `${error.data.detail} `, + status: 'error', + }); } }); }, - [dispatch, reset, installModel] + [reset, installModel] ); return ( diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx index 5db2743669..b3544af5b3 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx @@ -1,8 +1,6 @@ import { Box, Button, Flex, Heading } from '@invoke-ai/ui-library'; -import { useAppDispatch } from 'app/store/storeHooks'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { t } from 'i18next'; import { useCallback, useMemo } from 'react'; import { useListModelInstallsQuery, usePruneCompletedModelInstallsMutation } from 'services/api/endpoints/models'; @@ -10,8 +8,6 @@ import { useListModelInstallsQuery, usePruneCompletedModelInstallsMutation } fro import { ModelInstallQueueItem } from './ModelInstallQueueItem'; export const ModelInstallQueue = () => { - const dispatch = useAppDispatch(); - const { data } = useListModelInstallsQuery(); const [_pruneCompletedModelInstalls] = usePruneCompletedModelInstallsMutation(); @@ -20,28 +16,22 @@ export const ModelInstallQueue = () => { _pruneCompletedModelInstalls() .unwrap() .then((_) => { - dispatch( - addToast( - makeToast({ - title: t('toast.prunedQueue'), - status: 'success', - }) - ) - ); + toast({ + id: 'MODEL_INSTALL_QUEUE_PRUNED', + title: t('toast.prunedQueue'), + status: 'success', + }); }) .catch((error) => { if (error) { - dispatch( - addToast( - makeToast({ - title: `${error.data.detail} `, - status: 'error', - }) - ) - ); + toast({ + id: 'MODEL_INSTALL_QUEUE_PRUNE_FAILED', + title: `${error.data.detail} `, + status: 'error', + }); } }); - }, [_pruneCompletedModelInstalls, dispatch]); + }, [_pruneCompletedModelInstalls]); const pruneAvailable = useMemo(() => { return data?.some( diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueueItem.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueueItem.tsx index d1fc600510..82a28b2d75 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueueItem.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueueItem.tsx @@ -1,7 +1,5 @@ import { Flex, IconButton, Progress, Text, Tooltip } from '@invoke-ai/ui-library'; -import { useAppDispatch } from 'app/store/storeHooks'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { t } from 'i18next'; import { isNil } from 'lodash-es'; import { useCallback, useMemo } from 'react'; @@ -29,7 +27,6 @@ const formatBytes = (bytes: number) => { export const ModelInstallQueueItem = (props: ModelListItemProps) => { const { installJob } = props; - const dispatch = useAppDispatch(); const [deleteImportModel] = useCancelModelInstallMutation(); @@ -37,28 +34,22 @@ export const ModelInstallQueueItem = (props: ModelListItemProps) => { deleteImportModel(installJob.id) .unwrap() .then((_) => { - dispatch( - addToast( - makeToast({ - title: t('toast.modelImportCanceled'), - status: 'success', - }) - ) - ); + toast({ + id: 'MODEL_INSTALL_CANCELED', + title: t('toast.modelImportCanceled'), + status: 'success', + }); }) .catch((error) => { if (error) { - dispatch( - addToast( - makeToast({ - title: `${error.data.detail} `, - status: 'error', - }) - ) - ); + toast({ + id: 'MODEL_INSTALL_CANCEL_FAILED', + title: `${error.data.detail} `, + status: 'error', + }); } }); - }, [deleteImportModel, installJob, dispatch]); + }, [deleteImportModel, installJob]); const sourceLocation = useMemo(() => { switch (installJob.source.type) { diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderResults.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderResults.tsx index 360d6c1403..67261730e5 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderResults.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ScanFolder/ScanFolderResults.tsx @@ -11,10 +11,8 @@ import { InputGroup, InputRightElement, } from '@invoke-ai/ui-library'; -import { useAppDispatch } from 'app/store/storeHooks'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast, ToastID } from 'features/toast/toast'; import type { ChangeEvent, ChangeEventHandler } from 'react'; import { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -30,7 +28,6 @@ type ScanModelResultsProps = { export const ScanModelsResults = ({ results }: ScanModelResultsProps) => { const { t } = useTranslation(); const [searchTerm, setSearchTerm] = useState(''); - const dispatch = useAppDispatch(); const [inplace, setInplace] = useState(true); const [installModel] = useInstallModelMutation(); @@ -61,58 +58,46 @@ export const ScanModelsResults = ({ results }: ScanModelResultsProps) => { installModel({ source: result.path, inplace }) .unwrap() .then((_) => { - dispatch( - addToast( - makeToast({ - title: t('toast.modelAddedSimple'), - status: 'success', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUED, + title: t('toast.modelAddedSimple'), + status: 'success', + }); }) .catch((error) => { if (error) { - dispatch( - addToast( - makeToast({ - title: `${error.data.detail} `, - status: 'error', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUE_FAILED, + title: `${error.data.detail} `, + status: 'error', + }); } }); } - }, [filteredResults, installModel, inplace, dispatch, t]); + }, [filteredResults, installModel, inplace, t]); const handleInstallOne = useCallback( (source: string) => { installModel({ source, inplace }) .unwrap() .then((_) => { - dispatch( - addToast( - makeToast({ - title: t('toast.modelAddedSimple'), - status: 'success', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUED, + title: t('toast.modelAddedSimple'), + status: 'success', + }); }) .catch((error) => { if (error) { - dispatch( - addToast( - makeToast({ - title: `${error.data.detail} `, - status: 'error', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUE_FAILED, + title: `${error.data.detail} `, + status: 'error', + }); } }); }, - [installModel, inplace, dispatch, t] + [installModel, inplace, t] ); return ( diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StartModelsResultItem.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StartModelsResultItem.tsx index a32fad810f..27a96c494c 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StartModelsResultItem.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StartModelsResultItem.tsx @@ -1,8 +1,6 @@ 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 { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast, ToastID } from 'features/toast/toast'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; @@ -14,7 +12,6 @@ type Props = { }; export const StarterModelsResultItem = ({ result }: Props) => { const { t } = useTranslation(); - const dispatch = useAppDispatch(); const allSources = useMemo(() => { const _allSources = [result.source]; if (result.dependencies) { @@ -29,29 +26,23 @@ export const StarterModelsResultItem = ({ result }: Props) => { installModel({ source }) .unwrap() .then((_) => { - dispatch( - addToast( - makeToast({ - title: t('toast.modelAddedSimple'), - status: 'success', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUED, + title: t('toast.modelAddedSimple'), + status: 'success', + }); }) .catch((error) => { if (error) { - dispatch( - addToast( - makeToast({ - title: `${error.data.detail} `, - status: 'error', - }) - ) - ); + toast({ + id: ToastID.MODEL_INSTALL_QUEUE_FAILED, + title: `${error.data.detail} `, + status: 'error', + }); } }); } - }, [allSources, installModel, dispatch, t]); + }, [allSources, installModel, t]); return ( diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx index c9d0c03ed8..a4a6d5c833 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx @@ -4,8 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice'; import ModelBaseBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelBaseBadge'; import ModelFormatBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelFormatBadge'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import type { MouseEvent } from 'react'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -53,25 +52,19 @@ const ModelListItem = (props: ModelListItemProps) => { deleteModel({ key: model.key }) .unwrap() .then((_) => { - dispatch( - addToast( - makeToast({ - title: `${t('modelManager.modelDeleted')}: ${model.name}`, - status: 'success', - }) - ) - ); + toast({ + id: 'MODEL_DELETED', + title: `${t('modelManager.modelDeleted')}: ${model.name}`, + status: 'success', + }); }) .catch((error) => { if (error) { - dispatch( - addToast( - makeToast({ - title: `${t('modelManager.modelDeleteFailed')}: ${model.name}`, - status: 'error', - }) - ) - ); + toast({ + id: 'MODEL_DELETE_FAILED', + title: `${t('modelManager.modelDeleteFailed')}: ${model.name}`, + status: 'error', + }); } }); dispatch(setSelectedModelKey(null)); diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/ControlNetOrT2IAdapterDefaultSettings.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/ControlNetOrT2IAdapterDefaultSettings.tsx index dcdc4e2a36..9a84fbc726 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/ControlNetOrT2IAdapterDefaultSettings.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/ControlNetOrT2IAdapterDefaultSettings.tsx @@ -1,10 +1,9 @@ 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 { DefaultPreprocessor } from 'features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/DefaultPreprocessor'; import type { FormField } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { useCallback } from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { useForm } from 'react-hook-form'; @@ -19,7 +18,6 @@ export type ControlNetOrT2IAdapterDefaultSettingsFormData = { export const ControlNetOrT2IAdapterDefaultSettings = () => { const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); const { t } = useTranslation(); - const dispatch = useAppDispatch(); const { defaultSettingsDefaults, isLoading: isLoadingDefaultSettings } = useControlNetOrT2IAdapterDefaultSettings(selectedModelKey); @@ -46,30 +44,24 @@ export const ControlNetOrT2IAdapterDefaultSettings = () => { }) .unwrap() .then((_) => { - dispatch( - addToast( - makeToast({ - title: t('modelManager.defaultSettingsSaved'), - status: 'success', - }) - ) - ); + toast({ + id: 'DEFAULT_SETTINGS_SAVED', + title: t('modelManager.defaultSettingsSaved'), + status: 'success', + }); reset(data); }) .catch((error) => { if (error) { - dispatch( - addToast( - makeToast({ - title: `${error.data.detail} `, - status: 'error', - }) - ) - ); + toast({ + id: 'DEFAULT_SETTINGS_SAVE_FAILED', + title: `${error.data.detail} `, + status: 'error', + }); } }); }, - [selectedModelKey, dispatch, reset, updateModel, t] + [selectedModelKey, reset, updateModel, t] ); if (isLoadingDefaultSettings) { diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Fields/ModelImageUpload.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Fields/ModelImageUpload.tsx index 0d7920ef77..292835a7b7 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Fields/ModelImageUpload.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Fields/ModelImageUpload.tsx @@ -1,8 +1,6 @@ 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 { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { useCallback, useState } from 'react'; import { useDropzone } from 'react-dropzone'; import { useTranslation } from 'react-i18next'; @@ -15,7 +13,6 @@ type Props = { }; const ModelImageUpload = ({ model_key, model_image }: Props) => { - const dispatch = useAppDispatch(); const [image, setImage] = useState(model_image || null); const { t } = useTranslation(); @@ -34,27 +31,21 @@ const ModelImageUpload = ({ model_key, model_image }: Props) => { .unwrap() .then(() => { setImage(URL.createObjectURL(file)); - dispatch( - addToast( - makeToast({ - title: t('modelManager.modelImageUpdated'), - status: 'success', - }) - ) - ); + toast({ + id: 'MODEL_IMAGE_UPDATED', + title: t('modelManager.modelImageUpdated'), + status: 'success', + }); }) - .catch((_) => { - dispatch( - addToast( - makeToast({ - title: t('modelManager.modelImageUpdateFailed'), - status: 'error', - }) - ) - ); + .catch(() => { + toast({ + id: 'MODEL_IMAGE_UPDATE_FAILED', + title: t('modelManager.modelImageUpdateFailed'), + status: 'error', + }); }); }, - [dispatch, model_key, t, updateModelImage] + [model_key, t, updateModelImage] ); const handleResetImage = useCallback(() => { @@ -65,26 +56,20 @@ const ModelImageUpload = ({ model_key, model_image }: Props) => { deleteModelImage(model_key) .unwrap() .then(() => { - dispatch( - addToast( - makeToast({ - title: t('modelManager.modelImageDeleted'), - status: 'success', - }) - ) - ); + toast({ + id: 'MODEL_IMAGE_DELETED', + title: t('modelManager.modelImageDeleted'), + status: 'success', + }); }) - .catch((_) => { - dispatch( - addToast( - makeToast({ - title: t('modelManager.modelImageDeleteFailed'), - status: 'error', - }) - ) - ); + .catch(() => { + toast({ + id: 'MODEL_IMAGE_DELETE_FAILED', + title: t('modelManager.modelImageDeleteFailed'), + status: 'error', + }); }); - }, [dispatch, model_key, t, deleteModelImage]); + }, [model_key, t, deleteModelImage]); const { getInputProps, getRootProps } = useDropzone({ accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] }, diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings.tsx index e096b11209..233fc7bc6b 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings.tsx @@ -1,11 +1,10 @@ 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 { DefaultHeight } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight'; import { DefaultWidth } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth'; import type { ParameterScheduler } from 'features/parameters/types/parameterSchemas'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { useCallback } from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { useForm } from 'react-hook-form'; @@ -39,7 +38,6 @@ export type MainModelDefaultSettingsFormData = { export const MainModelDefaultSettings = () => { const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); const { t } = useTranslation(); - const dispatch = useAppDispatch(); const { defaultSettingsDefaults, @@ -76,30 +74,24 @@ export const MainModelDefaultSettings = () => { }) .unwrap() .then((_) => { - dispatch( - addToast( - makeToast({ - title: t('modelManager.defaultSettingsSaved'), - status: 'success', - }) - ) - ); + toast({ + id: 'DEFAULT_SETTINGS_SAVED', + title: t('modelManager.defaultSettingsSaved'), + status: 'success', + }); reset(data); }) .catch((error) => { if (error) { - dispatch( - addToast( - makeToast({ - title: `${error.data.detail} `, - status: 'error', - }) - ) - ); + toast({ + id: 'DEFAULT_SETTINGS_SAVE_FAILED', + title: `${error.data.detail} `, + status: 'error', + }); } }); }, - [selectedModelKey, dispatch, reset, updateModel, t] + [selectedModelKey, reset, updateModel, t] ); if (isLoadingDefaultSettings) { diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx index d95eed8d24..fa7ca4c394 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx @@ -4,8 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { ModelConvertButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton'; import { ModelEditButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelEditButton'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { useCallback } from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { useForm } from 'react-hook-form'; @@ -47,25 +46,19 @@ export const Model = () => { .then((payload) => { form.reset(payload, { keepDefaultValues: true }); dispatch(setSelectedModelMode('view')); - dispatch( - addToast( - makeToast({ - title: t('modelManager.modelUpdated'), - status: 'success', - }) - ) - ); + toast({ + id: 'MODEL_UPDATED', + title: t('modelManager.modelUpdated'), + status: 'success', + }); }) .catch((_) => { form.reset(); - dispatch( - addToast( - makeToast({ - title: t('modelManager.modelUpdateFailed'), - status: 'error', - }) - ) - ); + toast({ + id: 'MODEL_UPDATE_FAILED', + title: t('modelManager.modelUpdateFailed'), + status: 'error', + }); }); }, [dispatch, data?.key, form, t, updateModel] diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton.tsx index bcff0451d6..40ffca76b4 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton.tsx @@ -9,9 +9,7 @@ import { useDisclosure, } from '@invoke-ai/ui-library'; import { skipToken } from '@reduxjs/toolkit/query'; -import { useAppDispatch } from 'app/store/storeHooks'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useConvertModelMutation, useGetModelConfigQuery } from 'services/api/endpoints/models'; @@ -22,7 +20,6 @@ interface ModelConvertProps { export const ModelConvertButton = (props: ModelConvertProps) => { const { modelKey } = props; - const dispatch = useAppDispatch(); const { t } = useTranslation(); const { data } = useGetModelConfigQuery(modelKey ?? skipToken); const [convertModel, { isLoading }] = useConvertModelMutation(); @@ -33,38 +30,26 @@ export const ModelConvertButton = (props: ModelConvertProps) => { return; } - dispatch( - addToast( - makeToast({ - title: `${t('modelManager.convertingModelBegin')}: ${data?.name}`, - status: 'info', - }) - ) - ); + const toastId = `CONVERTING_MODEL_${data.key}`; + toast({ + id: toastId, + title: `${t('modelManager.convertingModelBegin')}: ${data?.name}`, + status: 'info', + }); convertModel(data?.key) .unwrap() .then(() => { - dispatch( - addToast( - makeToast({ - title: `${t('modelManager.modelConverted')}: ${data?.name}`, - status: 'success', - }) - ) - ); + toast({ id: toastId, title: `${t('modelManager.modelConverted')}: ${data?.name}`, status: 'success' }); }) .catch(() => { - dispatch( - addToast( - makeToast({ - title: `${t('modelManager.modelConversionFailed')}: ${data?.name}`, - status: 'error', - }) - ) - ); + toast({ + id: toastId, + title: `${t('modelManager.modelConversionFailed')}: ${data?.name}`, + status: 'error', + }); }); - }, [data, isLoading, dispatch, t, convertModel]); + }, [data, isLoading, t, convertModel]); if (data?.format !== 'checkpoint') { return; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx index 226a8f006d..6da87f4e98 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx @@ -3,7 +3,6 @@ import 'reactflow/dist/style.css'; import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library'; import { Combobox, Flex, Popover, PopoverAnchor, PopoverBody, PopoverContent } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; -import { useAppToaster } from 'app/components/Toaster'; import { useAppDispatch, useAppStore } from 'app/store/storeHooks'; import type { SelectInstance } from 'chakra-react-select'; 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 type { AnyNode } 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 { memo, useCallback, useMemo, useRef } from 'react'; import { flushSync } from 'react-dom'; @@ -60,7 +60,6 @@ const filterOption = memoize((option: FilterOptionOption, inputV const AddNodePopover = () => { const dispatch = useAppDispatch(); const buildInvocation = useBuildNode(); - const toaster = useAppToaster(); const { t } = useTranslation(); const selectRef = useRef | null>(null); const inputRef = useRef(null); @@ -127,7 +126,7 @@ const AddNodePopover = () => { const errorMessage = t('nodes.unknownNode', { nodeType: nodeType, }); - toaster({ + toast({ status: 'error', title: errorMessage, }); @@ -163,7 +162,7 @@ const AddNodePopover = () => { } return node; }, - [buildInvocation, store, dispatch, t, toaster] + [buildInvocation, store, dispatch, t] ); const onChange = useCallback( diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/ClearFlowButton.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/ClearFlowButton.tsx index 9a675c7214..7ceb991bd8 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/ClearFlowButton.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/ClearFlowButton.tsx @@ -1,8 +1,7 @@ import { ConfirmationAlertDialog, Flex, IconButton, Text, useDisclosure } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiTrashSimpleFill } from 'react-icons/pi'; @@ -16,14 +15,11 @@ const ClearFlowButton = () => { const handleNewWorkflow = useCallback(() => { dispatch(nodeEditorReset()); - dispatch( - addToast( - makeToast({ - title: t('workflows.workflowCleared'), - status: 'success', - }) - ) - ); + toast({ + id: 'WORKFLOW_CLEARED', + title: t('workflows.workflowCleared'), + status: 'success', + }); onClose(); }, [dispatch, onClose, t]); diff --git a/invokeai/frontend/web/src/features/parameters/hooks/usePreselectedImage.ts b/invokeai/frontend/web/src/features/parameters/hooks/usePreselectedImage.ts index d892906fcd..683f5479f9 100644 --- a/invokeai/frontend/web/src/features/parameters/hooks/usePreselectedImage.ts +++ b/invokeai/frontend/web/src/features/parameters/hooks/usePreselectedImage.ts @@ -1,10 +1,10 @@ import { skipToken } from '@reduxjs/toolkit/query'; -import { useAppToaster } from 'app/components/Toaster'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice'; import { parseAndRecallAllMetadata } from 'features/metadata/util/handlers'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; +import { toast } from 'features/toast/toast'; import { setActiveTab } from 'features/ui/store/uiSlice'; import { t } from 'i18next'; import { useCallback, useEffect } from 'react'; @@ -16,7 +16,6 @@ export const usePreselectedImage = (selectedImage?: { }) => { const dispatch = useAppDispatch(); const optimalDimension = useAppSelector(selectOptimalDimension); - const toaster = useAppToaster(); const { currentData: selectedImageDto } = useGetImageDTOQuery(selectedImage?.imageName ?? skipToken); @@ -26,14 +25,13 @@ export const usePreselectedImage = (selectedImage?: { if (selectedImageDto) { dispatch(setInitialCanvasImage(selectedImageDto, optimalDimension)); dispatch(setActiveTab('canvas')); - toaster({ + toast({ + id: 'SENT_TO_CANVAS', title: t('toast.sentToUnifiedCanvas'), status: 'info', - duration: 2500, - isClosable: true, }); } - }, [selectedImageDto, dispatch, optimalDimension, toaster]); + }, [selectedImageDto, dispatch, optimalDimension]); const handleSendToImg2Img = useCallback(() => { if (selectedImageDto) { diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCancelBatch.ts b/invokeai/frontend/web/src/features/queue/hooks/useCancelBatch.ts index 8600525dae..9d92eabff8 100644 --- a/invokeai/frontend/web/src/features/queue/hooks/useCancelBatch.ts +++ b/invokeai/frontend/web/src/features/queue/hooks/useCancelBatch.ts @@ -1,5 +1,5 @@ -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { addToast } from 'features/system/store/systemSlice'; +import { useAppSelector } from 'app/store/storeHooks'; +import { toast } from 'features/toast/toast'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useCancelByBatchIdsMutation, useGetBatchStatusQuery } from 'services/api/endpoints/queue'; @@ -23,7 +23,6 @@ export const useCancelBatch = (batch_id: string) => { const [trigger, { isLoading }] = useCancelByBatchIdsMutation({ fixedCacheKey: 'cancelByBatchIds', }); - const dispatch = useAppDispatch(); const { t } = useTranslation(); const cancelBatch = useCallback(async () => { if (isCanceled) { @@ -31,21 +30,19 @@ export const useCancelBatch = (batch_id: string) => { } try { await trigger({ batch_ids: [batch_id] }).unwrap(); - dispatch( - addToast({ - title: t('queue.cancelBatchSucceeded'), - status: 'success', - }) - ); + toast({ + id: 'CANCEL_BATCH_SUCCEEDED', + title: t('queue.cancelBatchSucceeded'), + status: 'success', + }); } catch { - dispatch( - addToast({ - title: t('queue.cancelBatchFailed'), - status: 'error', - }) - ); + toast({ + id: 'CANCEL_BATCH_FAILED', + title: t('queue.cancelBatchFailed'), + status: 'error', + }); } - }, [batch_id, dispatch, isCanceled, t, trigger]); + }, [batch_id, isCanceled, t, trigger]); return { cancelBatch, isLoading, isCanceled, isDisabled: !isConnected }; }; diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCancelCurrentQueueItem.ts b/invokeai/frontend/web/src/features/queue/hooks/useCancelCurrentQueueItem.ts index a0275076e3..057490ed99 100644 --- a/invokeai/frontend/web/src/features/queue/hooks/useCancelCurrentQueueItem.ts +++ b/invokeai/frontend/web/src/features/queue/hooks/useCancelCurrentQueueItem.ts @@ -1,5 +1,5 @@ -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { addToast } from 'features/system/store/systemSlice'; +import { useAppSelector } from 'app/store/storeHooks'; +import { toast } from 'features/toast/toast'; import { isNil } from 'lodash-es'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -9,7 +9,6 @@ export const useCancelCurrentQueueItem = () => { const isConnected = useAppSelector((s) => s.system.isConnected); const { data: queueStatus } = useGetQueueStatusQuery(); const [trigger, { isLoading }] = useCancelQueueItemMutation(); - const dispatch = useAppDispatch(); const { t } = useTranslation(); const currentQueueItemId = useMemo(() => queueStatus?.queue.item_id, [queueStatus?.queue.item_id]); const cancelQueueItem = useCallback(async () => { @@ -18,21 +17,19 @@ export const useCancelCurrentQueueItem = () => { } try { await trigger(currentQueueItemId).unwrap(); - dispatch( - addToast({ - title: t('queue.cancelSucceeded'), - status: 'success', - }) - ); + toast({ + id: 'QUEUE_CANCEL_SUCCEEDED', + title: t('queue.cancelSucceeded'), + status: 'success', + }); } catch { - dispatch( - addToast({ - title: t('queue.cancelFailed'), - status: 'error', - }) - ); + toast({ + id: 'QUEUE_CANCEL_FAILED', + title: t('queue.cancelFailed'), + status: 'error', + }); } - }, [currentQueueItemId, dispatch, t, trigger]); + }, [currentQueueItemId, t, trigger]); const isDisabled = useMemo(() => !isConnected || isNil(currentQueueItemId), [isConnected, currentQueueItemId]); diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCancelQueueItem.ts b/invokeai/frontend/web/src/features/queue/hooks/useCancelQueueItem.ts index f22b98a7ee..268eca75cc 100644 --- a/invokeai/frontend/web/src/features/queue/hooks/useCancelQueueItem.ts +++ b/invokeai/frontend/web/src/features/queue/hooks/useCancelQueueItem.ts @@ -1,5 +1,5 @@ -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { addToast } from 'features/system/store/systemSlice'; +import { useAppSelector } from 'app/store/storeHooks'; +import { toast } from 'features/toast/toast'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useCancelQueueItemMutation } from 'services/api/endpoints/queue'; @@ -7,26 +7,23 @@ import { useCancelQueueItemMutation } from 'services/api/endpoints/queue'; export const useCancelQueueItem = (item_id: number) => { const isConnected = useAppSelector((s) => s.system.isConnected); const [trigger, { isLoading }] = useCancelQueueItemMutation(); - const dispatch = useAppDispatch(); const { t } = useTranslation(); const cancelQueueItem = useCallback(async () => { try { await trigger(item_id).unwrap(); - dispatch( - addToast({ - title: t('queue.cancelSucceeded'), - status: 'success', - }) - ); + toast({ + id: 'QUEUE_CANCEL_SUCCEEDED', + title: t('queue.cancelSucceeded'), + status: 'success', + }); } catch { - dispatch( - addToast({ - title: t('queue.cancelFailed'), - status: 'error', - }) - ); + toast({ + id: 'QUEUE_CANCEL_FAILED', + title: t('queue.cancelFailed'), + status: 'error', + }); } - }, [dispatch, item_id, t, trigger]); + }, [item_id, t, trigger]); return { cancelQueueItem, isLoading, isDisabled: !isConnected }; }; diff --git a/invokeai/frontend/web/src/features/queue/hooks/useClearInvocationCache.ts b/invokeai/frontend/web/src/features/queue/hooks/useClearInvocationCache.ts index 34b46d79b4..7ef9d93742 100644 --- a/invokeai/frontend/web/src/features/queue/hooks/useClearInvocationCache.ts +++ b/invokeai/frontend/web/src/features/queue/hooks/useClearInvocationCache.ts @@ -1,12 +1,11 @@ -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { addToast } from 'features/system/store/systemSlice'; +import { useAppSelector } from 'app/store/storeHooks'; +import { toast } from 'features/toast/toast'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useClearInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo'; export const useClearInvocationCache = () => { const { t } = useTranslation(); - const dispatch = useAppDispatch(); const { data: cacheStatus } = useGetInvocationCacheStatusQuery(); const isConnected = useAppSelector((s) => s.system.isConnected); const [trigger, { isLoading }] = useClearInvocationCacheMutation({ @@ -22,21 +21,19 @@ export const useClearInvocationCache = () => { try { await trigger().unwrap(); - dispatch( - addToast({ - title: t('invocationCache.clearSucceeded'), - status: 'success', - }) - ); + toast({ + id: 'INVOCATION_CACHE_CLEAR_SUCCEEDED', + title: t('invocationCache.clearSucceeded'), + status: 'success', + }); } catch { - dispatch( - addToast({ - title: t('invocationCache.clearFailed'), - status: 'error', - }) - ); + toast({ + id: 'INVOCATION_CACHE_CLEAR_FAILED', + title: t('invocationCache.clearFailed'), + status: 'error', + }); } - }, [isDisabled, trigger, dispatch, t]); + }, [isDisabled, trigger, t]); return { clearInvocationCache, isLoading, cacheStatus, isDisabled }; }; diff --git a/invokeai/frontend/web/src/features/queue/hooks/useClearQueue.ts b/invokeai/frontend/web/src/features/queue/hooks/useClearQueue.ts index 0ca2528dab..ca7d1e4894 100644 --- a/invokeai/frontend/web/src/features/queue/hooks/useClearQueue.ts +++ b/invokeai/frontend/web/src/features/queue/hooks/useClearQueue.ts @@ -1,6 +1,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; 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 { useTranslation } from 'react-i18next'; import { useClearQueueMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue'; @@ -21,21 +21,19 @@ export const useClearQueue = () => { try { await trigger().unwrap(); - dispatch( - addToast({ - title: t('queue.clearSucceeded'), - status: 'success', - }) - ); + toast({ + id: 'QUEUE_CLEAR_SUCCEEDED', + title: t('queue.clearSucceeded'), + status: 'success', + }); dispatch(listCursorChanged(undefined)); dispatch(listPriorityChanged(undefined)); } catch { - dispatch( - addToast({ - title: t('queue.clearFailed'), - status: 'error', - }) - ); + toast({ + id: 'QUEUE_CLEAR_FAILED', + title: t('queue.clearFailed'), + status: 'error', + }); } }, [queueStatus?.queue.total, trigger, dispatch, t]); diff --git a/invokeai/frontend/web/src/features/queue/hooks/useDisableInvocationCache.ts b/invokeai/frontend/web/src/features/queue/hooks/useDisableInvocationCache.ts index c7d5a575d2..371e9198e7 100644 --- a/invokeai/frontend/web/src/features/queue/hooks/useDisableInvocationCache.ts +++ b/invokeai/frontend/web/src/features/queue/hooks/useDisableInvocationCache.ts @@ -1,12 +1,11 @@ -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { addToast } from 'features/system/store/systemSlice'; +import { useAppSelector } from 'app/store/storeHooks'; +import { toast } from 'features/toast/toast'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useDisableInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo'; export const useDisableInvocationCache = () => { const { t } = useTranslation(); - const dispatch = useAppDispatch(); const { data: cacheStatus } = useGetInvocationCacheStatusQuery(); const isConnected = useAppSelector((s) => s.system.isConnected); const [trigger, { isLoading }] = useDisableInvocationCacheMutation({ @@ -25,21 +24,19 @@ export const useDisableInvocationCache = () => { try { await trigger().unwrap(); - dispatch( - addToast({ - title: t('invocationCache.disableSucceeded'), - status: 'success', - }) - ); + toast({ + id: 'INVOCATION_CACHE_DISABLE_SUCCEEDED', + title: t('invocationCache.disableSucceeded'), + status: 'success', + }); } catch { - dispatch( - addToast({ - title: t('invocationCache.disableFailed'), - status: 'error', - }) - ); + toast({ + id: 'INVOCATION_CACHE_DISABLE_FAILED', + title: t('invocationCache.disableFailed'), + status: 'error', + }); } - }, [isDisabled, trigger, dispatch, t]); + }, [isDisabled, trigger, t]); return { disableInvocationCache, isLoading, cacheStatus, isDisabled }; }; diff --git a/invokeai/frontend/web/src/features/queue/hooks/useEnableInvocationCache.ts b/invokeai/frontend/web/src/features/queue/hooks/useEnableInvocationCache.ts index 22bb7aa97d..fb39cf7347 100644 --- a/invokeai/frontend/web/src/features/queue/hooks/useEnableInvocationCache.ts +++ b/invokeai/frontend/web/src/features/queue/hooks/useEnableInvocationCache.ts @@ -1,12 +1,11 @@ -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { addToast } from 'features/system/store/systemSlice'; +import { useAppSelector } from 'app/store/storeHooks'; +import { toast } from 'features/toast/toast'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useEnableInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo'; export const useEnableInvocationCache = () => { const { t } = useTranslation(); - const dispatch = useAppDispatch(); const { data: cacheStatus } = useGetInvocationCacheStatusQuery(); const isConnected = useAppSelector((s) => s.system.isConnected); const [trigger, { isLoading }] = useEnableInvocationCacheMutation({ @@ -25,21 +24,19 @@ export const useEnableInvocationCache = () => { try { await trigger().unwrap(); - dispatch( - addToast({ - title: t('invocationCache.enableSucceeded'), - status: 'success', - }) - ); + toast({ + id: 'INVOCATION_CACHE_ENABLE_SUCCEEDED', + title: t('invocationCache.enableSucceeded'), + status: 'success', + }); } catch { - dispatch( - addToast({ - title: t('invocationCache.enableFailed'), - status: 'error', - }) - ); + toast({ + id: 'INVOCATION_CACHE_ENABLE_FAILED', + title: t('invocationCache.enableFailed'), + status: 'error', + }); } - }, [isDisabled, trigger, dispatch, t]); + }, [isDisabled, trigger, t]); return { enableInvocationCache, isLoading, cacheStatus, isDisabled }; }; diff --git a/invokeai/frontend/web/src/features/queue/hooks/usePauseProcessor.ts b/invokeai/frontend/web/src/features/queue/hooks/usePauseProcessor.ts index 25c3423bcf..f5424c6b18 100644 --- a/invokeai/frontend/web/src/features/queue/hooks/usePauseProcessor.ts +++ b/invokeai/frontend/web/src/features/queue/hooks/usePauseProcessor.ts @@ -1,11 +1,10 @@ -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { addToast } from 'features/system/store/systemSlice'; +import { useAppSelector } from 'app/store/storeHooks'; +import { toast } from 'features/toast/toast'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useGetQueueStatusQuery, usePauseProcessorMutation } from 'services/api/endpoints/queue'; export const usePauseProcessor = () => { - const dispatch = useAppDispatch(); const { t } = useTranslation(); const isConnected = useAppSelector((s) => s.system.isConnected); const { data: queueStatus } = useGetQueueStatusQuery(); @@ -21,21 +20,19 @@ export const usePauseProcessor = () => { } try { await trigger().unwrap(); - dispatch( - addToast({ - title: t('queue.pauseSucceeded'), - status: 'success', - }) - ); + toast({ + id: 'PAUSE_SUCCEEDED', + title: t('queue.pauseSucceeded'), + status: 'success', + }); } catch { - dispatch( - addToast({ - title: t('queue.pauseFailed'), - status: 'error', - }) - ); + toast({ + id: 'PAUSE_FAILED', + title: t('queue.pauseFailed'), + status: 'error', + }); } - }, [isStarted, trigger, dispatch, t]); + }, [isStarted, trigger, t]); const isDisabled = useMemo(() => !isConnected || !isStarted, [isConnected, isStarted]); diff --git a/invokeai/frontend/web/src/features/queue/hooks/usePruneQueue.ts b/invokeai/frontend/web/src/features/queue/hooks/usePruneQueue.ts index 2cfec364fa..eaeabe5423 100644 --- a/invokeai/frontend/web/src/features/queue/hooks/usePruneQueue.ts +++ b/invokeai/frontend/web/src/features/queue/hooks/usePruneQueue.ts @@ -1,6 +1,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; 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 { useTranslation } from 'react-i18next'; import { useGetQueueStatusQuery, usePruneQueueMutation } from 'services/api/endpoints/queue'; @@ -30,21 +30,19 @@ export const usePruneQueue = () => { } try { const data = await trigger().unwrap(); - dispatch( - addToast({ - title: t('queue.pruneSucceeded', { item_count: data.deleted }), - status: 'success', - }) - ); + toast({ + id: 'PRUNE_SUCCEEDED', + title: t('queue.pruneSucceeded', { item_count: data.deleted }), + status: 'success', + }); dispatch(listCursorChanged(undefined)); dispatch(listPriorityChanged(undefined)); } catch { - dispatch( - addToast({ - title: t('queue.pruneFailed'), - status: 'error', - }) - ); + toast({ + id: 'PRUNE_FAILED', + title: t('queue.pruneFailed'), + status: 'error', + }); } }, [finishedCount, trigger, dispatch, t]); diff --git a/invokeai/frontend/web/src/features/queue/hooks/useResumeProcessor.ts b/invokeai/frontend/web/src/features/queue/hooks/useResumeProcessor.ts index 6e3ea83d7d..851b268416 100644 --- a/invokeai/frontend/web/src/features/queue/hooks/useResumeProcessor.ts +++ b/invokeai/frontend/web/src/features/queue/hooks/useResumeProcessor.ts @@ -1,11 +1,10 @@ -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { addToast } from 'features/system/store/systemSlice'; +import { useAppSelector } from 'app/store/storeHooks'; +import { toast } from 'features/toast/toast'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useGetQueueStatusQuery, useResumeProcessorMutation } from 'services/api/endpoints/queue'; export const useResumeProcessor = () => { - const dispatch = useAppDispatch(); const isConnected = useAppSelector((s) => s.system.isConnected); const { data: queueStatus } = useGetQueueStatusQuery(); const { t } = useTranslation(); @@ -21,21 +20,19 @@ export const useResumeProcessor = () => { } try { await trigger().unwrap(); - dispatch( - addToast({ - title: t('queue.resumeSucceeded'), - status: 'success', - }) - ); + toast({ + id: 'PROCESSOR_RESUMED', + title: t('queue.resumeSucceeded'), + status: 'success', + }); } catch { - dispatch( - addToast({ - title: t('queue.resumeFailed'), - status: 'error', - }) - ); + toast({ + id: 'PROCESSOR_RESUME_FAILED', + title: t('queue.resumeFailed'), + status: 'error', + }); } - }, [isStarted, trigger, dispatch, t]); + }, [isStarted, trigger, t]); const isDisabled = useMemo(() => !isConnected || isStarted, [isConnected, isStarted]); diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/useClearIntermediates.ts b/invokeai/frontend/web/src/features/system/components/SettingsModal/useClearIntermediates.ts index e9f1debcf8..f392acb521 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/useClearIntermediates.ts +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/useClearIntermediates.ts @@ -1,7 +1,7 @@ import { useAppDispatch } from 'app/store/storeHooks'; import { resetCanvas } from 'features/canvas/store/canvasSlice'; 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 { useTranslation } from 'react-i18next'; import { useClearIntermediatesMutation, useGetIntermediatesCountQuery } from 'services/api/endpoints/images'; @@ -42,20 +42,18 @@ export const useClearIntermediates = (shouldShowClearIntermediates: boolean): Us .then((clearedCount) => { dispatch(controlAdaptersReset()); dispatch(resetCanvas()); - dispatch( - addToast({ - title: t('settings.intermediatesCleared', { count: clearedCount }), - status: 'info', - }) - ); + toast({ + id: 'INTERMEDIATES_CLEARED', + title: t('settings.intermediatesCleared', { count: clearedCount }), + status: 'info', + }); }) .catch(() => { - dispatch( - addToast({ - title: t('settings.intermediatesClearedFailed'), - status: 'error', - }) - ); + toast({ + id: 'INTERMEDIATES_CLEAR_FAILED', + title: t('settings.intermediatesClearedFailed'), + status: 'error', + }); }); }, [t, _clearIntermediates, dispatch, hasPendingItems]); diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 17ddec5471..65903460ed 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -1,11 +1,7 @@ -import type { UseToastOptions } from '@invoke-ai/ui-library'; 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 { 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 { socketConnected, @@ -13,13 +9,10 @@ import { socketGeneratorProgress, socketGraphExecutionStateComplete, socketInvocationComplete, - socketInvocationError, - socketInvocationRetrievalError, socketInvocationStarted, socketModelLoadCompleted, socketModelLoadStarted, socketQueueItemStatusChanged, - socketSessionRetrievalError, } from 'services/events/actions'; import type { Language, SystemState } from './types'; @@ -29,7 +22,6 @@ const initialSystemState: SystemState = { isConnected: false, shouldConfirmOnDelete: true, enableImageDebugging: false, - toastQueue: [], denoiseProgress: null, shouldAntialiasProgressImage: false, consoleLogLevel: 'debug', @@ -51,12 +43,6 @@ export const systemSlice = createSlice({ setEnableImageDebugging: (state, action: PayloadAction) => { state.enableImageDebugging = action.payload; }, - addToast: (state, action: PayloadAction) => { - state.toastQueue.push(action.payload); - }, - clearToastQueue: (state) => { - state.toastQueue = []; - }, consoleLogLevelChanged: (state, action: PayloadAction) => { state.consoleLogLevel = action.payload; }, @@ -162,29 +148,12 @@ export const systemSlice = createSlice({ 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 { setShouldConfirmOnDelete, setEnableImageDebugging, - addToast, - clearToastQueue, consoleLogLevelChanged, shouldLogToConsoleChanged, shouldAntialiasProgressImageChanged, @@ -194,8 +163,6 @@ export const { setShouldEnableInformationalPopovers, } = systemSlice.actions; -const isAnyServerError = isAnyOf(socketInvocationError, socketSessionRetrievalError, socketInvocationRetrievalError); - export const selectSystemSlice = (state: RootState) => state.system; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ diff --git a/invokeai/frontend/web/src/features/system/store/types.ts b/invokeai/frontend/web/src/features/system/store/types.ts index 430df9aa7d..d8bc8cd08a 100644 --- a/invokeai/frontend/web/src/features/system/store/types.ts +++ b/invokeai/frontend/web/src/features/system/store/types.ts @@ -1,4 +1,3 @@ -import type { UseToastOptions } from '@invoke-ai/ui-library'; import type { LogLevel } from 'app/logging/logger'; import type { ProgressImage } from 'services/events/types'; import { z } from 'zod'; @@ -47,7 +46,6 @@ export interface SystemState { isConnected: boolean; shouldConfirmOnDelete: boolean; enableImageDebugging: boolean; - toastQueue: UseToastOptions[]; denoiseProgress: DenoiseProgress | null; consoleLogLevel: LogLevel; shouldLogToConsole: boolean; diff --git a/invokeai/frontend/web/src/features/system/util/makeToast.ts b/invokeai/frontend/web/src/features/system/util/makeToast.ts deleted file mode 100644 index aa77fd60ae..0000000000 --- a/invokeai/frontend/web/src/features/system/util/makeToast.ts +++ /dev/null @@ -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 }; -}; diff --git a/invokeai/frontend/web/src/features/workflowLibrary/components/NewWorkflowConfirmationAlertDialog.tsx b/invokeai/frontend/web/src/features/workflowLibrary/components/NewWorkflowConfirmationAlertDialog.tsx index b01d259da7..701441b093 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/components/NewWorkflowConfirmationAlertDialog.tsx +++ b/invokeai/frontend/web/src/features/workflowLibrary/components/NewWorkflowConfirmationAlertDialog.tsx @@ -2,8 +2,7 @@ import { ConfirmationAlertDialog, Flex, Text, useDisclosure } from '@invoke-ai/u import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { workflowModeChanged } from 'features/nodes/store/workflowSlice'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -21,14 +20,11 @@ export const NewWorkflowConfirmationAlertDialog = memo((props: Props) => { dispatch(nodeEditorReset()); dispatch(workflowModeChanged('edit')); - dispatch( - addToast( - makeToast({ - title: t('workflows.newWorkflowCreated'), - status: 'success', - }) - ) - ); + toast({ + id: 'NEW_WORKFLOW_CREATED', + title: t('workflows.newWorkflowCreated'), + status: 'success', + }); onClose(); }, [dispatch, onClose, t]); diff --git a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useDeleteLibraryWorkflow.ts b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useDeleteLibraryWorkflow.ts index 7d2c636e9c..7b93a34f83 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useDeleteLibraryWorkflow.ts +++ b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useDeleteLibraryWorkflow.ts @@ -1,5 +1,4 @@ -import { useToast } from '@invoke-ai/ui-library'; -import { useAppToaster } from 'app/components/Toaster'; +import { toast } from 'features/toast/toast'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useDeleteWorkflowMutation, workflowsApi } from 'services/api/endpoints/workflows'; @@ -17,8 +16,6 @@ type UseDeleteLibraryWorkflowReturn = { type UseDeleteLibraryWorkflow = (arg: UseDeleteLibraryWorkflowOptions) => UseDeleteLibraryWorkflowReturn; export const useDeleteLibraryWorkflow: UseDeleteLibraryWorkflow = ({ onSuccess, onError }) => { - const toaster = useAppToaster(); - const toast = useToast(); const { t } = useTranslation(); const [_deleteWorkflow, deleteWorkflowResult] = useDeleteWorkflowMutation(); @@ -26,21 +23,21 @@ export const useDeleteLibraryWorkflow: UseDeleteLibraryWorkflow = ({ onSuccess, async (workflow_id: string) => { try { await _deleteWorkflow(workflow_id).unwrap(); - toaster({ + toast({ + id: 'WORKFLOW_DELETED', title: t('toast.workflowDeleted'), }); onSuccess && onSuccess(); } catch { - if (!toast.isActive(`auth-error-toast-${workflowsApi.endpoints.deleteWorkflow.name}`)) { - toaster({ - title: t('toast.problemDeletingWorkflow'), - status: 'error', - }); - } + toast({ + id: `AUTH_ERROR_TOAST_${workflowsApi.endpoints.deleteWorkflow.name}`, + title: t('toast.problemDeletingWorkflow'), + status: 'error', + }); onError && onError(); } }, - [_deleteWorkflow, toaster, t, onSuccess, onError, toast] + [_deleteWorkflow, t, onSuccess, onError] ); return { deleteWorkflow, deleteWorkflowResult }; diff --git a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow.ts b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow.ts index 7ea9329540..12c302f9c9 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow.ts +++ b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow.ts @@ -1,6 +1,6 @@ -import { useAppToaster } from 'app/components/Toaster'; import { useAppDispatch } from 'app/store/storeHooks'; import { workflowLoadRequested } from 'features/nodes/store/actions'; +import { toast } from 'features/toast/toast'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useLazyGetImageWorkflowQuery } from 'services/api/endpoints/images'; @@ -21,7 +21,6 @@ type UseGetAndLoadEmbeddedWorkflow = ( export const useGetAndLoadEmbeddedWorkflow: UseGetAndLoadEmbeddedWorkflow = ({ onSuccess, onError }) => { const dispatch = useAppDispatch(); - const toaster = useAppToaster(); const { t } = useTranslation(); const [_getAndLoadEmbeddedWorkflow, getAndLoadEmbeddedWorkflowResult] = useLazyGetImageWorkflowQuery(); 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 onSuccess && onSuccess(); } else { - toaster({ + toast({ + id: 'PROBLEM_RETRIEVING_WORKFLOW', title: t('toast.problemRetrievingWorkflow'), status: 'error', }); } } catch { - toaster({ + toast({ + id: 'PROBLEM_RETRIEVING_WORKFLOW', title: t('toast.problemRetrievingWorkflow'), status: 'error', }); onError && onError(); } }, - [_getAndLoadEmbeddedWorkflow, dispatch, onSuccess, toaster, t, onError] + [_getAndLoadEmbeddedWorkflow, dispatch, onSuccess, t, onError] ); return { getAndLoadEmbeddedWorkflow, getAndLoadEmbeddedWorkflowResult }; diff --git a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow.ts b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow.ts index f616812175..89933999bd 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow.ts +++ b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow.ts @@ -1,5 +1,4 @@ import { useToast } from '@invoke-ai/ui-library'; -import { useAppToaster } from 'app/components/Toaster'; import { useAppDispatch } from 'app/store/storeHooks'; import { workflowLoadRequested } from 'features/nodes/store/actions'; import { useCallback } from 'react'; @@ -20,7 +19,6 @@ type UseGetAndLoadLibraryWorkflow = (arg: UseGetAndLoadLibraryWorkflowOptions) = export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = ({ onSuccess, onError }) => { const dispatch = useAppDispatch(); - const toaster = useAppToaster(); const toast = useToast(); const { t } = useTranslation(); 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 onSuccess && onSuccess(); } catch { - if (!toast.isActive(`auth-error-toast-${workflowsApi.endpoints.getWorkflow.name}`)) { - toaster({ - title: t('toast.problemRetrievingWorkflow'), - status: 'error', - }); - } + toast({ + id: `AUTH_ERROR_TOAST_${workflowsApi.endpoints.getWorkflow.name}`, + title: t('toast.problemRetrievingWorkflow'), + status: 'error', + }); onError && onError(); } }, - [_getAndLoadWorkflow, dispatch, onSuccess, toaster, t, onError, toast] + [_getAndLoadWorkflow, dispatch, onSuccess, t, onError, toast] ); return { getAndLoadWorkflow, getAndLoadWorkflowResult }; diff --git a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useLoadWorkflowFromFile.tsx b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useLoadWorkflowFromFile.tsx index 7a39d4ecd0..94a1ef5c51 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useLoadWorkflowFromFile.tsx +++ b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useLoadWorkflowFromFile.tsx @@ -1,8 +1,7 @@ import { useLogger } from 'app/logging/useLogger'; import { useAppDispatch } from 'app/store/storeHooks'; import { workflowLoadRequested } from 'features/nodes/store/actions'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { workflowLoadedFromFile } from 'features/workflowLibrary/store/actions'; import type { RefObject } from 'react'; import { useCallback } from 'react'; @@ -35,14 +34,11 @@ export const useLoadWorkflowFromFile: UseLoadWorkflowFromFile = ({ resetRef, onS } catch (e) { // There was a problem reading the file logger.error(t('nodes.unableToLoadWorkflow')); - dispatch( - addToast( - makeToast({ - title: t('nodes.unableToLoadWorkflow'), - status: 'error', - }) - ) - ); + toast({ + id: 'UNABLE_TO_LOAD_WORKFLOW', + title: t('nodes.unableToLoadWorkflow'), + status: 'error', + }); reader.abort(); } }; diff --git a/invokeai/frontend/web/src/services/api/authToastMiddleware.ts b/invokeai/frontend/web/src/services/api/authToastMiddleware.ts index 002cc96174..3a906a613b 100644 --- a/invokeai/frontend/web/src/services/api/authToastMiddleware.ts +++ b/invokeai/frontend/web/src/services/api/authToastMiddleware.ts @@ -1,6 +1,6 @@ -import type { Middleware, MiddlewareAPI } from '@reduxjs/toolkit'; +import type { Middleware } 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 { z } from 'zod'; @@ -22,7 +22,7 @@ const zRejectedForbiddenAction = z.object({ .optional(), }); -export const authToastMiddleware: Middleware = (api: MiddlewareAPI) => (next) => (action) => { +export const authToastMiddleware: Middleware = () => (next) => (action) => { if (isRejectedWithValue(action)) { try { const parsed = zRejectedForbiddenAction.parse(action); @@ -32,16 +32,13 @@ export const authToastMiddleware: Middleware = (api: MiddlewareAPI) => (next) => return; } - const { dispatch } = api; const customMessage = parsed.payload.data.detail !== 'Forbidden' ? parsed.payload.data.detail : undefined; - dispatch( - addToast({ - id: `auth-error-toast-${endpointName}`, - title: t('common.somethingWentWrong'), - status: 'error', - description: customMessage, - }) - ); + toast({ + id: `auth-error-toast-${endpointName}`, + title: t('toast.somethingWentWrong'), + status: 'error', + description: customMessage, + }); } catch (error) { // no-op } diff --git a/invokeai/frontend/web/src/services/api/endpoints/images.ts b/invokeai/frontend/web/src/services/api/endpoints/images.ts index 14edf6fb87..5d56a61add 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/images.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/images.ts @@ -4,7 +4,7 @@ import { getStore } from 'app/store/nanostores/store'; import type { JSONObject } from 'common/types'; import type { BoardId } 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 { keyBy } from 'lodash-es'; import type { components, paths } from 'services/api/schema'; @@ -206,13 +206,12 @@ export const imagesApi = api.injectEndpoints({ const { data } = await queryFulfilled; if (data.deleted_images.length < imageDTOs.length) { - dispatch( - addToast({ - title: t('gallery.problemDeletingImages'), - description: t('gallery.problemDeletingImagesDesc'), - status: 'warning', - }) - ); + toast({ + id: 'problem-deleting-images', + title: t('gallery.problemDeletingImages'), + description: t('gallery.problemDeletingImagesDesc'), + status: 'warning', + }); } // convert to an object so we can access the successfully delete image DTOs by name diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index 1160a2bee5..578b60efa7 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -1,4 +1,3 @@ -import type { UseToastOptions } from '@invoke-ai/ui-library'; import type { EntityState } from '@reduxjs/toolkit'; import type { components, paths } from 'services/api/schema'; import type { O } from 'ts-toolbelt'; @@ -200,7 +199,7 @@ type CanvasInitialImageAction = { type ToastAction = { type: 'TOAST'; - toastOptions?: UseToastOptions; + title?: string; }; type AddToBatchAction = { diff --git a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts index 4476624e4e..446a3e8a4b 100644 --- a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts +++ b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts @@ -2,8 +2,7 @@ import { $baseUrl } from 'app/store/nanostores/baseUrl'; import { $bulkDownloadId } from 'app/store/nanostores/bulkDownloadId'; import { $queueId } from 'app/store/nanostores/queueId'; import type { AppDispatch } from 'app/store/store'; -import { addToast } from 'features/system/store/systemSlice'; -import { makeToast } from 'features/system/util/makeToast'; +import { toast } from 'features/toast/toast'; import { socketBulkDownloadCompleted, socketBulkDownloadFailed, @@ -52,15 +51,12 @@ export const setEventListeners = (arg: SetEventListenersArg) => { if (error && error.message) { const data: string | undefined = (error as unknown as { data: string | undefined }).data; if (data === 'ERR_UNAUTHENTICATED') { - dispatch( - addToast( - makeToast({ - title: error.message, - status: 'error', - duration: 10000, - }) - ) - ); + toast({ + id: `connect-error-${error.message}`, + title: error.message, + status: 'error', + duration: 10000, + }); } } });