feat: Enhance updateConvoInAllQueries to support moving conversations to the top

This commit is contained in:
Danny Avila 2025-12-18 09:23:02 -05:00
parent c7bc5548bc
commit 4c115b684c
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
3 changed files with 134 additions and 10 deletions

View file

@ -310,7 +310,7 @@ export default function useEventHandlers({
if (requestMessage.parentMessageId === Constants.NO_PARENT) {
addConvoToAllQueries(queryClient, update);
} else {
updateConvoInAllQueries(queryClient, update.conversationId!, (_c) => update);
updateConvoInAllQueries(queryClient, update.conversationId!, (_c) => update, true);
}
} else if (setConversation) {
setConversation((prevState) => {
@ -385,7 +385,7 @@ export default function useEventHandlers({
if (parentMessageId === Constants.NO_PARENT) {
addConvoToAllQueries(queryClient, update);
} else {
updateConvoInAllQueries(queryClient, update.conversationId!, (_c) => update);
updateConvoInAllQueries(queryClient, update.conversationId!, (_c) => update, true);
}
}
} else if (setConversation) {

View file

@ -596,6 +596,77 @@ describe('Conversation Utilities', () => {
expect(data!.pages[0].conversations[0].model).toBe('gpt-4');
});
it('updateConvoInAllQueries with moveToTop moves convo to front and updates updatedAt', () => {
// Add more conversations so 'a' is not at position 0
const convoC = { conversationId: 'c', updatedAt: '2024-01-03T12:00:00Z' } as TConversation;
queryClient.setQueryData(['allConversations'], {
pages: [{ conversations: [convoC, convoA], nextCursor: null }],
pageParams: [],
});
const before = new Date().toISOString();
updateConvoInAllQueries(queryClient, 'a', (c) => ({ ...c, model: 'gpt-4' }), true);
const data = queryClient.getQueryData<InfiniteData<any>>(['allConversations']);
// 'a' should now be at position 0
expect(data!.pages[0].conversations[0].conversationId).toBe('a');
expect(data!.pages[0].conversations[0].model).toBe('gpt-4');
// updatedAt should be updated
expect(
new Date(data!.pages[0].conversations[0].updatedAt).getTime(),
).toBeGreaterThanOrEqual(new Date(before).getTime());
// 'c' should now be at position 1
expect(data!.pages[0].conversations[1].conversationId).toBe('c');
});
it('updateConvoInAllQueries with moveToTop from second page', () => {
const convoC = { conversationId: 'c', updatedAt: '2024-01-03T12:00:00Z' } as TConversation;
const convoD = { conversationId: 'd', updatedAt: '2024-01-04T12:00:00Z' } as TConversation;
queryClient.setQueryData(['allConversations'], {
pages: [
{ conversations: [convoC, convoD], nextCursor: 'cursor1' },
{ conversations: [convoA, convoB], nextCursor: null },
],
pageParams: [],
});
updateConvoInAllQueries(queryClient, 'a', (c) => ({ ...c, title: 'Updated' }), true);
const data = queryClient.getQueryData<InfiniteData<any>>(['allConversations']);
// 'a' should now be at front of page 0
expect(data!.pages[0].conversations[0].conversationId).toBe('a');
expect(data!.pages[0].conversations[0].title).toBe('Updated');
// Page 0 should have 3 conversations now
expect(data!.pages[0].conversations.length).toBe(3);
// Page 1 should have 1 conversation (only 'b' remains)
expect(data!.pages[1].conversations.length).toBe(1);
expect(data!.pages[1].conversations[0].conversationId).toBe('b');
});
it('updateConvoInAllQueries with moveToTop when already at position 0 updates in place', () => {
const originalUpdatedAt = convoA.updatedAt;
updateConvoInAllQueries(queryClient, 'a', (c) => ({ ...c, model: 'gpt-4' }), true);
const data = queryClient.getQueryData<InfiniteData<any>>(['allConversations']);
expect(data!.pages[0].conversations[0].conversationId).toBe('a');
expect(data!.pages[0].conversations[0].model).toBe('gpt-4');
// updatedAt should still be updated even when already at top
expect(data!.pages[0].conversations[0].updatedAt).not.toBe(originalUpdatedAt);
});
it('updateConvoInAllQueries with moveToTop returns original data if convo not found', () => {
const dataBefore = queryClient.getQueryData<InfiniteData<any>>(['allConversations']);
updateConvoInAllQueries(
queryClient,
'nonexistent',
(c) => ({ ...c, model: 'gpt-4' }),
true,
);
const dataAfter = queryClient.getQueryData<InfiniteData<any>>(['allConversations']);
expect(dataAfter).toEqual(dataBefore);
});
it('removeConvoFromAllQueries deletes conversation', () => {
removeConvoFromAllQueries(queryClient, 'a');
const data = queryClient.getQueryData<InfiniteData<any>>(['allConversations']);

View file

@ -352,6 +352,7 @@ export function updateConvoInAllQueries(
queryClient: QueryClient,
conversationId: string,
updater: (c: TConversation) => TConversation,
moveToTop = false,
) {
const queries = queryClient
.getQueryCache()
@ -362,15 +363,67 @@ export function updateConvoInAllQueries(
if (!oldData) {
return oldData;
}
return {
...oldData,
pages: oldData.pages.map((page) => ({
...page,
conversations: page.conversations.map((c) =>
c.conversationId === conversationId ? updater(c) : c,
// Find conversation location (single pass with early exit)
let pageIdx = -1;
let convoIdx = -1;
for (let pi = 0; pi < oldData.pages.length; pi++) {
const ci = oldData.pages[pi].conversations.findIndex(
(c) => c.conversationId === conversationId,
);
if (ci !== -1) {
pageIdx = pi;
convoIdx = ci;
break;
}
}
if (pageIdx === -1) {
return oldData;
}
const found = oldData.pages[pageIdx].conversations[convoIdx];
const updated = moveToTop
? { ...updater(found), updatedAt: new Date().toISOString() }
: updater(found);
// If not moving to top, or already at top of page 0, update in place
if (!moveToTop || (pageIdx === 0 && convoIdx === 0)) {
return {
...oldData,
pages: oldData.pages.map((page, pi) =>
pi === pageIdx
? {
...page,
conversations: page.conversations.map((c, ci) => (ci === convoIdx ? updated : c)),
}
: page,
),
})),
};
};
}
// Move to top: only modify affected pages
const newPages = oldData.pages.map((page, pi) => {
if (pi === 0 && pageIdx === 0) {
// Source is page 0: remove from current position, add to front
const convos = page.conversations.filter((_, ci) => ci !== convoIdx);
return { ...page, conversations: [updated, ...convos] };
}
if (pi === 0) {
// Add to front of page 0
return { ...page, conversations: [updated, ...page.conversations] };
}
if (pi === pageIdx) {
// Remove from source page
return {
...page,
conversations: page.conversations.filter((_, ci) => ci !== convoIdx),
};
}
return page;
});
return { ...oldData, pages: newPages };
});
}
}