mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
* refactor: expand container * chore: bump @codesandbox/sandpack-react to latest * WIP: first pass, show editor * feat: implement ArtifactCodeEditor and ArtifactTabs components for enhanced artifact management * refactor: fileKey * refactor: auto scrolling code editor and add messageId to artifact * feat: first pass, editing artifact * feat: first pass, robust artifact replacement * fix: robust artifact replacement & re-render when expected * feat: Download Artifacts * refactor: improve artifact editing UX * fix: layout shift of new download button * fix: enhance missing output checks and logging in StreamRunManager
1070 lines
34 KiB
TypeScript
1070 lines
34 KiB
TypeScript
import {
|
|
Constants,
|
|
defaultAssistantsVersion,
|
|
ConversationListResponse,
|
|
} from 'librechat-data-provider';
|
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { dataService, MutationKeys, QueryKeys, defaultOrderQuery } from 'librechat-data-provider';
|
|
import type { InfiniteData, UseMutationResult } from '@tanstack/react-query';
|
|
import type * as t from 'librechat-data-provider';
|
|
import { useConversationTagsQuery, useConversationsInfiniteQuery } from './queries';
|
|
import useUpdateTagsInConvo from '~/hooks/Conversations/useUpdateTagsInConvo';
|
|
import { updateConversationTag } from '~/utils/conversationTags';
|
|
import { normalizeData } from '~/utils/collection';
|
|
import {
|
|
logger,
|
|
/* Conversations */
|
|
addConversation,
|
|
updateConvoFields,
|
|
updateConversation,
|
|
deleteConversation,
|
|
} from '~/utils';
|
|
|
|
export type TGenTitleMutation = UseMutationResult<
|
|
t.TGenTitleResponse,
|
|
unknown,
|
|
t.TGenTitleRequest,
|
|
unknown
|
|
>;
|
|
|
|
/** Conversations */
|
|
export const useGenTitleMutation = (): TGenTitleMutation => {
|
|
const queryClient = useQueryClient();
|
|
return useMutation((payload: t.TGenTitleRequest) => dataService.genTitle(payload), {
|
|
onSuccess: (response, vars) => {
|
|
queryClient.setQueryData(
|
|
[QueryKeys.conversation, vars.conversationId],
|
|
(convo: t.TConversation | undefined) => {
|
|
if (!convo) {
|
|
return convo;
|
|
}
|
|
return { ...convo, title: response.title };
|
|
},
|
|
);
|
|
queryClient.setQueryData<t.ConversationData>([QueryKeys.allConversations], (convoData) => {
|
|
if (!convoData) {
|
|
return convoData;
|
|
}
|
|
return updateConvoFields(convoData, {
|
|
conversationId: vars.conversationId,
|
|
title: response.title,
|
|
} as t.TConversation);
|
|
});
|
|
document.title = response.title;
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useUpdateConversationMutation = (
|
|
id: string,
|
|
): UseMutationResult<
|
|
t.TUpdateConversationResponse,
|
|
unknown,
|
|
t.TUpdateConversationRequest,
|
|
unknown
|
|
> => {
|
|
const queryClient = useQueryClient();
|
|
return useMutation(
|
|
(payload: t.TUpdateConversationRequest) => dataService.updateConversation(payload),
|
|
{
|
|
onSuccess: (updatedConvo) => {
|
|
queryClient.setQueryData([QueryKeys.conversation, id], updatedConvo);
|
|
queryClient.setQueryData<t.ConversationData>([QueryKeys.allConversations], (convoData) => {
|
|
if (!convoData) {
|
|
return convoData;
|
|
}
|
|
return updateConversation(convoData, updatedConvo);
|
|
});
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Add or remove tags for a conversation
|
|
*/
|
|
export const useTagConversationMutation = (
|
|
conversationId: string,
|
|
options?: t.updateTagsInConvoOptions,
|
|
): UseMutationResult<t.TTagConversationResponse, unknown, t.TTagConversationRequest, unknown> => {
|
|
const query = useConversationTagsQuery();
|
|
const { updateTagsInConversation } = useUpdateTagsInConvo();
|
|
return useMutation(
|
|
(payload: t.TTagConversationRequest) =>
|
|
dataService.addTagToConversation(conversationId, payload),
|
|
{
|
|
onSuccess: (updatedTags, ...rest) => {
|
|
// Because the logic for calculating the bookmark count is complex,
|
|
// the client does not perform the calculation,
|
|
// but instead refetch the data from the API.
|
|
query.refetch();
|
|
updateTagsInConversation(conversationId, updatedTags);
|
|
|
|
options?.onSuccess?.(updatedTags, ...rest);
|
|
},
|
|
onError: options?.onError,
|
|
onMutate: options?.onMutate,
|
|
},
|
|
);
|
|
};
|
|
|
|
export const useArchiveConversationMutation = (
|
|
id: string,
|
|
): UseMutationResult<
|
|
t.TArchiveConversationResponse,
|
|
unknown,
|
|
t.TArchiveConversationRequest,
|
|
unknown
|
|
> => {
|
|
const queryClient = useQueryClient();
|
|
const { refetch } = useConversationsInfiniteQuery();
|
|
const { refetch: archiveRefetch } = useConversationsInfiniteQuery({
|
|
pageNumber: '1', // dummy value not used to refetch
|
|
isArchived: true,
|
|
});
|
|
return useMutation(
|
|
(payload: t.TArchiveConversationRequest) => dataService.archiveConversation(payload),
|
|
{
|
|
onSuccess: (_data, vars) => {
|
|
const isArchived = vars.isArchived === true;
|
|
if (isArchived) {
|
|
queryClient.setQueryData([QueryKeys.conversation, id], null);
|
|
} else {
|
|
queryClient.setQueryData([QueryKeys.conversation, id], _data);
|
|
}
|
|
|
|
queryClient.setQueryData<t.ConversationData>([QueryKeys.allConversations], (convoData) => {
|
|
if (!convoData) {
|
|
return convoData;
|
|
}
|
|
const pageSize = convoData.pages[0].pageSize as number;
|
|
|
|
return normalizeData(
|
|
isArchived ? deleteConversation(convoData, id) : addConversation(convoData, _data),
|
|
'conversations',
|
|
pageSize,
|
|
);
|
|
});
|
|
|
|
if (isArchived) {
|
|
const current = queryClient.getQueryData<t.ConversationData>([
|
|
QueryKeys.allConversations,
|
|
]);
|
|
refetch({ refetchPage: (page, index) => index === (current?.pages.length ?? 1) - 1 });
|
|
}
|
|
|
|
queryClient.setQueryData<t.ConversationData>(
|
|
[QueryKeys.archivedConversations],
|
|
(convoData) => {
|
|
if (!convoData) {
|
|
return convoData;
|
|
}
|
|
const pageSize = convoData.pages[0].pageSize as number;
|
|
return normalizeData(
|
|
isArchived ? addConversation(convoData, _data) : deleteConversation(convoData, id),
|
|
'conversations',
|
|
pageSize,
|
|
);
|
|
},
|
|
);
|
|
|
|
if (!isArchived) {
|
|
const currentArchive = queryClient.getQueryData<t.ConversationData>([
|
|
QueryKeys.archivedConversations,
|
|
]);
|
|
archiveRefetch({
|
|
refetchPage: (page, index) => index === (currentArchive?.pages.length ?? 1) - 1,
|
|
});
|
|
}
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
export const useArchiveConvoMutation = (options?: t.ArchiveConvoOptions) => {
|
|
const queryClient = useQueryClient();
|
|
const { onSuccess, ..._options } = options ?? {};
|
|
|
|
return useMutation<t.TArchiveConversationResponse, unknown, t.TArchiveConversationRequest>(
|
|
(payload: t.TArchiveConversationRequest) => dataService.archiveConversation(payload),
|
|
{
|
|
onSuccess: (_data, vars) => {
|
|
const { conversationId } = vars;
|
|
const isArchived = vars.isArchived === true;
|
|
if (isArchived) {
|
|
queryClient.setQueryData([QueryKeys.conversation, conversationId], null);
|
|
} else {
|
|
queryClient.setQueryData([QueryKeys.conversation, conversationId], _data);
|
|
}
|
|
|
|
queryClient.setQueryData<t.ConversationData>([QueryKeys.allConversations], (convoData) => {
|
|
if (!convoData) {
|
|
return convoData;
|
|
}
|
|
const pageSize = convoData.pages[0].pageSize as number;
|
|
return normalizeData(
|
|
isArchived
|
|
? deleteConversation(convoData, conversationId)
|
|
: addConversation(convoData, _data),
|
|
'conversations',
|
|
pageSize,
|
|
);
|
|
});
|
|
|
|
queryClient.setQueryData<t.ConversationData>(
|
|
[QueryKeys.archivedConversations],
|
|
(convoData) => {
|
|
if (!convoData) {
|
|
return convoData;
|
|
}
|
|
const pageSize = convoData.pages[0].pageSize as number;
|
|
return normalizeData(
|
|
isArchived
|
|
? addConversation(convoData, _data)
|
|
: deleteConversation(convoData, conversationId),
|
|
'conversations',
|
|
pageSize,
|
|
);
|
|
},
|
|
);
|
|
|
|
onSuccess?.(_data, vars);
|
|
},
|
|
..._options,
|
|
},
|
|
);
|
|
};
|
|
|
|
export const useCreateSharedLinkMutation = (
|
|
options?: t.MutationOptions<t.TCreateShareLinkRequest, { conversationId: string }>,
|
|
): UseMutationResult<t.TSharedLinkResponse, unknown, { conversationId: string }, unknown> => {
|
|
const queryClient = useQueryClient();
|
|
|
|
const { onSuccess, ..._options } = options || {};
|
|
return useMutation(
|
|
({ conversationId }: { conversationId: string }) => {
|
|
if (!conversationId) {
|
|
throw new Error('Conversation ID is required');
|
|
}
|
|
|
|
return dataService.createSharedLink(conversationId);
|
|
},
|
|
{
|
|
onSuccess: (_data: t.TSharedLinkResponse, vars, context) => {
|
|
queryClient.setQueryData([QueryKeys.sharedLinks, _data.conversationId], _data);
|
|
|
|
onSuccess?.(_data, vars, context);
|
|
},
|
|
..._options,
|
|
},
|
|
);
|
|
};
|
|
|
|
export const useUpdateSharedLinkMutation = (
|
|
options?: t.MutationOptions<t.TUpdateShareLinkRequest, { shareId: string }>,
|
|
): UseMutationResult<t.TSharedLinkResponse, unknown, { shareId: string }, unknown> => {
|
|
const queryClient = useQueryClient();
|
|
|
|
const { onSuccess, ..._options } = options || {};
|
|
return useMutation(
|
|
({ shareId }) => {
|
|
if (!shareId) {
|
|
throw new Error('Share ID is required');
|
|
}
|
|
return dataService.updateSharedLink(shareId);
|
|
},
|
|
{
|
|
onSuccess: (_data: t.TSharedLinkResponse, vars, context) => {
|
|
queryClient.setQueryData([QueryKeys.sharedLinks, _data.conversationId], _data);
|
|
|
|
onSuccess?.(_data, vars, context);
|
|
},
|
|
..._options,
|
|
},
|
|
);
|
|
};
|
|
|
|
export const useDeleteSharedLinkMutation = (
|
|
options?: t.DeleteSharedLinkOptions,
|
|
): UseMutationResult<
|
|
t.TDeleteSharedLinkResponse,
|
|
unknown,
|
|
{ shareId: string },
|
|
t.DeleteSharedLinkContext
|
|
> => {
|
|
const queryClient = useQueryClient();
|
|
const { onSuccess } = options || {};
|
|
|
|
return useMutation((vars) => dataService.deleteSharedLink(vars.shareId), {
|
|
onMutate: async (vars) => {
|
|
await queryClient.cancelQueries({
|
|
queryKey: [QueryKeys.sharedLinks],
|
|
exact: false,
|
|
});
|
|
|
|
const previousQueries = new Map();
|
|
const queryKeys = queryClient.getQueryCache().findAll([QueryKeys.sharedLinks]);
|
|
|
|
queryKeys.forEach((query) => {
|
|
const previousData = queryClient.getQueryData(query.queryKey);
|
|
previousQueries.set(query.queryKey, previousData);
|
|
|
|
queryClient.setQueryData<t.SharedLinkQueryData>(query.queryKey, (old) => {
|
|
if (!old?.pages) {
|
|
return old;
|
|
}
|
|
|
|
const updatedPages = old.pages.map((page) => ({
|
|
...page,
|
|
links: page.links.filter((link) => link.shareId !== vars.shareId),
|
|
}));
|
|
|
|
const nonEmptyPages = updatedPages.filter((page) => page.links.length > 0);
|
|
|
|
return {
|
|
...old,
|
|
pages: nonEmptyPages,
|
|
};
|
|
});
|
|
});
|
|
|
|
return { previousQueries };
|
|
},
|
|
|
|
onError: (_err, _vars, context) => {
|
|
if (context?.previousQueries) {
|
|
context.previousQueries.forEach((prevData: unknown, prevQueryKey: unknown) => {
|
|
queryClient.setQueryData(prevQueryKey as string[], prevData);
|
|
});
|
|
}
|
|
},
|
|
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: [QueryKeys.sharedLinks],
|
|
exact: false,
|
|
});
|
|
},
|
|
|
|
onSuccess: (data, variables) => {
|
|
if (onSuccess) {
|
|
onSuccess(data, variables);
|
|
}
|
|
|
|
queryClient.refetchQueries({
|
|
queryKey: [QueryKeys.sharedLinks],
|
|
exact: true,
|
|
});
|
|
},
|
|
});
|
|
};
|
|
|
|
// Add a tag or update tag information (tag, description, position, etc.)
|
|
export const useConversationTagMutation = ({
|
|
context,
|
|
tag,
|
|
options,
|
|
}: {
|
|
context: string;
|
|
tag?: string;
|
|
options?: t.UpdateConversationTagOptions;
|
|
}): UseMutationResult<t.TConversationTagResponse, unknown, t.TConversationTagRequest, unknown> => {
|
|
const queryClient = useQueryClient();
|
|
const { onSuccess, ..._options } = options || {};
|
|
const onMutationSuccess: typeof onSuccess = (_data, vars) => {
|
|
queryClient.setQueryData<t.TConversationTag[]>([QueryKeys.conversationTags], (queryData) => {
|
|
if (!queryData) {
|
|
return [
|
|
{
|
|
count: 1,
|
|
position: 0,
|
|
tag: Constants.SAVED_TAG,
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
},
|
|
] as t.TConversationTag[];
|
|
}
|
|
if (tag === undefined || !tag.length) {
|
|
// Check if the tag already exists
|
|
const existingTagIndex = queryData.findIndex((item) => item.tag === _data.tag);
|
|
if (existingTagIndex !== -1) {
|
|
logger.log(
|
|
'tag_mutation',
|
|
`"Created" tag exists, updating from ${context}`,
|
|
queryData,
|
|
_data,
|
|
);
|
|
// If the tag exists, update it
|
|
const updatedData = [...queryData];
|
|
updatedData[existingTagIndex] = { ...updatedData[existingTagIndex], ..._data };
|
|
return updatedData.sort((a, b) => a.position - b.position);
|
|
} else {
|
|
// If the tag doesn't exist, add it
|
|
logger.log(
|
|
'tag_mutation',
|
|
`"Created" tag is new, adding from ${context}`,
|
|
queryData,
|
|
_data,
|
|
);
|
|
return [...queryData, _data].sort((a, b) => a.position - b.position);
|
|
}
|
|
}
|
|
logger.log('tag_mutation', `Updating tag from ${context}`, queryData, _data);
|
|
return updateConversationTag(queryData, vars, _data, tag);
|
|
});
|
|
if (vars.addToConversation === true && vars.conversationId != null && _data.tag) {
|
|
const currentConvo = queryClient.getQueryData<t.TConversation>([
|
|
QueryKeys.conversation,
|
|
vars.conversationId,
|
|
]);
|
|
if (!currentConvo) {
|
|
return;
|
|
}
|
|
logger.log(
|
|
'tag_mutation',
|
|
`\`updateTagsInConversation\` Update from ${context}`,
|
|
currentConvo,
|
|
);
|
|
updateTagsInConversation(vars.conversationId, [...(currentConvo.tags || []), _data.tag]);
|
|
}
|
|
// Change the tag title to the new title
|
|
if (tag != null) {
|
|
replaceTagsInAllConversations(tag, _data.tag);
|
|
}
|
|
};
|
|
const { updateTagsInConversation, replaceTagsInAllConversations } = useUpdateTagsInConvo();
|
|
return useMutation(
|
|
(payload: t.TConversationTagRequest) =>
|
|
tag != null
|
|
? dataService.updateConversationTag(tag, payload)
|
|
: dataService.createConversationTag(payload),
|
|
{
|
|
onSuccess: (...args) => {
|
|
onMutationSuccess(...args);
|
|
onSuccess?.(...args);
|
|
},
|
|
..._options,
|
|
},
|
|
);
|
|
};
|
|
|
|
// When a bookmark is deleted, remove that bookmark(tag) from all conversations associated with it
|
|
export const useDeleteTagInConversations = () => {
|
|
const queryClient = useQueryClient();
|
|
const deleteTagInAllConversation = (deletedTag: string) => {
|
|
const data = queryClient.getQueryData<InfiniteData<ConversationListResponse>>([
|
|
QueryKeys.allConversations,
|
|
]);
|
|
|
|
const conversationIdsWithTag = [] as string[];
|
|
|
|
// remove deleted tag from conversations
|
|
const newData = JSON.parse(JSON.stringify(data)) as InfiniteData<ConversationListResponse>;
|
|
for (let pageIndex = 0; pageIndex < newData.pages.length; pageIndex++) {
|
|
const page = newData.pages[pageIndex];
|
|
page.conversations = page.conversations.map((conversation) => {
|
|
if (
|
|
conversation.conversationId != null &&
|
|
conversation.conversationId &&
|
|
conversation.tags?.includes(deletedTag) === true
|
|
) {
|
|
conversationIdsWithTag.push(conversation.conversationId);
|
|
conversation.tags = conversation.tags.filter((t) => t !== deletedTag);
|
|
}
|
|
return conversation;
|
|
});
|
|
}
|
|
queryClient.setQueryData<InfiniteData<ConversationListResponse>>(
|
|
[QueryKeys.allConversations],
|
|
newData,
|
|
);
|
|
|
|
// Remove the deleted tag from the cache of each conversation
|
|
for (let i = 0; i < conversationIdsWithTag.length; i++) {
|
|
const conversationId = conversationIdsWithTag[i];
|
|
const conversationData = queryClient.getQueryData<t.TConversation>([
|
|
QueryKeys.conversation,
|
|
conversationId,
|
|
]);
|
|
if (conversationData && conversationData.tags) {
|
|
conversationData.tags = conversationData.tags.filter((t) => t !== deletedTag);
|
|
queryClient.setQueryData<t.TConversation>(
|
|
[QueryKeys.conversation, conversationId],
|
|
conversationData,
|
|
);
|
|
}
|
|
}
|
|
};
|
|
return deleteTagInAllConversation;
|
|
};
|
|
// Delete a tag
|
|
export const useDeleteConversationTagMutation = (
|
|
options?: t.DeleteConversationTagOptions,
|
|
): UseMutationResult<t.TConversationTagResponse, unknown, string, void> => {
|
|
const queryClient = useQueryClient();
|
|
const deleteTagInAllConversations = useDeleteTagInConversations();
|
|
|
|
const { onSuccess, ..._options } = options || {};
|
|
|
|
return useMutation((tag: string) => dataService.deleteConversationTag(tag), {
|
|
onSuccess: (_data, tagToDelete, context) => {
|
|
queryClient.setQueryData<t.TConversationTag[]>([QueryKeys.conversationTags], (data) => {
|
|
if (!data) {
|
|
return data;
|
|
}
|
|
return data.filter((t) => t.tag !== tagToDelete);
|
|
});
|
|
|
|
deleteTagInAllConversations(tagToDelete);
|
|
onSuccess?.(_data, tagToDelete, context);
|
|
},
|
|
..._options,
|
|
});
|
|
};
|
|
|
|
export const useDeleteConversationMutation = (
|
|
options?: t.DeleteConversationOptions,
|
|
): UseMutationResult<
|
|
t.TDeleteConversationResponse,
|
|
unknown,
|
|
t.TDeleteConversationRequest,
|
|
unknown
|
|
> => {
|
|
const queryClient = useQueryClient();
|
|
const { refetch } = useConversationsInfiniteQuery();
|
|
const { onSuccess, ..._options } = options || {};
|
|
return useMutation(
|
|
(payload: t.TDeleteConversationRequest) => dataService.deleteConversation(payload),
|
|
{
|
|
onSuccess: (_data, vars, context) => {
|
|
const conversationId = vars.conversationId ?? '';
|
|
if (!conversationId) {
|
|
return;
|
|
}
|
|
|
|
const handleDelete = (convoData: t.ConversationData | undefined) => {
|
|
if (!convoData) {
|
|
return convoData;
|
|
}
|
|
return normalizeData(
|
|
deleteConversation(convoData, conversationId),
|
|
'conversations',
|
|
Number(convoData.pages[0].pageSize),
|
|
);
|
|
};
|
|
|
|
queryClient.setQueryData([QueryKeys.conversation, conversationId], null);
|
|
queryClient.setQueryData<t.ConversationData>([QueryKeys.allConversations], handleDelete);
|
|
queryClient.setQueryData<t.ConversationData>(
|
|
[QueryKeys.archivedConversations],
|
|
handleDelete,
|
|
);
|
|
const current = queryClient.getQueryData<t.ConversationData>([QueryKeys.allConversations]);
|
|
refetch({ refetchPage: (page, index) => index === (current?.pages.length ?? 1) - 1 });
|
|
onSuccess?.(_data, vars, context);
|
|
},
|
|
..._options,
|
|
},
|
|
);
|
|
};
|
|
|
|
export const useDuplicateConversationMutation = (
|
|
options?: t.DuplicateConvoOptions,
|
|
): UseMutationResult<t.TDuplicateConvoResponse, unknown, t.TDuplicateConvoRequest, unknown> => {
|
|
const queryClient = useQueryClient();
|
|
const { onSuccess, ..._options } = options ?? {};
|
|
return useMutation((payload) => dataService.duplicateConversation(payload), {
|
|
onSuccess: (data, vars, context) => {
|
|
const originalId = vars.conversationId ?? '';
|
|
if (originalId.length === 0) {
|
|
return;
|
|
}
|
|
queryClient.setQueryData(
|
|
[QueryKeys.conversation, data.conversation.conversationId],
|
|
data.conversation,
|
|
);
|
|
queryClient.setQueryData<t.ConversationData>([QueryKeys.allConversations], (convoData) => {
|
|
if (!convoData) {
|
|
return convoData;
|
|
}
|
|
return addConversation(convoData, data.conversation);
|
|
});
|
|
queryClient.setQueryData<t.TMessage[]>(
|
|
[QueryKeys.messages, data.conversation.conversationId],
|
|
data.messages,
|
|
);
|
|
onSuccess?.(data, vars, context);
|
|
},
|
|
..._options,
|
|
});
|
|
};
|
|
|
|
export const useForkConvoMutation = (
|
|
options?: t.ForkConvoOptions,
|
|
): UseMutationResult<t.TForkConvoResponse, unknown, t.TForkConvoRequest, unknown> => {
|
|
const queryClient = useQueryClient();
|
|
const { onSuccess, ..._options } = options || {};
|
|
return useMutation((payload: t.TForkConvoRequest) => dataService.forkConversation(payload), {
|
|
onSuccess: (data, vars, context) => {
|
|
if (!vars.conversationId) {
|
|
return;
|
|
}
|
|
queryClient.setQueryData(
|
|
[QueryKeys.conversation, data.conversation.conversationId],
|
|
data.conversation,
|
|
);
|
|
queryClient.setQueryData<t.ConversationData>([QueryKeys.allConversations], (convoData) => {
|
|
if (!convoData) {
|
|
return convoData;
|
|
}
|
|
return addConversation(convoData, data.conversation);
|
|
});
|
|
queryClient.setQueryData<t.TMessage[]>(
|
|
[QueryKeys.messages, data.conversation.conversationId],
|
|
data.messages,
|
|
);
|
|
onSuccess?.(data, vars, context);
|
|
},
|
|
..._options,
|
|
});
|
|
};
|
|
|
|
export const useUploadConversationsMutation = (
|
|
_options?: t.MutationOptions<t.TImportResponse, FormData>,
|
|
) => {
|
|
const queryClient = useQueryClient();
|
|
const { onSuccess, onError, onMutate } = _options || {};
|
|
|
|
return useMutation<t.TImportResponse, unknown, FormData>({
|
|
mutationFn: (formData: FormData) => dataService.importConversationsFile(formData),
|
|
onSuccess: (data, variables, context) => {
|
|
/* TODO: optimize to return imported conversations and add manually */
|
|
queryClient.invalidateQueries([QueryKeys.allConversations]);
|
|
if (onSuccess) {
|
|
onSuccess(data, variables, context);
|
|
}
|
|
},
|
|
onError: (err, variables, context) => {
|
|
if (onError) {
|
|
onError(err, variables, context);
|
|
}
|
|
},
|
|
onMutate,
|
|
});
|
|
};
|
|
|
|
export const useUpdatePresetMutation = (
|
|
options?: t.UpdatePresetOptions,
|
|
): UseMutationResult<
|
|
t.TPreset, // response data
|
|
unknown,
|
|
t.TPreset,
|
|
unknown
|
|
> => {
|
|
return useMutation([MutationKeys.updatePreset], {
|
|
mutationFn: (preset: t.TPreset) => dataService.updatePreset(preset),
|
|
...(options || {}),
|
|
});
|
|
};
|
|
|
|
export const useDeletePresetMutation = (
|
|
options?: t.DeletePresetOptions,
|
|
): UseMutationResult<
|
|
t.PresetDeleteResponse, // response data
|
|
unknown,
|
|
t.TPreset | undefined,
|
|
unknown
|
|
> => {
|
|
return useMutation([MutationKeys.deletePreset], {
|
|
mutationFn: (preset: t.TPreset | undefined) => dataService.deletePreset(preset),
|
|
...(options || {}),
|
|
});
|
|
};
|
|
|
|
/* Avatar upload */
|
|
export const useUploadAvatarMutation = (
|
|
options?: t.UploadAvatarOptions,
|
|
): UseMutationResult<
|
|
t.AvatarUploadResponse, // response data
|
|
unknown, // error
|
|
FormData, // request
|
|
unknown // context
|
|
> => {
|
|
return useMutation([MutationKeys.avatarUpload], {
|
|
mutationFn: (variables: FormData) => dataService.uploadAvatar(variables),
|
|
...(options || {}),
|
|
});
|
|
};
|
|
|
|
/* Speech to text */
|
|
export const useSpeechToTextMutation = (
|
|
options?: t.SpeechToTextOptions,
|
|
): UseMutationResult<
|
|
t.SpeechToTextResponse, // response data
|
|
unknown, // error
|
|
FormData, // request
|
|
unknown // context
|
|
> => {
|
|
return useMutation([MutationKeys.speechToText], {
|
|
mutationFn: (variables: FormData) => dataService.speechToText(variables),
|
|
...(options || {}),
|
|
});
|
|
};
|
|
|
|
/* Text to speech */
|
|
export const useTextToSpeechMutation = (
|
|
options?: t.TextToSpeechOptions,
|
|
): UseMutationResult<
|
|
ArrayBuffer, // response data
|
|
unknown, // error
|
|
FormData, // request
|
|
unknown // context
|
|
> => {
|
|
return useMutation([MutationKeys.textToSpeech], {
|
|
mutationFn: (variables: FormData) => dataService.textToSpeech(variables),
|
|
...(options || {}),
|
|
});
|
|
};
|
|
|
|
/**
|
|
* ASSISTANTS
|
|
*/
|
|
|
|
/**
|
|
* Create a new assistant
|
|
*/
|
|
export const useCreateAssistantMutation = (
|
|
options?: t.CreateAssistantMutationOptions,
|
|
): UseMutationResult<t.Assistant, Error, t.AssistantCreateParams> => {
|
|
const queryClient = useQueryClient();
|
|
return useMutation(
|
|
(newAssistantData: t.AssistantCreateParams) => dataService.createAssistant(newAssistantData),
|
|
{
|
|
onMutate: (variables) => options?.onMutate?.(variables),
|
|
onError: (error, variables, context) => options?.onError?.(error, variables, context),
|
|
onSuccess: (newAssistant, variables, context) => {
|
|
const listRes = queryClient.getQueryData<t.AssistantListResponse>([
|
|
QueryKeys.assistants,
|
|
variables.endpoint,
|
|
defaultOrderQuery,
|
|
]);
|
|
|
|
if (!listRes) {
|
|
return options?.onSuccess?.(newAssistant, variables, context);
|
|
}
|
|
|
|
const currentAssistants = [newAssistant, ...JSON.parse(JSON.stringify(listRes.data))];
|
|
|
|
queryClient.setQueryData<t.AssistantListResponse>(
|
|
[QueryKeys.assistants, variables.endpoint, defaultOrderQuery],
|
|
{
|
|
...listRes,
|
|
data: currentAssistants,
|
|
},
|
|
);
|
|
return options?.onSuccess?.(newAssistant, variables, context);
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Hook for updating an assistant
|
|
*/
|
|
export const useUpdateAssistantMutation = (
|
|
options?: t.UpdateAssistantMutationOptions,
|
|
): UseMutationResult<
|
|
t.Assistant,
|
|
Error,
|
|
{ assistant_id: string; data: t.AssistantUpdateParams }
|
|
> => {
|
|
const queryClient = useQueryClient();
|
|
return useMutation(
|
|
({ assistant_id, data }: { assistant_id: string; data: t.AssistantUpdateParams }) => {
|
|
const { endpoint } = data;
|
|
const endpointsConfig = queryClient.getQueryData<t.TEndpointsConfig>([QueryKeys.endpoints]);
|
|
const endpointConfig = endpointsConfig?.[endpoint];
|
|
const version = endpointConfig?.version ?? defaultAssistantsVersion[endpoint];
|
|
return dataService.updateAssistant({
|
|
data,
|
|
version,
|
|
assistant_id,
|
|
});
|
|
},
|
|
{
|
|
onMutate: (variables) => options?.onMutate?.(variables),
|
|
onError: (error, variables, context) => options?.onError?.(error, variables, context),
|
|
onSuccess: (updatedAssistant, variables, context) => {
|
|
const listRes = queryClient.getQueryData<t.AssistantListResponse>([
|
|
QueryKeys.assistants,
|
|
variables.data.endpoint,
|
|
defaultOrderQuery,
|
|
]);
|
|
|
|
if (!listRes) {
|
|
return options?.onSuccess?.(updatedAssistant, variables, context);
|
|
}
|
|
|
|
queryClient.setQueryData<t.AssistantDocument[]>(
|
|
[QueryKeys.assistantDocs, variables.data.endpoint],
|
|
(prev) => {
|
|
if (!prev) {
|
|
return prev;
|
|
}
|
|
return prev.map((doc) => {
|
|
if (doc.assistant_id === variables.assistant_id) {
|
|
return {
|
|
...doc,
|
|
conversation_starters: updatedAssistant.conversation_starters,
|
|
append_current_datetime: variables.data.append_current_datetime,
|
|
};
|
|
}
|
|
return doc;
|
|
});
|
|
},
|
|
);
|
|
|
|
queryClient.setQueryData<t.AssistantListResponse>(
|
|
[QueryKeys.assistants, variables.data.endpoint, defaultOrderQuery],
|
|
{
|
|
...listRes,
|
|
data: listRes.data.map((assistant) => {
|
|
if (assistant.id === variables.assistant_id) {
|
|
return updatedAssistant;
|
|
}
|
|
return assistant;
|
|
}),
|
|
},
|
|
);
|
|
return options?.onSuccess?.(updatedAssistant, variables, context);
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Hook for deleting an assistant
|
|
*/
|
|
export const useDeleteAssistantMutation = (
|
|
options?: t.DeleteAssistantMutationOptions,
|
|
): UseMutationResult<void, Error, t.DeleteAssistantBody> => {
|
|
const queryClient = useQueryClient();
|
|
return useMutation(
|
|
({ assistant_id, model, endpoint }: t.DeleteAssistantBody) => {
|
|
const endpointsConfig = queryClient.getQueryData<t.TEndpointsConfig>([QueryKeys.endpoints]);
|
|
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
|
return dataService.deleteAssistant({ assistant_id, model, version, endpoint });
|
|
},
|
|
{
|
|
onMutate: (variables) => options?.onMutate?.(variables),
|
|
onError: (error, variables, context) => options?.onError?.(error, variables, context),
|
|
onSuccess: (_data, variables, context) => {
|
|
const listRes = queryClient.getQueryData<t.AssistantListResponse>([
|
|
QueryKeys.assistants,
|
|
variables.endpoint,
|
|
defaultOrderQuery,
|
|
]);
|
|
|
|
if (!listRes) {
|
|
return options?.onSuccess?.(_data, variables, context);
|
|
}
|
|
|
|
const data = listRes.data.filter((assistant) => assistant.id !== variables.assistant_id);
|
|
|
|
queryClient.setQueryData<t.AssistantListResponse>(
|
|
[QueryKeys.assistants, variables.endpoint, defaultOrderQuery],
|
|
{
|
|
...listRes,
|
|
data,
|
|
},
|
|
);
|
|
|
|
return options?.onSuccess?.(_data, variables, data);
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Hook for uploading an assistant avatar
|
|
*/
|
|
export const useUploadAssistantAvatarMutation = (
|
|
options?: t.UploadAssistantAvatarOptions,
|
|
): UseMutationResult<
|
|
t.Assistant, // response data
|
|
unknown, // error
|
|
t.AssistantAvatarVariables, // request
|
|
unknown // context
|
|
> => {
|
|
return useMutation([MutationKeys.assistantAvatarUpload], {
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
mutationFn: ({ postCreation, ...variables }: t.AssistantAvatarVariables) =>
|
|
dataService.uploadAssistantAvatar(variables),
|
|
...(options || {}),
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook for updating Assistant Actions
|
|
*/
|
|
export const useUpdateAction = (
|
|
options?: t.UpdateActionOptions,
|
|
): UseMutationResult<
|
|
t.UpdateActionResponse, // response data
|
|
unknown, // error
|
|
t.UpdateActionVariables, // request
|
|
unknown // context
|
|
> => {
|
|
const queryClient = useQueryClient();
|
|
return useMutation([MutationKeys.updateAction], {
|
|
mutationFn: (variables: t.UpdateActionVariables) => dataService.updateAction(variables),
|
|
|
|
onMutate: (variables) => options?.onMutate?.(variables),
|
|
onError: (error, variables, context) => options?.onError?.(error, variables, context),
|
|
onSuccess: (updateActionResponse, variables, context) => {
|
|
const listRes = queryClient.getQueryData<t.AssistantListResponse>([
|
|
QueryKeys.assistants,
|
|
variables.endpoint,
|
|
defaultOrderQuery,
|
|
]);
|
|
|
|
if (!listRes) {
|
|
return options?.onSuccess?.(updateActionResponse, variables, context);
|
|
}
|
|
|
|
const updatedAssistant = updateActionResponse[1];
|
|
|
|
queryClient.setQueryData<t.AssistantListResponse>(
|
|
[QueryKeys.assistants, variables.endpoint, defaultOrderQuery],
|
|
{
|
|
...listRes,
|
|
data: listRes.data.map((assistant) => {
|
|
if (assistant.id === variables.assistant_id) {
|
|
return updatedAssistant;
|
|
}
|
|
return assistant;
|
|
}),
|
|
},
|
|
);
|
|
|
|
queryClient.setQueryData<t.Action[]>([QueryKeys.actions], (prev) => {
|
|
return prev
|
|
?.map((action) => {
|
|
if (action.action_id === variables.action_id) {
|
|
return updateActionResponse[2];
|
|
}
|
|
return action;
|
|
})
|
|
.concat(
|
|
variables.action_id != null && variables.action_id ? [] : [updateActionResponse[2]],
|
|
);
|
|
});
|
|
|
|
return options?.onSuccess?.(updateActionResponse, variables, context);
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook for deleting an Assistant Action
|
|
*/
|
|
export const useDeleteAction = (
|
|
options?: t.DeleteActionOptions,
|
|
): UseMutationResult<
|
|
void, // response data for a delete operation is typically void
|
|
Error, // error type
|
|
t.DeleteActionVariables, // request variables
|
|
unknown // context
|
|
> => {
|
|
const queryClient = useQueryClient();
|
|
return useMutation([MutationKeys.deleteAction], {
|
|
mutationFn: (variables: t.DeleteActionVariables) => {
|
|
const { endpoint } = variables;
|
|
const endpointsConfig = queryClient.getQueryData<t.TEndpointsConfig>([QueryKeys.endpoints]);
|
|
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
|
return dataService.deleteAction({
|
|
...variables,
|
|
version,
|
|
});
|
|
},
|
|
|
|
onMutate: (variables) => options?.onMutate?.(variables),
|
|
onError: (error, variables, context) => options?.onError?.(error, variables, context),
|
|
onSuccess: (_data, variables, context) => {
|
|
let domain: string | undefined = '';
|
|
queryClient.setQueryData<t.Action[]>([QueryKeys.actions], (prev) => {
|
|
return prev?.filter((action) => {
|
|
domain = action.metadata.domain;
|
|
return action.action_id !== variables.action_id;
|
|
});
|
|
});
|
|
|
|
queryClient.setQueryData<t.AssistantListResponse>(
|
|
[QueryKeys.assistants, variables.endpoint, defaultOrderQuery],
|
|
(prev) => {
|
|
if (!prev) {
|
|
return prev;
|
|
}
|
|
|
|
return {
|
|
...prev,
|
|
data: prev.data.map((assistant) => {
|
|
if (assistant.id === variables.assistant_id) {
|
|
return {
|
|
...assistant,
|
|
tools: (assistant.tools ?? []).filter(
|
|
(tool) => !(tool.function?.name.includes(domain ?? '') ?? false),
|
|
),
|
|
};
|
|
}
|
|
return assistant;
|
|
}),
|
|
};
|
|
},
|
|
);
|
|
|
|
return options?.onSuccess?.(_data, variables, context);
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook for verifying email address
|
|
*/
|
|
export const useVerifyEmailMutation = (
|
|
options?: t.VerifyEmailOptions,
|
|
): UseMutationResult<t.VerifyEmailResponse, unknown, t.TVerifyEmail, unknown> => {
|
|
return useMutation({
|
|
mutationFn: (variables: t.TVerifyEmail) => dataService.verifyEmail(variables),
|
|
...(options || {}),
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook for resending verficiation email
|
|
*/
|
|
export const useResendVerificationEmail = (
|
|
options?: t.ResendVerifcationOptions,
|
|
): UseMutationResult<t.VerifyEmailResponse, unknown, t.TResendVerificationEmail, unknown> => {
|
|
return useMutation({
|
|
mutationFn: (variables: t.TResendVerificationEmail) =>
|
|
dataService.resendVerificationEmail(variables),
|
|
...(options || {}),
|
|
});
|
|
};
|
|
|
|
export const useAcceptTermsMutation = (
|
|
options?: t.AcceptTermsMutationOptions,
|
|
): UseMutationResult<t.TAcceptTermsResponse, unknown, void, unknown> => {
|
|
const queryClient = useQueryClient();
|
|
return useMutation(() => dataService.acceptTerms(), {
|
|
onSuccess: (data, variables, context) => {
|
|
queryClient.setQueryData<t.TUserTermsResponse>([QueryKeys.userTerms], {
|
|
termsAccepted: true,
|
|
});
|
|
options?.onSuccess?.(data, variables, context);
|
|
},
|
|
onError: options?.onError,
|
|
onMutate: options?.onMutate,
|
|
});
|
|
};
|