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 b09b57bd0c..df1759f3a9 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,44 +3,16 @@ 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'); -const getTitle = (errorType: string) => { - if (errorType === 'OutOfMemoryError') { - return t('toast.outOfMemoryError'); - } - return t('toast.serverError'); -}; - -const getDescription = (errorType: string, sessionId: string, isLocal?: boolean) => { - if (!isLocal) { - if (errorType === 'OutOfMemoryError') { - return ToastWithSessionRefDescription({ - message: t('toast.outOfMemoryDescription'), - sessionId, - }); - } - return ToastWithSessionRefDescription({ - message: errorType, - sessionId, - }); - } - return errorType; -}; - export const addInvocationErrorEventListener = (startAppListening: AppStartListening) => { startAppListening({ actionCreator: socketInvocationError, - effect: (action, { getState }) => { + effect: (action) => { log.error(action.payload, `Invocation error (${action.payload.data.node.type})`); - const { source_node_id, error_type, error_message, error_traceback, graph_execution_state_id } = - action.payload.data; + const { source_node_id, error_type, error_message, error_traceback } = action.payload.data; const nes = deepClone($nodeExecutionStates.get()[source_node_id]); if (nes) { nes.status = zNodeStatus.enum.FAILED; @@ -53,19 +25,6 @@ export const addInvocationErrorEventListener = (startAppListening: AppStartListe }; upsertExecutionState(nes.nodeId, nes); } - - const errorType = startCase(error_type); - const sessionId = graph_execution_state_id; - const { isLocal } = getState().config; - - toast({ - id: `INVOCATION_ERROR_${errorType}`, - title: getTitle(errorType), - status: 'error', - duration: null, - description: getDescription(errorType, sessionId, isLocal), - updateDescription: isLocal ? true : false, - }); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged.tsx similarity index 73% rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged.ts rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged.tsx index 3b274b2889..b72401d915 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged.tsx @@ -3,6 +3,8 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware' import { deepClone } from 'common/util/deepClone'; import { $nodeExecutionStates } from 'features/nodes/hooks/useExecutionState'; import { zNodeStatus } from 'features/nodes/types/invocation'; +import ErrorToastDescription, { getTitleFromErrorType } from 'features/toast/ErrorToastDescription'; +import { toast } from 'features/toast/toast'; import { forEach } from 'lodash-es'; import { queueApi, queueItemsAdapter } from 'services/api/endpoints/queue'; import { socketQueueItemStatusChanged } from 'services/events/actions'; @@ -12,7 +14,7 @@ const log = logger('socketio'); export const addSocketQueueItemStatusChangedEventListener = (startAppListening: AppStartListening) => { startAppListening({ actionCreator: socketQueueItemStatusChanged, - effect: async (action, { dispatch }) => { + effect: async (action, { dispatch, getState }) => { // we've got new status for the queue item, batch and queue const { queue_item, batch_status, queue_status } = action.payload.data; @@ -54,7 +56,7 @@ export const addSocketQueueItemStatusChangedEventListener = (startAppListening: ]) ); - if (['in_progress'].includes(action.payload.data.queue_item.status)) { + if (queue_item.status === 'in_progress') { forEach($nodeExecutionStates.get(), (nes) => { if (!nes) { return; @@ -67,6 +69,26 @@ export const addSocketQueueItemStatusChangedEventListener = (startAppListening: clone.outputs = []; $nodeExecutionStates.setKey(clone.nodeId, clone); }); + } else if (queue_item.status === 'failed' && queue_item.error_type) { + const { error_type, error_message, session_id } = queue_item; + const isLocal = getState().config.isLocal ?? true; + const sessionId = session_id; + + toast({ + id: `INVOCATION_ERROR_${error_type}`, + title: getTitleFromErrorType(error_type), + status: 'error', + duration: null, + description: ( + + ), + updateDescription: isLocal ? true : false, + }); } }, }); diff --git a/invokeai/frontend/web/src/features/toast/ErrorToastDescription.tsx b/invokeai/frontend/web/src/features/toast/ErrorToastDescription.tsx new file mode 100644 index 0000000000..b9729c1510 --- /dev/null +++ b/invokeai/frontend/web/src/features/toast/ErrorToastDescription.tsx @@ -0,0 +1,60 @@ +import { Flex, IconButton, Text } from '@invoke-ai/ui-library'; +import { t } from 'i18next'; +import { upperFirst } from 'lodash-es'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiCopyBold } from 'react-icons/pi'; + +function onCopy(sessionId: string) { + navigator.clipboard.writeText(sessionId); +} + +const ERROR_TYPE_TO_TITLE: Record = { + OutOfMemoryError: 'toast.outOfMemoryError', +}; + +const COMMERCIAL_ERROR_TYPE_TO_DESC: Record = { + OutOfMemoryError: 'toast.outOfMemoryErrorDesc', +}; + +export const getTitleFromErrorType = (errorType: string) => { + return t(ERROR_TYPE_TO_TITLE[errorType] ?? 'toast.serverError'); +}; + +type Props = { errorType: string; errorMessage?: string | null; sessionId: string; isLocal: boolean }; + +export default function ErrorToastDescription({ errorType, errorMessage, sessionId, isLocal }: Props) { + const { t } = useTranslation(); + const description = useMemo(() => { + // Special handling for commercial error types + const descriptionTKey = isLocal ? null : COMMERCIAL_ERROR_TYPE_TO_DESC[errorType]; + if (descriptionTKey) { + return t(descriptionTKey); + } + if (errorMessage) { + return upperFirst(errorMessage); + } + }, [errorMessage, errorType, isLocal, t]); + return ( + + {description && {description}} + {!isLocal && ( + + + {t('toast.sessionRef', { sessionId })} + + } + onClick={onCopy.bind(null, sessionId)} + variant="ghost" + sx={sx} + /> + + )} + + ); +} + +const sx = { svg: { fill: 'base.50' } }; diff --git a/invokeai/frontend/web/src/features/toast/ToastWithSessionRefDescription.tsx b/invokeai/frontend/web/src/features/toast/ToastWithSessionRefDescription.tsx deleted file mode 100644 index 9d2999e765..0000000000 --- a/invokeai/frontend/web/src/features/toast/ToastWithSessionRefDescription.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Flex, IconButton, Text } from '@invoke-ai/ui-library'; -import { t } from 'i18next'; -import { PiCopyBold } from 'react-icons/pi'; - -function onCopy(sessionId: string) { - navigator.clipboard.writeText(sessionId); -} - -type Props = { message: string; sessionId: string }; - -export default function ToastWithSessionRefDescription({ message, sessionId }: Props) { - return ( - - {message} - - {t('toast.sessionRef', { sessionId })} - } - onClick={onCopy.bind(null, sessionId)} - variant="ghost" - sx={sx} - /> - - - ); -} - -const sx = { svg: { fill: 'base.50' } };