mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-24 11:12:49 +00:00
🧵 fix: Preserve Streaming Messages During Stale Refetch (#13247)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
This commit is contained in:
parent
40a3df3901
commit
34a693121c
2 changed files with 131 additions and 15 deletions
82
client/src/data-provider/Messages/__tests__/queries.test.ts
Normal file
82
client/src/data-provider/Messages/__tests__/queries.test.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import type { TMessage } from 'librechat-data-provider';
|
||||
import { getStableMessages } from '../queries';
|
||||
|
||||
const message = (overrides: Partial<TMessage>): TMessage =>
|
||||
({
|
||||
messageId: 'message-id',
|
||||
conversationId: 'convo-id',
|
||||
parentMessageId: '00000000-0000-0000-0000-000000000000',
|
||||
text: '',
|
||||
sender: 'User',
|
||||
isCreatedByUser: true,
|
||||
createdAt: '2026-05-21T12:00:00.000Z',
|
||||
updatedAt: '2026-05-21T12:00:00.000Z',
|
||||
...overrides,
|
||||
}) as TMessage;
|
||||
|
||||
describe('getStableMessages', () => {
|
||||
it('keeps cache when an empty result races with unhydrated stream messages', () => {
|
||||
const currentMessages = [
|
||||
message({ messageId: 'persisted-1' }),
|
||||
message({ messageId: 'user-2', createdAt: undefined, updatedAt: undefined }),
|
||||
message({
|
||||
messageId: 'user-2_',
|
||||
isCreatedByUser: false,
|
||||
createdAt: undefined,
|
||||
updatedAt: undefined,
|
||||
}),
|
||||
];
|
||||
|
||||
const result = getStableMessages({
|
||||
pathname: '/c/convo-id',
|
||||
result: [],
|
||||
currentMessages,
|
||||
});
|
||||
|
||||
expect(result).toBe(currentMessages);
|
||||
});
|
||||
|
||||
it('keeps cache when a one-message result returns for a larger cache', () => {
|
||||
const currentMessages = [
|
||||
message({ messageId: 'persisted-1' }),
|
||||
message({ messageId: 'persisted-2' }),
|
||||
];
|
||||
|
||||
const result = getStableMessages({
|
||||
pathname: '/c/convo-id',
|
||||
result: [currentMessages[0]],
|
||||
currentMessages,
|
||||
});
|
||||
|
||||
expect(result).toBe(currentMessages);
|
||||
});
|
||||
|
||||
it('accepts fewer persisted messages when the cache is fully hydrated', () => {
|
||||
const currentMessages = [
|
||||
message({ messageId: 'persisted-1' }),
|
||||
message({ messageId: 'persisted-2' }),
|
||||
message({ messageId: 'persisted-3' }),
|
||||
];
|
||||
const serverMessages = [currentMessages[0], currentMessages[1]];
|
||||
|
||||
const result = getStableMessages({
|
||||
pathname: '/c/convo-id',
|
||||
result: serverMessages,
|
||||
currentMessages,
|
||||
});
|
||||
|
||||
expect(result).toBe(serverMessages);
|
||||
});
|
||||
|
||||
it('does not preserve cache on the new conversation route', () => {
|
||||
const currentMessages = [message({ messageId: 'user-1', createdAt: undefined })];
|
||||
|
||||
const result = getStableMessages({
|
||||
pathname: '/c/new',
|
||||
result: [],
|
||||
currentMessages,
|
||||
});
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
@ -5,6 +5,39 @@ import { QueryKeys, dataService } from 'librechat-data-provider';
|
|||
import type * as t from 'librechat-data-provider';
|
||||
import { logger } from '~/utils';
|
||||
|
||||
type StableMessagesParams = {
|
||||
pathname: string;
|
||||
result: t.TMessage[];
|
||||
currentMessages?: t.TMessage[];
|
||||
};
|
||||
|
||||
function hasUnhydratedMessage(messages: t.TMessage[]) {
|
||||
return messages.some((message) => {
|
||||
const messageId = message.messageId ?? '';
|
||||
return message.createdAt == null || message.updatedAt == null || messageId.endsWith('_');
|
||||
});
|
||||
}
|
||||
|
||||
export function getStableMessages({
|
||||
pathname,
|
||||
result,
|
||||
currentMessages,
|
||||
}: StableMessagesParams): t.TMessage[] {
|
||||
if (pathname.includes('/c/new') || !currentMessages?.length) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (result.length >= currentMessages.length) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (result.length === 1 || hasUnhydratedMessage(currentMessages)) {
|
||||
return currentMessages;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export const useGetMessagesByConvoId = <TData = t.TMessage[]>(
|
||||
id: string,
|
||||
config?: UseQueryOptions<t.TMessage[], unknown, TData>,
|
||||
|
|
@ -15,22 +48,23 @@ export const useGetMessagesByConvoId = <TData = t.TMessage[]>(
|
|||
[QueryKeys.messages, id],
|
||||
async () => {
|
||||
const result = await dataService.getMessagesByConvoId(id);
|
||||
if (!location.pathname.includes('/c/new') && result?.length === 1) {
|
||||
const currentMessages = queryClient.getQueryData<t.TMessage[]>([QueryKeys.messages, id]);
|
||||
if (currentMessages?.length === 1) {
|
||||
return result;
|
||||
}
|
||||
if (currentMessages && currentMessages?.length > 1) {
|
||||
logger.warn(
|
||||
'messages',
|
||||
`Messages query for convo ${id} returned fewer than cache; path: "${location.pathname}"`,
|
||||
result,
|
||||
currentMessages,
|
||||
);
|
||||
return currentMessages;
|
||||
}
|
||||
const currentMessages = queryClient.getQueryData<t.TMessage[]>([QueryKeys.messages, id]);
|
||||
const stableMessages = getStableMessages({
|
||||
pathname: location.pathname,
|
||||
result,
|
||||
currentMessages,
|
||||
});
|
||||
|
||||
if (stableMessages === currentMessages) {
|
||||
logger.warn(
|
||||
'messages',
|
||||
`Messages query for convo ${id} returned fewer than cache; path: "${location.pathname}"`,
|
||||
result,
|
||||
currentMessages,
|
||||
);
|
||||
}
|
||||
return result;
|
||||
|
||||
return stableMessages;
|
||||
},
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue