Compare commits

...

1 Commits

Author SHA1 Message Date
Mary Hipp
2259157231 explicitly send details on image upload and enqueue failures 2025-07-15 12:02:46 -04:00
9 changed files with 80 additions and 8 deletions

View File

@@ -1,6 +1,7 @@
import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { truncate } from 'es-toolkit/compat';
import { trackErrorDetails } from 'features/system/store/actions';
import { zPydanticValidationError } from 'features/system/store/zodSchemas';
import { toast } from 'features/toast/toast';
import { t } from 'i18next';
@@ -34,7 +35,7 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
// error
startAppListening({
matcher: queueApi.endpoints.enqueueBatch.matchRejected,
effect: (action) => {
effect: (action, { dispatch }) => {
const response = action.payload;
const batchConfig = action.meta.arg.originalArgs;
@@ -46,6 +47,13 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
description: t('common.unknownError'),
});
log.error({ batchConfig } as JsonObject, t('queue.batchFailedToQueue'));
dispatch(
trackErrorDetails({
title: 'Enqueue Batch Rejected',
errorMessage: t('common.unknownError'),
description: null,
})
);
return;
}
@@ -59,6 +67,9 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
status: 'error',
description,
});
dispatch(
trackErrorDetails({ title: 'Enqueue Batch Rejected', errorMessage: e.msg, description: description })
);
});
} else if (response.status !== 403) {
toast({
@@ -69,6 +80,13 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
});
}
log.error({ batchConfig, error: serializeError(response) } as JsonObject, t('queue.batchFailedToQueue'));
dispatch(
trackErrorDetails({
title: 'Enqueue Batch Rejected',
errorMessage: t('common.unknownError'),
description: null,
})
);
},
});
};

View File

