Merge branch 'main' into release/make-web-dist-startable

This commit is contained in:
psychedelicious 2023-05-26 18:06:38 +10:00 committed by GitHub
commit 582f516fef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 647 additions and 302 deletions

View File

@ -8,8 +8,16 @@ import type { TypedStartListening, TypedAddListener } from '@reduxjs/toolkit';
import type { RootState, AppDispatch } from '../../store'; import type { RootState, AppDispatch } from '../../store';
import { addInitialImageSelectedListener } from './listeners/initialImageSelected'; import { addInitialImageSelectedListener } from './listeners/initialImageSelected';
import { addImageUploadedListener } from './listeners/imageUploaded'; import {
import { addRequestedImageDeletionListener } from './listeners/imageDeleted'; addImageUploadedFulfilledListener,
addImageUploadedRejectedListener,
} from './listeners/imageUploaded';
import {
addImageDeletedFulfilledListener,
addImageDeletedPendingListener,
addImageDeletedRejectedListener,
addRequestedImageDeletionListener,
} from './listeners/imageDeleted';
import { addUserInvokedCanvasListener } from './listeners/userInvokedCanvas'; import { addUserInvokedCanvasListener } from './listeners/userInvokedCanvas';
import { addUserInvokedNodesListener } from './listeners/userInvokedNodes'; import { addUserInvokedNodesListener } from './listeners/userInvokedNodes';
import { addUserInvokedTextToImageListener } from './listeners/userInvokedTextToImage'; import { addUserInvokedTextToImageListener } from './listeners/userInvokedTextToImage';
@ -28,6 +36,37 @@ import { addSocketDisconnectedListener } from './listeners/socketio/socketDiscon
import { addSocketSubscribedListener } from './listeners/socketio/socketSubscribed'; import { addSocketSubscribedListener } from './listeners/socketio/socketSubscribed';
import { addSocketUnsubscribedListener } from './listeners/socketio/socketUnsubscribed'; import { addSocketUnsubscribedListener } from './listeners/socketio/socketUnsubscribed';
import { addSessionReadyToInvokeListener } from './listeners/sessionReadyToInvoke'; import { addSessionReadyToInvokeListener } from './listeners/sessionReadyToInvoke';
import {
addImageMetadataReceivedFulfilledListener,
addImageMetadataReceivedRejectedListener,
} from './listeners/imageMetadataReceived';
import {
addImageUrlsReceivedFulfilledListener,
addImageUrlsReceivedRejectedListener,
} from './listeners/imageUrlsReceived';
import {
addSessionCreatedFulfilledListener,
addSessionCreatedPendingListener,
addSessionCreatedRejectedListener,
} from './listeners/sessionCreated';
import {
addSessionInvokedFulfilledListener,
addSessionInvokedPendingListener,
addSessionInvokedRejectedListener,
} from './listeners/sessionInvoked';
import {
addSessionCanceledFulfilledListener,
addSessionCanceledPendingListener,
addSessionCanceledRejectedListener,
} from './listeners/sessionCanceled';
import {
addReceivedResultImagesPageFulfilledListener,
addReceivedResultImagesPageRejectedListener,
} from './listeners/receivedResultImagesPage';
import {
addReceivedUploadImagesPageFulfilledListener,
addReceivedUploadImagesPageRejectedListener,
} from './listeners/receivedUploadImagesPage';
export const listenerMiddleware = createListenerMiddleware(); export const listenerMiddleware = createListenerMiddleware();
@ -47,23 +86,40 @@ export type AppListenerEffect = ListenerEffect<
AppDispatch AppDispatch
>; >;
addImageUploadedListener(); // Image uploaded
addInitialImageSelectedListener(); addImageUploadedFulfilledListener();
addRequestedImageDeletionListener(); addImageUploadedRejectedListener();
addInitialImageSelectedListener();
// Image deleted
addRequestedImageDeletionListener();
addImageDeletedPendingListener();
addImageDeletedFulfilledListener();
addImageDeletedRejectedListener();
// Image metadata
addImageMetadataReceivedFulfilledListener();
addImageMetadataReceivedRejectedListener();
// Image URLs
addImageUrlsReceivedFulfilledListener();
addImageUrlsReceivedRejectedListener();
// User Invoked
addUserInvokedCanvasListener(); addUserInvokedCanvasListener();
addUserInvokedNodesListener(); addUserInvokedNodesListener();
addUserInvokedTextToImageListener(); addUserInvokedTextToImageListener();
addUserInvokedImageToImageListener(); addUserInvokedImageToImageListener();
addSessionReadyToInvokeListener(); addSessionReadyToInvokeListener();
// Canvas actions
addCanvasSavedToGalleryListener(); addCanvasSavedToGalleryListener();
addCanvasDownloadedAsImageListener(); addCanvasDownloadedAsImageListener();
addCanvasCopiedToClipboardListener(); addCanvasCopiedToClipboardListener();
addCanvasMergedListener(); addCanvasMergedListener();
// socketio // socketio
addGeneratorProgressListener(); addGeneratorProgressListener();
addGraphExecutionStateCompleteListener(); addGraphExecutionStateCompleteListener();
addInvocationCompleteListener(); addInvocationCompleteListener();
@ -73,3 +129,24 @@ addSocketConnectedListener();
addSocketDisconnectedListener(); addSocketDisconnectedListener();
addSocketSubscribedListener(); addSocketSubscribedListener();
addSocketUnsubscribedListener(); addSocketUnsubscribedListener();
// Session Created
addSessionCreatedPendingListener();
addSessionCreatedFulfilledListener();
addSessionCreatedRejectedListener();
// Session Invoked
addSessionInvokedPendingListener();
addSessionInvokedFulfilledListener();
addSessionInvokedRejectedListener();
// Session Canceled
addSessionCanceledPendingListener();
addSessionCanceledFulfilledListener();
addSessionCanceledRejectedListener();
// Gallery pages
addReceivedResultImagesPageFulfilledListener();
addReceivedResultImagesPageRejectedListener();
addReceivedUploadImagesPageFulfilledListener();
addReceivedUploadImagesPageRejectedListener();

View File

