2025-01-23 18:19:04 -05:00
|
|
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
2025-03-05 12:04:26 -05:00
|
|
|
import { dataService, QueryKeys, Constants } from 'librechat-data-provider';
|
2025-11-12 13:32:47 -05:00
|
|
|
import type { UseMutationResult, UseMutationOptions } from '@tanstack/react-query';
|
2025-03-05 12:04:26 -05:00
|
|
|
import type * as t from 'librechat-data-provider';
|
2025-01-23 18:19:04 -05:00
|
|
|
|
2025-11-12 13:32:47 -05:00
|
|
|
type EditArtifactContext = {
|
|
|
|
|
previousMessages: Record<string, t.TMessage[] | undefined>;
|
|
|
|
|
updatedConversationId: string | null;
|
|
|
|
|
};
|
|
|
|
|
|
2025-01-23 18:19:04 -05:00
|
|
|
export const useEditArtifact = (
|
|
|
|
|
_options?: t.EditArtifactOptions,
|
2025-11-12 13:32:47 -05:00
|
|
|
): UseMutationResult<
|
|
|
|
|
t.TEditArtifactResponse,
|
|
|
|
|
Error,
|
|
|
|
|
t.TEditArtifactRequest,
|
|
|
|
|
EditArtifactContext
|
|
|
|
|
> => {
|
2025-01-23 18:19:04 -05:00
|
|
|
const queryClient = useQueryClient();
|
2025-11-12 13:32:47 -05:00
|
|
|
const { onSuccess, onError, onMutate: userOnMutate, ...options } = _options ?? {};
|
|
|
|
|
|
|
|
|
|
const mutationOptions: UseMutationOptions<
|
|
|
|
|
t.TEditArtifactResponse,
|
|
|
|
|
Error,
|
|
|
|
|
t.TEditArtifactRequest,
|
|
|
|
|
EditArtifactContext
|
|
|
|
|
> = {
|
2025-01-23 18:19:04 -05:00
|
|
|
mutationFn: (variables: t.TEditArtifactRequest) => dataService.editArtifact(variables),
|
2025-11-12 13:32:47 -05:00
|
|
|
/**
|
|
|
|
|
* onMutate: No optimistic updates for artifact editing
|
|
|
|
|
* The code editor shows changes instantly via local Sandpack state
|
|
|
|
|
* Optimistic updates cause "original content not found" errors because:
|
|
|
|
|
* 1. First edit optimistically updates cache
|
|
|
|
|
* 2. Artifact.content reflects the updated cache
|
|
|
|
|
* 3. Next edit sends updated content as "original" → doesn't match DB → error
|
|
|
|
|
*/
|
|
|
|
|
onMutate: async (vars) => {
|
|
|
|
|
// Call user's onMutate if provided
|
|
|
|
|
if (userOnMutate) {
|
|
|
|
|
await userOnMutate(vars);
|
|
|
|
|
}
|
|
|
|
|
return { previousMessages: {}, updatedConversationId: null };
|
|
|
|
|
},
|
|
|
|
|
onError: (error, vars, context) => {
|
|
|
|
|
onError?.(error, vars, context);
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* On success: Update with server response to ensure consistency
|
|
|
|
|
*/
|
2025-01-23 18:19:04 -05:00
|
|
|
onSuccess: (data, vars, context) => {
|
2025-03-05 12:04:26 -05:00
|
|
|
let targetNotFound = true;
|
|
|
|
|
const setMessageData = (conversationId?: string | null) => {
|
|
|
|
|
if (!conversationId) {
|
|
|
|
|
return;
|
2025-01-23 18:19:04 -05:00
|
|
|
}
|
2025-03-05 12:04:26 -05:00
|
|
|
queryClient.setQueryData<t.TMessage[]>([QueryKeys.messages, conversationId], (prev) => {
|
|
|
|
|
if (!prev) {
|
|
|
|
|
return prev;
|
|
|
|
|
}
|
2025-01-23 18:19:04 -05:00
|
|
|
|
2025-03-05 12:04:26 -05:00
|
|
|
const newArray = [...prev];
|
|
|
|
|
let targetIndex: number | undefined;
|
2025-01-23 18:19:04 -05:00
|
|
|
|
2025-03-05 12:04:26 -05:00
|
|
|
for (let i = newArray.length - 1; i >= 0; i--) {
|
|
|
|
|
if (newArray[i].messageId === vars.messageId) {
|
|
|
|
|
targetIndex = i;
|
|
|
|
|
targetNotFound = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-01-23 18:19:04 -05:00
|
|
|
}
|
|
|
|
|
|
2025-03-05 12:04:26 -05:00
|
|
|
if (targetIndex == null) {
|
|
|
|
|
return prev;
|
|
|
|
|
}
|
2025-01-23 18:19:04 -05:00
|
|
|
|
2025-03-05 12:04:26 -05:00
|
|
|
newArray[targetIndex] = {
|
|
|
|
|
...newArray[targetIndex],
|
|
|
|
|
content: data.content,
|
|
|
|
|
text: data.text,
|
|
|
|
|
};
|
2025-01-23 18:19:04 -05:00
|
|
|
|
2025-03-05 12:04:26 -05:00
|
|
|
return newArray;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
setMessageData(data.conversationId);
|
|
|
|
|
if (targetNotFound) {
|
|
|
|
|
console.warn(
|
|
|
|
|
'Edited Artifact Message not found in cache, trying `new` as `conversationId`',
|
|
|
|
|
);
|
2025-11-12 13:32:47 -05:00
|
|
|
setMessageData(Constants.NEW_CONVO as string);
|
2025-03-05 12:04:26 -05:00
|
|
|
}
|
2025-01-23 18:19:04 -05:00
|
|
|
|
|
|
|
|
onSuccess?.(data, vars, context);
|
|
|
|
|
},
|
|
|
|
|
...options,
|
2025-11-12 13:32:47 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return useMutation(mutationOptions);
|
2025-01-23 18:19:04 -05:00
|
|
|
};
|
2025-12-25 01:43:54 -05:00
|
|
|
|
|
|
|
|
type BranchMessageContext = {
|
|
|
|
|
previousMessages: t.TMessage[] | undefined;
|
|
|
|
|
conversationId: string | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useBranchMessageMutation = (
|
|
|
|
|
conversationId: string | null,
|
|
|
|
|
_options?: t.BranchMessageOptions,
|
|
|
|
|
): UseMutationResult<
|
|
|
|
|
t.TBranchMessageResponse,
|
|
|
|
|
Error,
|
|
|
|
|
t.TBranchMessageRequest,
|
|
|
|
|
BranchMessageContext
|
|
|
|
|
> => {
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
const { onSuccess, onError, onMutate: userOnMutate, ...options } = _options ?? {};
|
|
|
|
|
|
|
|
|
|
const mutationOptions: UseMutationOptions<
|
|
|
|
|
t.TBranchMessageResponse,
|
|
|
|
|
Error,
|
|
|
|
|
t.TBranchMessageRequest,
|
|
|
|
|
BranchMessageContext
|
|
|
|
|
> = {
|
|
|
|
|
mutationFn: (variables: t.TBranchMessageRequest) => dataService.branchMessage(variables),
|
|
|
|
|
onMutate: async (vars) => {
|
|
|
|
|
// Call user's onMutate if provided
|
|
|
|
|
if (userOnMutate) {
|
|
|
|
|
await userOnMutate(vars);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cancel any outgoing queries for messages
|
|
|
|
|
if (conversationId) {
|
|
|
|
|
await queryClient.cancelQueries([QueryKeys.messages, conversationId]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the previous messages for rollback
|
|
|
|
|
const previousMessages = conversationId
|
|
|
|
|
? queryClient.getQueryData<t.TMessage[]>([QueryKeys.messages, conversationId])
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
return { previousMessages, conversationId };
|
|
|
|
|
},
|
|
|
|
|
onError: (error, vars, context) => {
|
|
|
|
|
// Rollback to previous messages on error
|
|
|
|
|
if (context?.conversationId && context?.previousMessages) {
|
|
|
|
|
queryClient.setQueryData(
|
|
|
|
|
[QueryKeys.messages, context.conversationId],
|
|
|
|
|
context.previousMessages,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
onError?.(error, vars, context);
|
|
|
|
|
},
|
|
|
|
|
onSuccess: (data, vars, context) => {
|
|
|
|
|
// Add the new message to the cache
|
|
|
|
|
const targetConversationId = data.conversationId || context?.conversationId;
|
|
|
|
|
if (targetConversationId) {
|
|
|
|
|
queryClient.setQueryData<t.TMessage[]>(
|
|
|
|
|
[QueryKeys.messages, targetConversationId],
|
|
|
|
|
(prev) => {
|
|
|
|
|
if (!prev) {
|
|
|
|
|
return [data];
|
|
|
|
|
}
|
|
|
|
|
return [...prev, data];
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onSuccess?.(data, vars, context);
|
|
|
|
|
},
|
|
|
|
|
...options,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return useMutation(mutationOptions);
|
|
|
|
|
};
|