@@ -6,6 +6,8 @@ import { omit } from 'es-toolkit/compat';
import { imageUploadedClientSide } from 'features/gallery/store/actions';
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
import { boardIdSelected, galleryViewChanged } from 'features/gallery/store/gallerySlice';
import { trackErrorDetails } from 'features/system/store/actions';
import { zPydanticValidationErrorWithDetail } from 'features/system/store/zodSchemas';
import { toast } from 'features/toast/toast';
import { t } from 'i18next';
import { boardsApi } from 'services/api/endpoints/boards';
@@ -130,7 +132,7 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
startAppListening({
matcher: imagesApi.endpoints.uploadImage.matchRejected,
effect: (action) => {
effect: (action, { dispatch }) => {
const sanitizedData = {
arg: {
...omit(action.meta.arg.originalArgs, ['file', 'postUploadAction']),
@@ -138,9 +140,15 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
},
};
log.error({ ...sanitizedData }, 'Image upload failed');
const parsedError = zPydanticValidationErrorWithDetail.safeParse(action.payload);
const errorMessage = parsedError.success ? parsedError.data.data.detail : action.error.message;
dispatch(trackErrorDetails({ title: 'Image Upload Rejected', errorMessage, description: null }));
toast({
title: t('toast.imageUploadFailed'),
description: action.error.message,
description: errorMessage,
status: 'error',
});
},

View File

@@ -1,9 +1,11 @@
import type { ButtonProps, IconButtonProps, SystemStyleObject } from '@invoke-ai/ui-library';
import { Button, IconButton } from '@invoke-ai/ui-library';
import { logger } from 'app/logging/logger';
import { useAppSelector } from 'app/store/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
import { trackErrorDetails } from 'features/system/store/actions';
import { selectIsClientSideUploadEnabled } from 'features/system/store/configSlice';
import { zPydanticValidationErrorWithDetail } from 'features/system/store/zodSchemas';
import { toast } from 'features/toast/toast';
import { memo, useCallback } from 'react';
import type { FileRejection } from 'react-dropzone';
@@ -65,6 +67,7 @@ export const useImageUploadButton = ({
const [uploadImage, request] = useUploadImageMutation();
const clientSideUpload = useClientSideUpload();
const { t } = useTranslation();
const dispatch = useAppDispatch();
const onDropAccepted = useCallback(
async (files: File[]) => {
@@ -116,6 +119,11 @@ export const useImageUploadButton = ({
}
} catch (error) {
onError?.(error);
const parsedError = zPydanticValidationErrorWithDetail.safeParse(error);
const errorMessage = parsedError.success ? parsedError.data.data.detail : undefined;
dispatch(trackErrorDetails({ title: 'Failed to upload image', errorMessage, description: null }));
toast({
id: 'UPLOAD_FAILED',
title: t('toast.imageUploadFailed'),
@@ -133,6 +141,7 @@ export const useImageUploadButton = ({
clientSideUpload,
onError,
t,
dispatch,
]
);

View File

@@ -13,7 +13,7 @@ import {
import { useStore } from '@nanostores/react';
import { logger } from 'app/logging/logger';
import { $projectUrl } from 'app/store/nanostores/projectId';
import { useAppSelector } from 'app/store/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { withResultAsync } from 'common/util/result';
import { parseify } from 'common/util/serialize';
@@ -40,6 +40,7 @@ import { useOutputFieldTemplate } from 'features/nodes/hooks/useOutputFieldTempl
import { useZoomToNode } from 'features/nodes/hooks/useZoomToNode';
import { useEnqueueWorkflows } from 'features/queue/hooks/useEnqueueWorkflows';
import { $isReadyToEnqueue } from 'features/queue/store/readiness';
import { trackErrorDetails } from 'features/system/store/actions';
import { selectAllowPublishWorkflows } from 'features/system/store/configSlice';
import { toast } from 'features/toast/toast';
import type { PropsWithChildren } from 'react';
@@ -209,6 +210,7 @@ const PublishWorkflowButton = memo(() => {
const isSelectingOutputNode = useStore($isSelectingOutputNode);
const inputs = usePublishInputs();
const allowPublishWorkflows = useAppSelector(selectAllowPublishWorkflows);
const dispatch = useAppDispatch();
const projectUrl = useStore($projectUrl);
@@ -217,6 +219,9 @@ const PublishWorkflowButton = memo(() => {
$isPublishing.set(true);
const result = await withResultAsync(() => enqueue(true, true));
if (result.isErr()) {
dispatch(
trackErrorDetails({ title: 'Failed to enqueue batch', errorMessage: result.error.message, description: null })
);
toast({
id: 'TOAST_PUBLISH_FAILED',
status: 'error',
@@ -225,6 +230,13 @@ const PublishWorkflowButton = memo(() => {
duration: null,
});
log.error({ error: serializeError(result.error) }, 'Failed to enqueue batch');
dispatch(
trackErrorDetails({
title: 'Failed to enqueue batch',
errorMessage: serializeError(result.error).message,
description: serializeError(result.error).stack?.toString() ?? null,
})
);
} else {
toast({
id: 'TOAST_PUBLISH_SUCCESSFUL',
@@ -249,7 +261,7 @@ const PublishWorkflowButton = memo(() => {
log.debug(parseify(result.value), 'Enqueued batch');
}
$isPublishing.set(false);
}, [enqueue, projectUrl, t]);
}, [enqueue, projectUrl, t, dispatch]);
const isDisabled = useMemo(() => {
return (

View File

@@ -21,6 +21,7 @@ import { buildSD3Graph } from 'features/nodes/util/graph/generation/buildSD3Grap
import { buildSDXLGraph } from 'features/nodes/util/graph/generation/buildSDXLGraph';
import type { GraphBuilderArg } from 'features/nodes/util/graph/types';
import { UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
import { trackErrorDetails } from 'features/system/store/actions';
import { toast } from 'features/toast/toast';
import { useCallback } from 'react';
import { serializeError } from 'serialize-error';
@@ -89,6 +90,7 @@ const enqueueCanvas = async (store: AppStore, canvasManager: CanvasManager, prep
}
const error = serializeError(buildGraphResult.error);
log.error({ error }, 'Failed to build graph');
dispatch(trackErrorDetails({ title, errorMessage: error.message, description }));
toast({
status,
title,

View File

@@ -19,6 +19,7 @@ import { buildSD3Graph } from 'features/nodes/util/graph/generation/buildSD3Grap
import { buildSDXLGraph } from 'features/nodes/util/graph/generation/buildSDXLGraph';
import type { GraphBuilderArg } from 'features/nodes/util/graph/types';
import { UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
import { trackErrorDetails } from 'features/system/store/actions';
import { toast } from 'features/toast/toast';
import { useCallback } from 'react';
import { serializeError } from 'serialize-error';
@@ -86,6 +87,7 @@ const enqueueGenerate = async (store: AppStore, prepend: boolean) => {
status = 'warning';
}
const error = serializeError(buildGraphResult.error);
dispatch(trackErrorDetails({ title, errorMessage: error.message, description }));
log.error({ error }, 'Failed to build graph');
toast({
status,

View File

@@ -1,10 +1,11 @@
import { useStore } from '@nanostores/react';
import { logger } from 'app/logging/logger';
import { useAppSelector } from 'app/store/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { withResultAsync } from 'common/util/result';
import { useIsWorkflowEditorLocked } from 'features/nodes/hooks/useIsWorkflowEditorLocked';
import { useEnqueueWorkflows } from 'features/queue/hooks/useEnqueueWorkflows';
import { $isReadyToEnqueue } from 'features/queue/store/readiness';
import { trackErrorDetails } from 'features/system/store/actions';
import { navigationApi } from 'features/ui/layouts/navigation-api';
import { VIEWER_PANEL_ID, WORKSPACE_PANEL_ID } from 'features/ui/layouts/shared';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
@@ -26,6 +27,7 @@ export const useInvoke = () => {
const enqueueCanvas = useEnqueueCanvas();
const enqueueGenerate = useEnqueueGenerate();
const enqueueUpscaling = useEnqueueUpscaling();
const dispatch = useAppDispatch();
const [_, { isLoading }] = useEnqueueBatchMutation({
...enqueueMutationFixedCacheKeyOptions,
@@ -55,9 +57,16 @@ export const useInvoke = () => {
if (result.isErr()) {
log.error({ error: serializeError(result.error) }, 'Failed to enqueue batch');
dispatch(
trackErrorDetails({
title: 'Failed to enqueue batch',
errorMessage: serializeError(result.error).message,
description: serializeError(result.error).stack?.toString() ?? null,
})
);
}
},
[enqueueCanvas, enqueueGenerate, enqueueUpscaling, enqueueWorkflows, isReady, tabName]
[enqueueCanvas, enqueueGenerate, enqueueUpscaling, enqueueWorkflows, isReady, tabName, dispatch]
);
const enqueueBack = useCallback(() => {

View File

@@ -2,3 +2,9 @@ import { createAction } from '@reduxjs/toolkit';
export const videoModalLinkClicked = createAction<string>('system/videoModalLinkClicked');
export const videoModalOpened = createAction('system/videoModalOpened');
export const trackErrorDetails = createAction<{
title: string;
errorMessage?: string;
description: string | null;
}>('system/trackErrorDetails');

View File

@@ -12,3 +12,9 @@ export const zPydanticValidationError = z.object({
),
}),
});
export const zPydanticValidationErrorWithDetail = z.object({
data: z.object({
detail: z.string(),
}),
});