@ -52,7 +52,6 @@ export const addCanvasMergedListener = () => {
dispatch( dispatch(
imageUploaded({ imageUploaded({
imageType: 'intermediates',
formData: { formData: {
file: new File([blob], filename, { type: 'image/png' }), file: new File([blob], filename, { type: 'image/png' }),
}, },
@ -65,7 +64,7 @@ export const addCanvasMergedListener = () => {
action.meta.arg.formData.file.name === filename action.meta.arg.formData.file.name === filename
); );
const mergedCanvasImage = payload.response; const mergedCanvasImage = payload;
dispatch( dispatch(
setMergedCanvas({ setMergedCanvas({

View File

@ -29,7 +29,6 @@ export const addCanvasSavedToGalleryListener = () => {
dispatch( dispatch(
imageUploaded({ imageUploaded({
imageType: 'results',
formData: { formData: {
file: new File([blob], 'mergedCanvas.png', { type: 'image/png' }), file: new File([blob], 'mergedCanvas.png', { type: 'image/png' }),
}, },

View File

@ -4,9 +4,14 @@ import { imageDeleted } from 'services/thunks/image';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { clamp } from 'lodash-es'; import { clamp } from 'lodash-es';
import { imageSelected } from 'features/gallery/store/gallerySlice'; import { imageSelected } from 'features/gallery/store/gallerySlice';
import { uploadsAdapter } from 'features/gallery/store/uploadsSlice';
import { resultsAdapter } from 'features/gallery/store/resultsSlice';
const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' }); const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' });
/**
* Called when the user requests an image deletion
*/
export const addRequestedImageDeletionListener = () => { export const addRequestedImageDeletionListener = () => {
startAppListening({ startAppListening({
actionCreator: requestedImageDeletion, actionCreator: requestedImageDeletion,
@ -19,11 +24,6 @@ export const addRequestedImageDeletionListener = () => {
const { image_name, image_type } = image; const { image_name, image_type } = image;
if (image_type !== 'uploads' && image_type !== 'results') {
moduleLog.warn({ data: image }, `Invalid image type ${image_type}`);
return;
}
const selectedImageName = getState().gallery.selectedImage?.image_name; const selectedImageName = getState().gallery.selectedImage?.image_name;
if (selectedImageName === image_name) { if (selectedImageName === image_name) {
@ -57,3 +57,49 @@ export const addRequestedImageDeletionListener = () => {
}, },
}); });
}; };
/**
* Called when the actual delete request is sent to the server
*/
export const addImageDeletedPendingListener = () => {
startAppListening({
actionCreator: imageDeleted.pending,
effect: (action, { dispatch, getState }) => {
const { imageName, imageType } = action.meta.arg;
// Preemptively remove the image from the gallery
if (imageType === 'uploads') {
uploadsAdapter.removeOne(getState().uploads, imageName);
}
if (imageType === 'results') {
resultsAdapter.removeOne(getState().results, imageName);
}
},
});
};
/**
* Called on successful delete
*/
export const addImageDeletedFulfilledListener = () => {
startAppListening({
actionCreator: imageDeleted.fulfilled,
effect: (action, { dispatch, getState }) => {
moduleLog.debug({ data: { image: action.meta.arg } }, 'Image deleted');
},
});
};
/**
* Called on failed delete
*/
export const addImageDeletedRejectedListener = () => {
startAppListening({
actionCreator: imageDeleted.rejected,
effect: (action, { dispatch, getState }) => {
moduleLog.debug(
{ data: { image: action.meta.arg } },
'Unable to delete image'
);
},
});
};

View File

@ -0,0 +1,43 @@
import { log } from 'app/logging/useLogger';
import { startAppListening } from '..';
import { imageMetadataReceived } from 'services/thunks/image';
import {
ResultsImageDTO,
resultUpserted,
} from 'features/gallery/store/resultsSlice';
import {
UploadsImageDTO,
uploadUpserted,
} from 'features/gallery/store/uploadsSlice';
const moduleLog = log.child({ namespace: 'image' });
export const addImageMetadataReceivedFulfilledListener = () => {
startAppListening({
actionCreator: imageMetadataReceived.fulfilled,
effect: (action, { getState, dispatch }) => {
const image = action.payload;
moduleLog.debug({ data: { image } }, 'Image metadata received');
if (image.image_type === 'results') {
dispatch(resultUpserted(action.payload as ResultsImageDTO));
}
if (image.image_type === 'uploads') {
dispatch(uploadUpserted(action.payload as UploadsImageDTO));
}
},
});
};
export const addImageMetadataReceivedRejectedListener = () => {
startAppListening({
actionCreator: imageMetadataReceived.rejected,
effect: (action, { getState, dispatch }) => {
moduleLog.debug(
{ data: { image: action.meta.arg } },
'Problem receiving image metadata'
);
},
});
};

View File

@ -1,25 +1,31 @@
import { startAppListening } from '..'; import { startAppListening } from '..';
import { uploadAdded } from 'features/gallery/store/uploadsSlice'; import { uploadUpserted } from 'features/gallery/store/uploadsSlice';
import { imageSelected } from 'features/gallery/store/gallerySlice'; import { imageSelected } from 'features/gallery/store/gallerySlice';
import { imageUploaded } from 'services/thunks/image'; import { imageUploaded } from 'services/thunks/image';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { initialImageSelected } from 'features/parameters/store/actions'; import { initialImageSelected } from 'features/parameters/store/actions';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { resultAdded } from 'features/gallery/store/resultsSlice'; import { resultUpserted } from 'features/gallery/store/resultsSlice';
import { isResultsImageDTO, isUploadsImageDTO } from 'services/types/guards'; import { isResultsImageDTO, isUploadsImageDTO } from 'services/types/guards';
import { log } from 'app/logging/useLogger';
export const addImageUploadedListener = () => { const moduleLog = log.child({ namespace: 'image' });
export const addImageUploadedFulfilledListener = () => {
startAppListening({ startAppListening({
predicate: (action): action is ReturnType<typeof imageUploaded.fulfilled> => predicate: (action): action is ReturnType<typeof imageUploaded.fulfilled> =>
imageUploaded.fulfilled.match(action) && imageUploaded.fulfilled.match(action) &&
action.payload.response.is_intermediate === false, action.payload.is_intermediate === false,
effect: (action, { dispatch, getState }) => { effect: (action, { dispatch, getState }) => {
const { response: image } = action.payload; const image = action.payload;
moduleLog.debug({ arg: '<Blob>', image }, 'Image uploaded');
const state = getState(); const state = getState();
// Handle uploads
if (isUploadsImageDTO(image)) { if (isUploadsImageDTO(image)) {
dispatch(uploadAdded(image)); dispatch(uploadUpserted(image));
dispatch(addToast({ title: 'Image Uploaded', status: 'success' })); dispatch(addToast({ title: 'Image Uploaded', status: 'success' }));
@ -36,9 +42,26 @@ export const addImageUploadedListener = () => {
} }
} }
// Handle results
// TODO: Can this ever happen? I don't think so...
if (isResultsImageDTO(image)) { if (isResultsImageDTO(image)) {
dispatch(resultAdded(image)); dispatch(resultUpserted(image));
} }
}, },
}); });
}; };
export const addImageUploadedRejectedListener = () => {
startAppListening({
actionCreator: imageUploaded.rejected,
effect: (action, { dispatch }) => {
dispatch(
addToast({
title: 'Image Upload Failed',
description: action.error.message,
status: 'error',
})
);
},
});
};

View File

@ -0,0 +1,51 @@
import { log } from 'app/logging/useLogger';
import { startAppListening } from '..';
import { imageUrlsReceived } from 'services/thunks/image';
import { resultsAdapter } from 'features/gallery/store/resultsSlice';
import { uploadsAdapter } from 'features/gallery/store/uploadsSlice';
const moduleLog = log.child({ namespace: 'image' });
export const addImageUrlsReceivedFulfilledListener = () => {
startAppListening({
actionCreator: imageUrlsReceived.fulfilled,
effect: (action, { getState, dispatch }) => {
const image = action.payload;
moduleLog.debug({ data: { image } }, 'Image URLs received');
const { image_type, image_name, image_url, thumbnail_url } = image;
if (image_type === 'results') {
resultsAdapter.updateOne(getState().results, {
id: image_name,
changes: {
image_url,
thumbnail_url,
},
});
}
if (image_type === 'uploads') {
uploadsAdapter.updateOne(getState().uploads, {
id: image_name,
changes: {
image_url,
thumbnail_url,
},
});
}
},
});
};
export const addImageUrlsReceivedRejectedListener = () => {
startAppListening({
actionCreator: imageUrlsReceived.rejected,
effect: (action, { getState, dispatch }) => {
moduleLog.debug(
{ data: { image: action.meta.arg } },
'Problem getting image URLs'
);
},
});
};

View File

@ -0,0 +1,33 @@
import { log } from 'app/logging/useLogger';
import { startAppListening } from '..';
import { receivedResultImagesPage } from 'services/thunks/gallery';
import { serializeError } from 'serialize-error';
const moduleLog = log.child({ namespace: 'gallery' });
export const addReceivedResultImagesPageFulfilledListener = () => {
startAppListening({
actionCreator: receivedResultImagesPage.fulfilled,
effect: (action, { getState, dispatch }) => {
const page = action.payload;
moduleLog.debug(
{ data: { page } },
`Received ${page.items.length} results`
);
},
});
};
export const addReceivedResultImagesPageRejectedListener = () => {
startAppListening({
actionCreator: receivedResultImagesPage.rejected,
effect: (action, { getState, dispatch }) => {
if (action.payload) {
moduleLog.debug(
{ data: { error: serializeError(action.payload.error) } },
'Problem receiving results'
);
}
},
});
};

View File

@ -0,0 +1,33 @@
import { log } from 'app/logging/useLogger';
import { startAppListening } from '..';
import { receivedUploadImagesPage } from 'services/thunks/gallery';
import { serializeError } from 'serialize-error';
const moduleLog = log.child({ namespace: 'gallery' });
export const addReceivedUploadImagesPageFulfilledListener = () => {
startAppListening({
actionCreator: receivedUploadImagesPage.fulfilled,
effect: (action, { getState, dispatch }) => {
const page = action.payload;
moduleLog.debug(
{ data: { page } },
`Received ${page.items.length} uploads`
);
},
});
};
export const addReceivedUploadImagesPageRejectedListener = () => {
startAppListening({
actionCreator: receivedUploadImagesPage.rejected,
effect: (action, { getState, dispatch }) => {
if (action.payload) {
moduleLog.debug(
{ data: { error: serializeError(action.payload.error) } },
'Problem receiving uploads'
);
}
},
});
};

View File

@ -0,0 +1,48 @@
import { log } from 'app/logging/useLogger';
import { startAppListening } from '..';
import { sessionCanceled } from 'services/thunks/session';
import { serializeError } from 'serialize-error';
const moduleLog = log.child({ namespace: 'session' });
export const addSessionCanceledPendingListener = () => {
startAppListening({
actionCreator: sessionCanceled.pending,
effect: (action, { getState, dispatch }) => {
//
},
});
};
export const addSessionCanceledFulfilledListener = () => {
startAppListening({
actionCreator: sessionCanceled.fulfilled,
effect: (action, { getState, dispatch }) => {
const { sessionId } = action.meta.arg;
moduleLog.debug(
{ data: { sessionId } },
`Session canceled (${sessionId})`
);
},
});
};
export const addSessionCanceledRejectedListener = () => {
startAppListening({
actionCreator: sessionCanceled.rejected,
effect: (action, { getState, dispatch }) => {
if (action.payload) {
const { arg, error } = action.payload;
moduleLog.error(
{
data: {
arg,
error: serializeError(error),
},
},
`Problem canceling session`
);
}
},
});
};

View File

@ -0,0 +1,45 @@
import { log } from 'app/logging/useLogger';
import { startAppListening } from '..';
import { sessionCreated } from 'services/thunks/session';
import { serializeError } from 'serialize-error';
const moduleLog = log.child({ namespace: 'session' });
export const addSessionCreatedPendingListener = () => {
startAppListening({
actionCreator: sessionCreated.pending,
effect: (action, { getState, dispatch }) => {
//
},
});
};
export const addSessionCreatedFulfilledListener = () => {
startAppListening({
actionCreator: sessionCreated.fulfilled,
effect: (action, { getState, dispatch }) => {
const session = action.payload;
moduleLog.debug({ data: { session } }, `Session created (${session.id})`);
},
});
};
export const addSessionCreatedRejectedListener = () => {
startAppListening({
actionCreator: sessionCreated.rejected,
effect: (action, { getState, dispatch }) => {
if (action.payload) {
const { arg, error } = action.payload;
moduleLog.error(
{
data: {
arg,
error: serializeError(error),
},
},
`Problem creating session`
);
}
},
});
};

View File

@ -0,0 +1,48 @@
import { log } from 'app/logging/useLogger';
import { startAppListening } from '..';
import { sessionInvoked } from 'services/thunks/session';
import { serializeError } from 'serialize-error';
const moduleLog = log.child({ namespace: 'session' });
export const addSessionInvokedPendingListener = () => {
startAppListening({
actionCreator: sessionInvoked.pending,
effect: (action, { getState, dispatch }) => {
//
},
});
};
export const addSessionInvokedFulfilledListener = () => {
startAppListening({
actionCreator: sessionInvoked.fulfilled,
effect: (action, { getState, dispatch }) => {
const { sessionId } = action.meta.arg;
moduleLog.debug(
{ data: { sessionId } },
`Session invoked (${sessionId})`
);
},
});
};
export const addSessionInvokedRejectedListener = () => {
startAppListening({
actionCreator: sessionInvoked.rejected,
effect: (action, { getState, dispatch }) => {
if (action.payload) {
const { arg, error } = action.payload;
moduleLog.error(
{
data: {
arg,
error: serializeError(error),
},
},
`Problem invoking session`
);
}
},
});
};

View File

@ -3,7 +3,7 @@ import { sessionInvoked } from 'services/thunks/session';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { sessionReadyToInvoke } from 'features/system/store/actions'; import { sessionReadyToInvoke } from 'features/system/store/actions';
const moduleLog = log.child({ namespace: 'invoke' }); const moduleLog = log.child({ namespace: 'session' });
export const addSessionReadyToInvokeListener = () => { export const addSessionReadyToInvokeListener = () => {
startAppListening({ startAppListening({
@ -11,7 +11,10 @@ export const addSessionReadyToInvokeListener = () => {
effect: (action, { getState, dispatch }) => { effect: (action, { getState, dispatch }) => {
const { sessionId } = getState().system; const { sessionId } = getState().system;
if (sessionId) { if (sessionId) {
moduleLog.info({ sessionId }, `Session invoked (${sessionId})})`); moduleLog.debug(
{ sessionId },
`Session ready to invoke (${sessionId})})`
);
dispatch(sessionInvoked({ sessionId })); dispatch(sessionInvoked({ sessionId }));
} }
}, },

View File

@ -10,7 +10,7 @@ export const addGraphExecutionStateCompleteListener = () => {
effect: (action, { dispatch, getState }) => { effect: (action, { dispatch, getState }) => {
moduleLog.debug( moduleLog.debug(
action.payload, action.payload,
`Graph execution state complete (${action.payload.data.graph_execution_state_id})` `Session invocation complete (${action.payload.data.graph_execution_state_id})`
); );
}, },
}); });

View File

@ -2,13 +2,11 @@ import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { invocationComplete } from 'services/events/actions'; import { invocationComplete } from 'services/events/actions';
import { import { imageMetadataReceived } from 'services/thunks/image';
imageMetadataReceived,
imageUrlsReceived,
} from 'services/thunks/image';
import { sessionCanceled } from 'services/thunks/session'; import { sessionCanceled } from 'services/thunks/session';
import { isImageOutput } from 'services/types/guards'; import { isImageOutput } from 'services/types/guards';
import { progressImageSet } from 'features/system/store/systemSlice'; import { progressImageSet } from 'features/system/store/systemSlice';
import { imageSelected } from 'features/gallery/store/gallerySlice';
const moduleLog = log.child({ namespace: 'socketio' }); const moduleLog = log.child({ namespace: 'socketio' });
const nodeDenylist = ['dataURL_image']; const nodeDenylist = ['dataURL_image'];
@ -17,7 +15,7 @@ export const addInvocationCompleteListener = () => {
startAppListening({ startAppListening({
actionCreator: invocationComplete, actionCreator: invocationComplete,
effect: async (action, { dispatch, getState, take }) => { effect: async (action, { dispatch, getState, take }) => {
moduleLog.info( moduleLog.debug(
action.payload, action.payload,
`Invocation complete (${action.payload.data.node.type})` `Invocation complete (${action.payload.data.node.type})`
); );
@ -46,6 +44,14 @@ export const addInvocationCompleteListener = () => {
}) })
); );
const [{ payload: imageDTO }] = await take(
imageMetadataReceived.fulfilled.match
);
if (getState().gallery.shouldAutoSwitchToNewImages) {
dispatch(imageSelected(imageDTO));
}
// Handle canvas image // Handle canvas image
if ( if (
graph_execution_state_id === graph_execution_state_id ===

View File

@ -8,7 +8,7 @@ export const addInvocationErrorListener = () => {
startAppListening({ startAppListening({
actionCreator: invocationError, actionCreator: invocationError,
effect: (action, { dispatch, getState }) => { effect: (action, { dispatch, getState }) => {
moduleLog.debug( moduleLog.error(
action.payload, action.payload,
`Invocation error (${action.payload.data.node.type})` `Invocation error (${action.payload.data.node.type})`
); );

View File

@ -19,7 +19,7 @@ export const addInvocationStartedListener = () => {
return; return;
} }
moduleLog.info( moduleLog.debug(
action.payload, action.payload,
`Invocation started (${action.payload.data.node.type})` `Invocation started (${action.payload.data.node.type})`
); );

View File

@ -91,7 +91,7 @@ export const addUserInvokedCanvasListener = () => {
dispatch(canvasGraphBuilt(graph)); dispatch(canvasGraphBuilt(graph));
moduleLog({ data: graph }, 'Canvas graph built'); moduleLog.debug({ data: graph }, 'Canvas graph built');
// If we are generating img2img or inpaint, we need to upload the init images // If we are generating img2img or inpaint, we need to upload the init images
if (baseNode.type === 'img2img' || baseNode.type === 'inpaint') { if (baseNode.type === 'img2img' || baseNode.type === 'inpaint') {
@ -106,19 +106,16 @@ export const addUserInvokedCanvasListener = () => {
); );
// Wait for the image to be uploaded // Wait for the image to be uploaded
const [{ payload: basePayload }] = await take( const [{ payload: baseImageDTO }] = await take(
(action): action is ReturnType<typeof imageUploaded.fulfilled> => (action): action is ReturnType<typeof imageUploaded.fulfilled> =>
imageUploaded.fulfilled.match(action) && imageUploaded.fulfilled.match(action) &&
action.meta.arg.formData.file.name === baseFilename action.meta.arg.formData.file.name === baseFilename
); );
// Update the base node with the image name and type // Update the base node with the image name and type
const { image_name: baseName, image_type: baseType } =
basePayload.response;
baseNode.image = { baseNode.image = {
image_name: baseName, image_name: baseImageDTO.image_name,
image_type: baseType, image_type: baseImageDTO.image_type,
}; };
} }
@ -135,19 +132,16 @@ export const addUserInvokedCanvasListener = () => {
); );
// Wait for the mask to be uploaded // Wait for the mask to be uploaded
const [{ payload: maskPayload }] = await take( const [{ payload: maskImageDTO }] = await take(
(action): action is ReturnType<typeof imageUploaded.fulfilled> => (action): action is ReturnType<typeof imageUploaded.fulfilled> =>
imageUploaded.fulfilled.match(action) && imageUploaded.fulfilled.match(action) &&
action.meta.arg.formData.file.name === maskFilename action.meta.arg.formData.file.name === maskFilename
); );
// Update the base node with the image name and type // Update the base node with the image name and type
const { image_name: maskName, image_type: maskType } =
maskPayload.response;
baseNode.mask = { baseNode.mask = {
image_name: maskName, image_name: maskImageDTO.image_name,
image_type: maskType, image_type: maskImageDTO.image_type,
}; };
} }

View File

@ -17,7 +17,7 @@ export const addUserInvokedImageToImageListener = () => {
const graph = buildImageToImageGraph(state); const graph = buildImageToImageGraph(state);
dispatch(imageToImageGraphBuilt(graph)); dispatch(imageToImageGraphBuilt(graph));
moduleLog({ data: graph }, 'Image to Image graph built'); moduleLog.debug({ data: graph }, 'Image to Image graph built');
dispatch(sessionCreated({ graph })); dispatch(sessionCreated({ graph }));

View File

@ -17,7 +17,7 @@ export const addUserInvokedNodesListener = () => {
const graph = buildNodesGraph(state); const graph = buildNodesGraph(state);
dispatch(nodesGraphBuilt(graph)); dispatch(nodesGraphBuilt(graph));
moduleLog({ data: graph }, 'Nodes graph built'); moduleLog.debug({ data: graph }, 'Nodes graph built');
dispatch(sessionCreated({ graph })); dispatch(sessionCreated({ graph }));

View File

@ -19,7 +19,7 @@ export const addUserInvokedTextToImageListener = () => {
dispatch(textToImageGraphBuilt(graph)); dispatch(textToImageGraphBuilt(graph));
moduleLog({ data: graph }, 'Text to Image graph built'); moduleLog.debug({ data: graph }, 'Text to Image graph built');
dispatch(sessionCreated({ graph })); dispatch(sessionCreated({ graph }));

View File

@ -68,7 +68,6 @@ const ImageUploader = (props: ImageUploaderProps) => {
async (file: File) => { async (file: File) => {
dispatch( dispatch(
imageUploaded({ imageUploaded({
imageType: 'uploads',
formData: { file }, formData: { file },
activeTabName, activeTabName,
}) })

View File

@ -1,14 +1,13 @@
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; import {
PayloadAction,
createEntityAdapter,
createSlice,
} from '@reduxjs/toolkit';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { import {
receivedResultImagesPage, receivedResultImagesPage,
IMAGES_PER_PAGE, IMAGES_PER_PAGE,
} from 'services/thunks/gallery'; } from 'services/thunks/gallery';
import {
imageDeleted,
imageMetadataReceived,
imageUrlsReceived,
} from 'services/thunks/image';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api';
import { dateComparator } from 'common/util/dateComparator'; import { dateComparator } from 'common/util/dateComparator';
@ -26,6 +25,7 @@ type AdditionalResultsState = {
pages: number; pages: number;
isLoading: boolean; isLoading: boolean;
nextPage: number; nextPage: number;
upsertedImageCount: number;
}; };
export const initialResultsState = export const initialResultsState =
@ -34,6 +34,7 @@ export const initialResultsState =
pages: 0, pages: 0,
isLoading: false, isLoading: false,
nextPage: 0, nextPage: 0,
upsertedImageCount: 0,
}); });
export type ResultsState = typeof initialResultsState; export type ResultsState = typeof initialResultsState;
@ -42,7 +43,10 @@ const resultsSlice = createSlice({
name: 'results', name: 'results',
initialState: initialResultsState, initialState: initialResultsState,
reducers: { reducers: {
resultAdded: resultsAdapter.upsertOne, resultUpserted: (state, action: PayloadAction<ResultsImageDTO>) => {
resultsAdapter.upsertOne(state, action.payload);
state.upsertedImageCount += 1;
},
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
/** /**
@ -68,47 +72,6 @@ const resultsSlice = createSlice({
state.nextPage = items.length < IMAGES_PER_PAGE ? page : page + 1; state.nextPage = items.length < IMAGES_PER_PAGE ? page : page + 1;
state.isLoading = false; state.isLoading = false;
}); });
/**
* Image Metadata Received - FULFILLED
*/
builder.addCase(imageMetadataReceived.fulfilled, (state, action) => {
const { image_type } = action.payload;
if (image_type === 'results') {
resultsAdapter.upsertOne(state, action.payload as ResultsImageDTO);
}
});
/**
* Image URLs Received - FULFILLED
*/
builder.addCase(imageUrlsReceived.fulfilled, (state, action) => {
const { image_name, image_type, image_url, thumbnail_url } =
action.payload;
if (image_type === 'results') {
resultsAdapter.updateOne(state, {
id: image_name,
changes: {
image_url: image_url,
thumbnail_url: thumbnail_url,
},
});
}
});
/**
* Delete Image - PENDING
* Pre-emptively remove the image from the gallery
*/
builder.addCase(imageDeleted.pending, (state, action) => {
const { imageType, imageName } = action.meta.arg;
if (imageType === 'results') {
resultsAdapter.removeOne(state, imageName);
}
});
}, },
}); });
@ -120,6 +83,6 @@ export const {
selectTotal: selectResultsTotal, selectTotal: selectResultsTotal,
} = resultsAdapter.getSelectors<RootState>((state) => state.results); } = resultsAdapter.getSelectors<RootState>((state) => state.results);
export const { resultAdded } = resultsSlice.actions; export const { resultUpserted } = resultsSlice.actions;
export default resultsSlice.reducer; export default resultsSlice.reducer;

View File

@ -1,11 +1,14 @@
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; import {
PayloadAction,
createEntityAdapter,
createSlice,
} from '@reduxjs/toolkit';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { import {
receivedUploadImagesPage, receivedUploadImagesPage,
IMAGES_PER_PAGE, IMAGES_PER_PAGE,
} from 'services/thunks/gallery'; } from 'services/thunks/gallery';
import { imageDeleted, imageUrlsReceived } from 'services/thunks/image';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api';
import { dateComparator } from 'common/util/dateComparator'; import { dateComparator } from 'common/util/dateComparator';
@ -23,6 +26,7 @@ type AdditionalUploadsState = {
pages: number; pages: number;
isLoading: boolean; isLoading: boolean;
nextPage: number; nextPage: number;
upsertedImageCount: number;
}; };
export const initialUploadsState = export const initialUploadsState =
@ -31,6 +35,7 @@ export const initialUploadsState =
pages: 0, pages: 0,
nextPage: 0, nextPage: 0,
isLoading: false, isLoading: false,
upsertedImageCount: 0,
}); });
export type UploadsState = typeof initialUploadsState; export type UploadsState = typeof initialUploadsState;
@ -39,7 +44,10 @@ const uploadsSlice = createSlice({
name: 'uploads', name: 'uploads',
initialState: initialUploadsState, initialState: initialUploadsState,
reducers: { reducers: {
uploadAdded: uploadsAdapter.upsertOne, uploadUpserted: (state, action: PayloadAction<UploadsImageDTO>) => {
uploadsAdapter.upsertOne(state, action.payload);
state.upsertedImageCount += 1;
},
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
/** /**
@ -65,36 +73,6 @@ const uploadsSlice = createSlice({
state.nextPage = items.length < IMAGES_PER_PAGE ? page : page + 1; state.nextPage = items.length < IMAGES_PER_PAGE ? page : page + 1;
state.isLoading = false; state.isLoading = false;
}); });
/**
* Image URLs Received - FULFILLED
*/
builder.addCase(imageUrlsReceived.fulfilled, (state, action) => {
const { image_name, image_type, image_url, thumbnail_url } =
action.payload;
if (image_type === 'uploads') {
uploadsAdapter.updateOne(state, {
id: image_name,
changes: {
image_url: image_url,
thumbnail_url: thumbnail_url,
},
});
}
});
/**
* Delete Image - pending
* Pre-emptively remove the image from the gallery
*/
builder.addCase(imageDeleted.pending, (state, action) => {
const { imageType, imageName } = action.meta.arg;
if (imageType === 'uploads') {
uploadsAdapter.removeOne(state, imageName);
}
});
}, },
}); });
@ -106,6 +84,6 @@ export const {
selectTotal: selectUploadsTotal, selectTotal: selectUploadsTotal,
} = uploadsAdapter.getSelectors<RootState>((state) => state.uploads); } = uploadsAdapter.getSelectors<RootState>((state) => state.uploads);
export const { uploadAdded } = uploadsSlice.actions; export const { uploadUpserted } = uploadsSlice.actions;
export default uploadsSlice.reducer; export default uploadsSlice.reducer;

View File

@ -1,5 +1,5 @@
import { UseToastOptions } from '@chakra-ui/react'; import { UseToastOptions } from '@chakra-ui/react';
import type { PayloadAction } from '@reduxjs/toolkit'; import { PayloadAction, isAnyOf } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/types/invokeai'; import * as InvokeAI from 'app/types/invokeai';
import { import {
@ -16,7 +16,11 @@ import {
import { ProgressImage } from 'services/events/types'; import { ProgressImage } from 'services/events/types';
import { makeToast } from '../../../app/components/Toaster'; import { makeToast } from '../../../app/components/Toaster';
import { sessionCanceled, sessionInvoked } from 'services/thunks/session'; import {
sessionCanceled,
sessionCreated,
sessionInvoked,
} from 'services/thunks/session';
import { receivedModels } from 'services/thunks/model'; import { receivedModels } from 'services/thunks/model';
import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice'; import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice';
import { LogLevelName } from 'roarr'; import { LogLevelName } from 'roarr';
@ -345,15 +349,8 @@ export const systemSlice = createSlice({
state.statusTranslationKey = 'common.statusPreparing'; state.statusTranslationKey = 'common.statusPreparing';
}); });
builder.addCase(sessionInvoked.rejected, (state, action) => {
const error = action.payload as string | undefined;
state.toastQueue.push(
makeToast({ title: error || t('toast.serverError'), status: 'error' })
);
});
/** /**
* Session Canceled * Session Canceled - FULFILLED
*/ */
builder.addCase(sessionCanceled.fulfilled, (state, action) => { builder.addCase(sessionCanceled.fulfilled, (state, action) => {
state.canceledSession = action.meta.arg.sessionId; state.canceledSession = action.meta.arg.sessionId;
@ -416,6 +413,26 @@ export const systemSlice = createSlice({
builder.addCase(imageUploaded.fulfilled, (state) => { builder.addCase(imageUploaded.fulfilled, (state) => {
state.isUploading = false; state.isUploading = false;
}); });
// *** Matchers - must be after all cases ***
/**
* Session Invoked - REJECTED
* Session Created - REJECTED
*/
builder.addMatcher(isAnySessionRejected, (state, action) => {
state.isProcessing = false;
state.isCancelable = false;
state.isCancelScheduled = false;
state.currentStep = 0;
state.totalSteps = 0;
state.statusTranslationKey = 'common.statusConnected';
state.progressImage = null;
state.toastQueue.push(
makeToast({ title: t('toast.serverError'), status: 'error' })
);
});
}, },
}); });
@ -444,3 +461,8 @@ export const {
} = systemSlice.actions; } = systemSlice.actions;
export default systemSlice.reducer; export default systemSlice.reducer;
const isAnySessionRejected = isAnyOf(
sessionCreated.rejected,
sessionInvoked.rejected
);

View File

@ -8,11 +8,7 @@ import {
import { socketSubscribed, socketUnsubscribed } from './actions'; import { socketSubscribed, socketUnsubscribed } from './actions';
import { AppThunkDispatch, RootState } from 'app/store/store'; import { AppThunkDispatch, RootState } from 'app/store/store';
import { getTimestamp } from 'common/util/getTimestamp'; import { getTimestamp } from 'common/util/getTimestamp';
import { import { sessionCreated } from 'services/thunks/session';
sessionInvoked,
sessionCreated,
sessionWithoutGraphCreated,
} from 'services/thunks/session';
import { OpenAPI } from 'services/api'; import { OpenAPI } from 'services/api';
import { setEventListeners } from 'services/events/util/setEventListeners'; import { setEventListeners } from 'services/events/util/setEventListeners';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
@ -66,10 +62,7 @@ export const socketMiddleware = () => {
socket.connect(); socket.connect();
} }
if ( if (sessionCreated.fulfilled.match(action)) {
sessionCreated.fulfilled.match(action) ||
sessionWithoutGraphCreated.fulfilled.match(action)
) {
const sessionId = action.payload.id; const sessionId = action.payload.id;
const oldSessionId = getState().system.sessionId; const oldSessionId = getState().system.sessionId;

View File

@ -1,51 +1,64 @@
import { log } from 'app/logging/useLogger';
import { createAppAsyncThunk } from 'app/store/storeUtils'; import { createAppAsyncThunk } from 'app/store/storeUtils';
import { ImagesService } from 'services/api'; import { ImagesService, PaginatedResults_ImageDTO_ } from 'services/api';
export const IMAGES_PER_PAGE = 20; export const IMAGES_PER_PAGE = 20;
const galleryLog = log.child({ namespace: 'gallery' }); type ReceivedResultImagesPageThunkConfig = {
rejectValue: {
error: unknown;
};
};
export const receivedResultImagesPage = createAppAsyncThunk( export const receivedResultImagesPage = createAppAsyncThunk<
PaginatedResults_ImageDTO_,
void,
ReceivedResultImagesPageThunkConfig
>(
'results/receivedResultImagesPage', 'results/receivedResultImagesPage',
async (_arg, { getState, rejectWithValue }) => { async (_arg, { getState, rejectWithValue }) => {
const { page, pages, nextPage } = getState().results; const { page, pages, nextPage, upsertedImageCount } = getState().results;
if (nextPage === page) { // If many images have been upserted, we need to offset the page number
return rejectWithValue([]); // TODO: add an offset param to the list images endpoint
} const pageOffset = Math.floor(upsertedImageCount / IMAGES_PER_PAGE);
const response = await ImagesService.listImagesWithMetadata({ const response = await ImagesService.listImagesWithMetadata({
imageType: 'results', imageType: 'results',
imageCategory: 'general', imageCategory: 'general',
page: getState().results.nextPage, page: nextPage + pageOffset,
perPage: IMAGES_PER_PAGE, perPage: IMAGES_PER_PAGE,
}); });
galleryLog.info({ response }, `Received ${response.items.length} results`);
return response; return response;
} }
); );
export const receivedUploadImagesPage = createAppAsyncThunk( type ReceivedUploadImagesPageThunkConfig = {
rejectValue: {
error: unknown;
};
};
export const receivedUploadImagesPage = createAppAsyncThunk<
PaginatedResults_ImageDTO_,
void,
ReceivedUploadImagesPageThunkConfig
>(
'uploads/receivedUploadImagesPage', 'uploads/receivedUploadImagesPage',
async (_arg, { getState, rejectWithValue }) => { async (_arg, { getState, rejectWithValue }) => {
const { page, pages, nextPage } = getState().uploads; const { page, pages, nextPage, upsertedImageCount } = getState().uploads;
if (nextPage === page) { // If many images have been upserted, we need to offset the page number
return rejectWithValue([]); // TODO: add an offset param to the list images endpoint
} const pageOffset = Math.floor(upsertedImageCount / IMAGES_PER_PAGE);
const response = await ImagesService.listImagesWithMetadata({ const response = await ImagesService.listImagesWithMetadata({
imageType: 'uploads', imageType: 'uploads',
imageCategory: 'general', imageCategory: 'general',
page: getState().uploads.nextPage, page: nextPage + pageOffset,
perPage: IMAGES_PER_PAGE, perPage: IMAGES_PER_PAGE,
}); });
galleryLog.info({ response }, `Received ${response.items.length} uploads`);
return response; return response;
} }
); );

View File

@ -1,10 +1,6 @@
import { log } from 'app/logging/useLogger';
import { createAppAsyncThunk } from 'app/store/storeUtils'; import { createAppAsyncThunk } from 'app/store/storeUtils';
import { InvokeTabName } from 'features/ui/store/tabMap'; import { InvokeTabName } from 'features/ui/store/tabMap';
import { ImagesService } from 'services/api'; import { ImagesService } from 'services/api';
import { getHeaders } from 'services/util/getHeaders';
const imagesLog = log.child({ namespace: 'image' });
type imageUrlsReceivedArg = Parameters< type imageUrlsReceivedArg = Parameters<
(typeof ImagesService)['getImageUrls'] (typeof ImagesService)['getImageUrls']
@ -17,7 +13,6 @@ export const imageUrlsReceived = createAppAsyncThunk(
'api/imageUrlsReceived', 'api/imageUrlsReceived',
async (arg: imageUrlsReceivedArg) => { async (arg: imageUrlsReceivedArg) => {
const response = await ImagesService.getImageUrls(arg); const response = await ImagesService.getImageUrls(arg);
imagesLog.info({ arg, response }, 'Received image urls');
return response; return response;
} }
); );
@ -33,7 +28,6 @@ export const imageMetadataReceived = createAppAsyncThunk(
'api/imageMetadataReceived', 'api/imageMetadataReceived',
async (arg: imageMetadataReceivedArg) => { async (arg: imageMetadataReceivedArg) => {
const response = await ImagesService.getImageMetadata(arg); const response = await ImagesService.getImageMetadata(arg);
imagesLog.info({ arg, response }, 'Received image record');
return response; return response;
} }
); );
@ -53,11 +47,7 @@ export const imageUploaded = createAppAsyncThunk(
// strip out `activeTabName` from arg - the route does not need it // strip out `activeTabName` from arg - the route does not need it
const { activeTabName, ...rest } = arg; const { activeTabName, ...rest } = arg;
const response = await ImagesService.uploadImage(rest); const response = await ImagesService.uploadImage(rest);
const { location } = getHeaders(response); return response;
imagesLog.debug({ arg: '<Blob>', response, location }, 'Image uploaded');
return { response, location };
} }
); );
@ -70,9 +60,6 @@ export const imageDeleted = createAppAsyncThunk(
'api/imageDeleted', 'api/imageDeleted',
async (arg: ImageDeletedArg) => { async (arg: ImageDeletedArg) => {
const response = await ImagesService.deleteImage(arg); const response = await ImagesService.deleteImage(arg);
imagesLog.debug({ arg, response }, 'Image deleted');
return response; return response;
} }
); );
@ -80,15 +67,12 @@ export const imageDeleted = createAppAsyncThunk(
type ImageUpdatedArg = Parameters<(typeof ImagesService)['updateImage']>[0]; type ImageUpdatedArg = Parameters<(typeof ImagesService)['updateImage']>[0];
/** /**
* `ImagesService.deleteImage()` thunk * `ImagesService.updateImage()` thunk
*/ */
export const imageUpdated = createAppAsyncThunk( export const imageUpdated = createAppAsyncThunk(
'api/imageUpdated', 'api/imageUpdated',
async (arg: ImageUpdatedArg) => { async (arg: ImageUpdatedArg) => {
const response = await ImagesService.updateImage(arg); const response = await ImagesService.updateImage(arg);
imagesLog.debug({ arg, response }, 'Image updated');
return response; return response;
} }
); );

View File

@ -1,7 +1,7 @@
import { createAppAsyncThunk } from 'app/store/storeUtils'; import { createAppAsyncThunk } from 'app/store/storeUtils';
import { SessionsService } from 'services/api'; import { GraphExecutionState, SessionsService } from 'services/api';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { serializeError } from 'serialize-error'; import { isObject } from 'lodash-es';
const sessionLog = log.child({ namespace: 'session' }); const sessionLog = log.child({ namespace: 'session' });
@ -11,103 +11,48 @@ type SessionCreatedArg = {
>[0]['requestBody']; >[0]['requestBody'];
}; };
type SessionCreatedThunkConfig = {
rejectValue: { arg: SessionCreatedArg; error: unknown };
};
/** /**
* `SessionsService.createSession()` thunk * `SessionsService.createSession()` thunk
*/ */
export const sessionCreated = createAppAsyncThunk( export const sessionCreated = createAppAsyncThunk<
'api/sessionCreated', GraphExecutionState,
async (arg: SessionCreatedArg, { rejectWithValue }) => { SessionCreatedArg,
SessionCreatedThunkConfig
>('api/sessionCreated', async (arg, { rejectWithValue }) => {
try { try {
const response = await SessionsService.createSession({ const response = await SessionsService.createSession({
requestBody: arg.graph, requestBody: arg.graph,
}); });
sessionLog.info({ arg, response }, `Session created (${response.id})`);
return response; return response;
} catch (err: any) { } catch (error) {
sessionLog.error( return rejectWithValue({ arg, error });
{
error: serializeError(err),
},
'Problem creating session'
);
return rejectWithValue(err.message);
} }
} });
);
/** type SessionInvokedArg = { sessionId: string };
* `SessionsService.createSession()` without graph thunk
*/
export const sessionWithoutGraphCreated = createAppAsyncThunk(
'api/sessionWithoutGraphCreated',
async (_, { rejectWithValue }) => {
try {
const response = await SessionsService.createSession({});
sessionLog.info({ response }, `Session created (${response.id})`);
return response;
} catch (err: any) {
sessionLog.error(
{
error: serializeError(err),
},
'Problem creating session'
);
return rejectWithValue(err.message);
}
}
);
type NodeAddedArg = Parameters<(typeof SessionsService)['addNode']>[0]; type SessionInvokedThunkConfig = {
rejectValue: {
arg: SessionInvokedArg;
error: unknown;
};
};
/** const isErrorWithStatus = (error: unknown): error is { status: number } =>
* `SessionsService.addNode()` thunk isObject(error) && 'status' in error;
*/
export const nodeAdded = createAppAsyncThunk(
'api/nodeAdded',
async (
arg: { node: NodeAddedArg['requestBody']; sessionId: string },
_thunkApi
) => {
const response = await SessionsService.addNode({
requestBody: arg.node,
sessionId: arg.sessionId,
});
sessionLog.info({ arg, response }, `Node added (${response})`);
return response;
}
);
type NodeUpdatedArg = Parameters<(typeof SessionsService)['updateNode']>[0];
/**
* `SessionsService.addNode()` thunk
*/
export const nodeUpdated = createAppAsyncThunk(
'api/nodeUpdated',
async (
arg: { node: NodeUpdatedArg['requestBody']; sessionId: string },
_thunkApi
) => {
const response = await SessionsService.updateNode({
requestBody: arg.node,
sessionId: arg.sessionId,
nodePath: arg.node.id,
});
sessionLog.info({ arg, response }, `Node updated (${response})`);
return response;
}
);
/** /**
* `SessionsService.invokeSession()` thunk * `SessionsService.invokeSession()` thunk
*/ */
export const sessionInvoked = createAppAsyncThunk( export const sessionInvoked = createAppAsyncThunk<
'api/sessionInvoked', void,
async (arg: { sessionId: string }, { rejectWithValue }) => { SessionInvokedArg,
SessionInvokedThunkConfig
>('api/sessionInvoked', async (arg, { rejectWithValue }) => {
const { sessionId } = arg; const { sessionId } = arg;
try { try {
@ -115,40 +60,40 @@ export const sessionInvoked = createAppAsyncThunk(
sessionId, sessionId,
all: true, all: true,
}); });
sessionLog.info({ arg, response }, `Session invoked (${sessionId})`);
return response; return response;
} catch (error) { } catch (error) {
const err = error as any; if (isErrorWithStatus(error) && error.status === 403) {
if (err.status === 403) { return rejectWithValue({ arg, error: (error as any).body.detail });
return rejectWithValue(err.body.detail);
} }
throw error; return rejectWithValue({ arg, error });
} }
} });
);
type SessionCanceledArg = Parameters< type SessionCanceledArg = Parameters<
(typeof SessionsService)['cancelSessionInvoke'] (typeof SessionsService)['cancelSessionInvoke']
>[0]; >[0];
type SessionCanceledThunkConfig = {
rejectValue: {
arg: SessionCanceledArg;
error: unknown;
};
};
/** /**
* `SessionsService.cancelSession()` thunk * `SessionsService.cancelSession()` thunk
*/ */
export const sessionCanceled = createAppAsyncThunk( export const sessionCanceled = createAppAsyncThunk<
'api/sessionCanceled', void,
async (arg: SessionCanceledArg, _thunkApi) => { SessionCanceledArg,
SessionCanceledThunkConfig
>('api/sessionCanceled', async (arg: SessionCanceledArg, _thunkApi) => {
const { sessionId } = arg; const { sessionId } = arg;
const response = await SessionsService.cancelSessionInvoke({ const response = await SessionsService.cancelSessionInvoke({
sessionId, sessionId,
}); });
sessionLog.info({ arg, response }, `Session canceled (${sessionId})`);
return response; return response;
} });
);
type SessionsListedArg = Parameters< type SessionsListedArg = Parameters<
(typeof SessionsService)['listSessions'] (typeof SessionsService)['listSessions']