Skip to content

Commit 67669b7

Browse files
lsteinCopilot
andauthored
QoL: Persist selected board and most recent image across browser sessions (invoke-ai#8920)
* Persist selected board and auto-select most recent image across browser sessions (#92) * Persist selectedBoardId across browser sessions Co-authored-by: lstein <111189+lstein@users.noreply.github.com> * fix(frontend): make appStarted listener async so image auto-selection works on startup Co-authored-by: lstein <111189+lstein@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com> * chore(frontend): remove unwanted package-lock.json --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
1 parent c7bdaf9 commit 67669b7

2 files changed

Lines changed: 21 additions & 18 deletions

File tree

invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/appStarted.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,12 @@ export const appStarted = createAction('app/appStarted');
1212
export const addAppStartedListener = (startAppListening: AppStartListening) => {
1313
startAppListening({
1414
actionCreator: appStarted,
15-
effect: (action, { unsubscribe, cancelActiveListeners, take, getState, dispatch }) => {
15+
effect: async (action, { unsubscribe, cancelActiveListeners, take, getState, dispatch }) => {
1616
// this should only run once
1717
cancelActiveListeners();
1818
unsubscribe();
1919

20-
// ensure an image is selected when we load the first board
21-
take(imagesApi.endpoints.getImageNames.matchFulfilled).then((firstImageLoad) => {
22-
if (firstImageLoad === null) {
23-
// timeout or cancelled
24-
return;
25-
}
26-
const [{ payload }] = firstImageLoad;
27-
const selectedImage = selectLastSelectedItem(getState());
28-
if (selectedImage) {
29-
return;
30-
}
31-
if (payload.image_names[0]) {
32-
dispatch(imageSelected(payload.image_names[0]));
33-
}
34-
});
35-
20+
// Fire patchmatch check without blocking the image-selection logic below
3621
dispatch(appInfoApi.endpoints.getPatchmatchStatus.initiate())
3722
.unwrap()
3823
.then((isPatchmatchAvailable) => {
@@ -43,6 +28,24 @@ export const addAppStartedListener = (startAppListening: AppStartListening) => {
4328
}
4429
})
4530
.catch(noop);
31+
32+
// ensure an image is selected when we load the first board.
33+
// The effect must be async and await take() so that RTK keeps the listener's AbortController
34+
// alive until the query resolves; a synchronous effect causes the controller to be aborted
35+
// immediately after the effect returns, before any network response arrives.
36+
const firstImageLoad = await take(imagesApi.endpoints.getImageNames.matchFulfilled, 5000);
37+
if (firstImageLoad === null) {
38+
// timeout or cancelled
39+
return;
40+
}
41+
const [{ payload }] = firstImageLoad;
42+
const selectedImage = selectLastSelectedItem(getState());
43+
if (selectedImage) {
44+
return;
45+
}
46+
if (payload.image_names[0]) {
47+
dispatch(imageSelected(payload.image_names[0]));
48+
}
4649
},
4750
});
4851
};

invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,6 @@ export const gallerySliceConfig: SliceConfig<typeof slice> = {
191191
}
192192
return zGalleryState.parse(state);
193193
},
194-
persistDenylist: ['selection', 'selectedBoardId', 'galleryView', 'imageToCompare'],
194+
persistDenylist: ['selection', 'galleryView', 'imageToCompare'],
195195
},
196196
};

0 commit comments

Comments
 (0)