mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
♻️ fix: Correct Message ID Assignment Logic (#8439)
* fix: Add `isRegenerate` flag to chat payload to avoid saving temporary response IDs * fix: Remove unused `isResubmission` flag * ci: Add tests for responseMessageId regeneration logic in BaseClient
This commit is contained in:
parent
170cc340d8
commit
e370a87ebe
8 changed files with 61 additions and 15 deletions
|
|
@ -197,6 +197,10 @@ class BaseClient {
|
||||||
this.currentMessages[this.currentMessages.length - 1].messageId = head;
|
this.currentMessages[this.currentMessages.length - 1].messageId = head;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts.isRegenerate && responseMessageId.endsWith('_')) {
|
||||||
|
responseMessageId = crypto.randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
this.responseMessageId = responseMessageId;
|
this.responseMessageId = responseMessageId;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -422,6 +422,46 @@ describe('BaseClient', () => {
|
||||||
expect(response).toEqual(expectedResult);
|
expect(response).toEqual(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should replace responseMessageId with new UUID when isRegenerate is true and messageId ends with underscore', async () => {
|
||||||
|
const mockCrypto = require('crypto');
|
||||||
|
const newUUID = 'new-uuid-1234';
|
||||||
|
jest.spyOn(mockCrypto, 'randomUUID').mockReturnValue(newUUID);
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
isRegenerate: true,
|
||||||
|
responseMessageId: 'existing-message-id_',
|
||||||
|
};
|
||||||
|
|
||||||
|
await TestClient.setMessageOptions(opts);
|
||||||
|
|
||||||
|
expect(TestClient.responseMessageId).toBe(newUUID);
|
||||||
|
expect(TestClient.responseMessageId).not.toBe('existing-message-id_');
|
||||||
|
|
||||||
|
mockCrypto.randomUUID.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not replace responseMessageId when isRegenerate is false', async () => {
|
||||||
|
const opts = {
|
||||||
|
isRegenerate: false,
|
||||||
|
responseMessageId: 'existing-message-id_',
|
||||||
|
};
|
||||||
|
|
||||||
|
await TestClient.setMessageOptions(opts);
|
||||||
|
|
||||||
|
expect(TestClient.responseMessageId).toBe('existing-message-id_');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not replace responseMessageId when it does not end with underscore', async () => {
|
||||||
|
const opts = {
|
||||||
|
isRegenerate: true,
|
||||||
|
responseMessageId: 'existing-message-id',
|
||||||
|
};
|
||||||
|
|
||||||
|
await TestClient.setMessageOptions(opts);
|
||||||
|
|
||||||
|
expect(TestClient.responseMessageId).toBe('existing-message-id');
|
||||||
|
});
|
||||||
|
|
||||||
test('sendMessage should work with provided conversationId and parentMessageId', async () => {
|
test('sendMessage should work with provided conversationId and parentMessageId', async () => {
|
||||||
const userMessage = 'Second message in the conversation';
|
const userMessage = 'Second message in the conversation';
|
||||||
const opts = {
|
const opts = {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const { saveMessage } = require('~/models');
|
||||||
const AgentController = async (req, res, next, initializeClient, addTitle) => {
|
const AgentController = async (req, res, next, initializeClient, addTitle) => {
|
||||||
let {
|
let {
|
||||||
text,
|
text,
|
||||||
|
isRegenerate,
|
||||||
endpointOption,
|
endpointOption,
|
||||||
conversationId,
|
conversationId,
|
||||||
isContinued = false,
|
isContinued = false,
|
||||||
|
|
@ -167,6 +168,7 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => {
|
||||||
onStart,
|
onStart,
|
||||||
getReqData,
|
getReqData,
|
||||||
isContinued,
|
isContinued,
|
||||||
|
isRegenerate,
|
||||||
editedContent,
|
editedContent,
|
||||||
conversationId,
|
conversationId,
|
||||||
parentMessageId,
|
parentMessageId,
|
||||||
|
|
|
||||||
|
|
@ -345,9 +345,7 @@ export type TOptions = {
|
||||||
isContinued?: boolean;
|
isContinued?: boolean;
|
||||||
isEdited?: boolean;
|
isEdited?: boolean;
|
||||||
overrideMessages?: t.TMessage[];
|
overrideMessages?: t.TMessage[];
|
||||||
/** This value is only true when the user submits a message with "Save & Submit" for a user-created message */
|
/** Currently only utilized when resubmitting user-created message, uses that message's currently attached files */
|
||||||
isResubmission?: boolean;
|
|
||||||
/** Currently only utilized when `isResubmission === true`, uses that message's currently attached files */
|
|
||||||
overrideFiles?: t.TMessage['files'];
|
overrideFiles?: t.TMessage['files'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,6 @@ const EditMessage = ({
|
||||||
conversationId,
|
conversationId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isResubmission: true,
|
|
||||||
overrideFiles: message.files,
|
overrideFiles: message.files,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,6 @@ export default function useChatFunctions({
|
||||||
{
|
{
|
||||||
editedContent = null,
|
editedContent = null,
|
||||||
editedMessageId = null,
|
editedMessageId = null,
|
||||||
isResubmission = false,
|
|
||||||
isRegenerate = false,
|
isRegenerate = false,
|
||||||
isContinued = false,
|
isContinued = false,
|
||||||
isEdited = false,
|
isEdited = false,
|
||||||
|
|
@ -230,7 +229,10 @@ export default function useChatFunctions({
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseMessageId =
|
const responseMessageId =
|
||||||
editedMessageId ?? (latestMessage?.messageId ? latestMessage?.messageId + '_' : null) ?? null;
|
editedMessageId ??
|
||||||
|
(latestMessage?.messageId && isRegenerate ? latestMessage?.messageId + '_' : null) ??
|
||||||
|
null;
|
||||||
|
|
||||||
const initialResponse: TMessage = {
|
const initialResponse: TMessage = {
|
||||||
sender: responseSender,
|
sender: responseSender,
|
||||||
text: '',
|
text: '',
|
||||||
|
|
@ -307,7 +309,6 @@ export default function useChatFunctions({
|
||||||
isEdited: isEditOrContinue,
|
isEdited: isEditOrContinue,
|
||||||
isContinued,
|
isContinued,
|
||||||
isRegenerate,
|
isRegenerate,
|
||||||
isResubmission,
|
|
||||||
initialResponse,
|
initialResponse,
|
||||||
isTemporary,
|
isTemporary,
|
||||||
ephemeralAgent,
|
ephemeralAgent,
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,15 @@ import * as s from './schemas';
|
||||||
|
|
||||||
export default function createPayload(submission: t.TSubmission) {
|
export default function createPayload(submission: t.TSubmission) {
|
||||||
const {
|
const {
|
||||||
conversation,
|
|
||||||
userMessage,
|
|
||||||
endpointOption,
|
|
||||||
isEdited,
|
isEdited,
|
||||||
|
userMessage,
|
||||||
isContinued,
|
isContinued,
|
||||||
isTemporary,
|
isTemporary,
|
||||||
ephemeralAgent,
|
isRegenerate,
|
||||||
|
conversation,
|
||||||
editedContent,
|
editedContent,
|
||||||
|
ephemeralAgent,
|
||||||
|
endpointOption,
|
||||||
} = submission;
|
} = submission;
|
||||||
const { conversationId } = s.tConvoUpdateSchema.parse(conversation);
|
const { conversationId } = s.tConvoUpdateSchema.parse(conversation);
|
||||||
const { endpoint: _e, endpointType } = endpointOption as {
|
const { endpoint: _e, endpointType } = endpointOption as {
|
||||||
|
|
@ -31,11 +32,12 @@ export default function createPayload(submission: t.TSubmission) {
|
||||||
...userMessage,
|
...userMessage,
|
||||||
...endpointOption,
|
...endpointOption,
|
||||||
endpoint,
|
endpoint,
|
||||||
ephemeralAgent: s.isAssistantsEndpoint(endpoint) ? undefined : ephemeralAgent,
|
|
||||||
isContinued: !!(isEdited && isContinued),
|
|
||||||
conversationId,
|
|
||||||
isTemporary,
|
isTemporary,
|
||||||
|
isRegenerate,
|
||||||
editedContent,
|
editedContent,
|
||||||
|
conversationId,
|
||||||
|
isContinued: !!(isEdited && isContinued),
|
||||||
|
ephemeralAgent: s.isAssistantsEndpoint(endpoint) ? undefined : ephemeralAgent,
|
||||||
};
|
};
|
||||||
|
|
||||||
return { server, payload };
|
return { server, payload };
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,7 @@ export type TEphemeralAgent = {
|
||||||
export type TPayload = Partial<TMessage> &
|
export type TPayload = Partial<TMessage> &
|
||||||
Partial<TEndpointOption> & {
|
Partial<TEndpointOption> & {
|
||||||
isContinued: boolean;
|
isContinued: boolean;
|
||||||
|
isRegenerate?: boolean;
|
||||||
conversationId: string | null;
|
conversationId: string | null;
|
||||||
messages?: TMessages;
|
messages?: TMessages;
|
||||||
isTemporary: boolean;
|
isTemporary: boolean;
|
||||||
|
|
@ -125,7 +126,6 @@ export type TSubmission = {
|
||||||
isTemporary: boolean;
|
isTemporary: boolean;
|
||||||
messages: TMessage[];
|
messages: TMessage[];
|
||||||
isRegenerate?: boolean;
|
isRegenerate?: boolean;
|
||||||
isResubmission?: boolean;
|
|
||||||
initialResponse?: TMessage;
|
initialResponse?: TMessage;
|
||||||
conversation: Partial<TConversation>;
|
conversation: Partial<TConversation>;
|
||||||
endpointOption: TEndpointOption;
|
endpointOption: TEndpointOption;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue