2024-06-25 03:02:38 -04:00
|
|
|
import { v4 } from 'uuid';
|
|
|
|
import { useQueryClient } from '@tanstack/react-query';
|
|
|
|
import {
|
|
|
|
Constants,
|
|
|
|
QueryKeys,
|
|
|
|
ContentTypes,
|
|
|
|
parseCompactConvo,
|
|
|
|
isAssistantsEndpoint,
|
|
|
|
} from 'librechat-data-provider';
|
2024-08-27 17:03:16 -04:00
|
|
|
import { useSetRecoilState, useResetRecoilState, useRecoilValue } from 'recoil';
|
2024-06-25 03:02:38 -04:00
|
|
|
import type {
|
|
|
|
TMessage,
|
|
|
|
TSubmission,
|
|
|
|
TConversation,
|
|
|
|
TEndpointOption,
|
|
|
|
TEndpointsConfig,
|
|
|
|
} from 'librechat-data-provider';
|
|
|
|
import type { SetterOrUpdater } from 'recoil';
|
|
|
|
import type { TAskFunction, ExtendedFile } from '~/common';
|
|
|
|
import useSetFilesToDelete from '~/hooks/Files/useSetFilesToDelete';
|
|
|
|
import useGetSender from '~/hooks/Conversations/useGetSender';
|
2024-08-27 17:03:16 -04:00
|
|
|
import { getArtifactsMode } from '~/utils/artifacts';
|
2024-08-09 02:11:56 -04:00
|
|
|
import { getEndpointField, logger } from '~/utils';
|
2024-06-25 03:02:38 -04:00
|
|
|
import useUserKey from '~/hooks/Input/useUserKey';
|
|
|
|
import store from '~/store';
|
|
|
|
|
|
|
|
export default function useChatFunctions({
|
|
|
|
index = 0,
|
|
|
|
files,
|
|
|
|
setFiles,
|
|
|
|
getMessages,
|
|
|
|
setMessages,
|
|
|
|
isSubmitting,
|
|
|
|
conversation,
|
|
|
|
latestMessage,
|
|
|
|
setSubmission,
|
|
|
|
setLatestMessage,
|
|
|
|
}: {
|
|
|
|
index?: number;
|
|
|
|
isSubmitting: boolean;
|
|
|
|
paramId?: string | undefined;
|
|
|
|
conversation: TConversation | null;
|
|
|
|
latestMessage: TMessage | null;
|
|
|
|
getMessages: () => TMessage[] | undefined;
|
|
|
|
setMessages: (messages: TMessage[]) => void;
|
|
|
|
files?: Map<string, ExtendedFile>;
|
|
|
|
setFiles?: SetterOrUpdater<Map<string, ExtendedFile>>;
|
|
|
|
setSubmission: SetterOrUpdater<TSubmission | null>;
|
|
|
|
setLatestMessage?: SetterOrUpdater<TMessage | null>;
|
|
|
|
}) {
|
2024-08-27 17:03:16 -04:00
|
|
|
const codeArtifacts = useRecoilValue(store.codeArtifacts);
|
|
|
|
const includeShadcnui = useRecoilValue(store.includeShadcnui);
|
|
|
|
const customPromptMode = useRecoilValue(store.customPromptMode);
|
2024-06-25 03:02:38 -04:00
|
|
|
const resetLatestMultiMessage = useResetRecoilState(store.latestMessageFamily(index + 1));
|
|
|
|
const setShowStopButton = useSetRecoilState(store.showStopButtonByIndex(index));
|
|
|
|
const setFilesToDelete = useSetFilesToDelete();
|
|
|
|
const getSender = useGetSender();
|
|
|
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
const { getExpiry } = useUserKey(conversation?.endpoint ?? '');
|
|
|
|
|
|
|
|
const ask: TAskFunction = (
|
|
|
|
{
|
|
|
|
text,
|
|
|
|
overrideConvoId,
|
|
|
|
overrideUserMessageId,
|
|
|
|
parentMessageId = null,
|
|
|
|
conversationId = null,
|
|
|
|
messageId = null,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
editedText = null,
|
|
|
|
editedMessageId = null,
|
|
|
|
resubmitFiles = false,
|
|
|
|
isRegenerate = false,
|
|
|
|
isContinued = false,
|
|
|
|
isEdited = false,
|
|
|
|
overrideMessages,
|
|
|
|
} = {},
|
|
|
|
) => {
|
|
|
|
setShowStopButton(false);
|
|
|
|
resetLatestMultiMessage();
|
|
|
|
if (!!isSubmitting || text === '') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const endpoint = conversation?.endpoint;
|
|
|
|
if (endpoint === null) {
|
|
|
|
console.error('No endpoint available');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
conversationId = conversationId ?? conversation?.conversationId ?? null;
|
|
|
|
if (conversationId == 'search') {
|
|
|
|
console.error('cannot send any message under search view!');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isContinued && !latestMessage) {
|
|
|
|
console.error('cannot continue AI message without latestMessage!');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const isEditOrContinue = isEdited || isContinued;
|
|
|
|
|
|
|
|
let currentMessages: TMessage[] | null = overrideMessages ?? getMessages() ?? [];
|
|
|
|
|
|
|
|
// construct the query message
|
|
|
|
// this is not a real messageId, it is used as placeholder before real messageId returned
|
|
|
|
text = text.trim();
|
|
|
|
const intermediateId = overrideUserMessageId ?? v4();
|
|
|
|
parentMessageId = parentMessageId || latestMessage?.messageId || Constants.NO_PARENT;
|
|
|
|
|
2024-07-20 01:51:59 -04:00
|
|
|
logger.dir('Ask function called with:', {
|
|
|
|
index,
|
|
|
|
latestMessage,
|
|
|
|
conversationId,
|
|
|
|
intermediateId,
|
|
|
|
parentMessageId,
|
|
|
|
currentMessages,
|
|
|
|
});
|
|
|
|
logger.log('=====================================');
|
|
|
|
|
2024-07-10 23:41:21 -04:00
|
|
|
if (conversationId == Constants.NEW_CONVO) {
|
2024-06-25 03:02:38 -04:00
|
|
|
parentMessageId = Constants.NO_PARENT;
|
|
|
|
currentMessages = [];
|
|
|
|
conversationId = null;
|
|
|
|
}
|
|
|
|
|
2024-08-16 10:30:14 +02:00
|
|
|
const parentMessage = currentMessages.find(
|
2024-06-25 03:02:38 -04:00
|
|
|
(msg) => msg.messageId === latestMessage?.parentMessageId,
|
|
|
|
);
|
|
|
|
|
|
|
|
let thread_id = parentMessage?.thread_id ?? latestMessage?.thread_id;
|
|
|
|
if (!thread_id) {
|
|
|
|
thread_id = currentMessages.find((message) => message.thread_id)?.thread_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
|
|
|
|
const endpointType = getEndpointField(endpointsConfig, endpoint, 'type');
|
|
|
|
|
|
|
|
// set the endpoint option
|
|
|
|
const convo = parseCompactConvo({
|
|
|
|
endpoint,
|
|
|
|
endpointType,
|
|
|
|
conversation: conversation ?? {},
|
|
|
|
});
|
|
|
|
|
|
|
|
const { modelDisplayLabel } = endpointsConfig?.[endpoint ?? ''] ?? {};
|
|
|
|
const endpointOption = {
|
|
|
|
...convo,
|
|
|
|
endpoint,
|
|
|
|
thread_id,
|
|
|
|
endpointType,
|
|
|
|
overrideConvoId,
|
|
|
|
key: getExpiry(),
|
|
|
|
modelDisplayLabel,
|
|
|
|
overrideUserMessageId,
|
2024-08-27 17:03:16 -04:00
|
|
|
artifacts: getArtifactsMode({ codeArtifacts, includeShadcnui, customPromptMode }),
|
2024-06-25 03:02:38 -04:00
|
|
|
} as TEndpointOption;
|
|
|
|
const responseSender = getSender({ model: conversation?.model, ...endpointOption });
|
|
|
|
|
|
|
|
const currentMsg: TMessage = {
|
|
|
|
text,
|
|
|
|
sender: 'User',
|
|
|
|
isCreatedByUser: true,
|
|
|
|
parentMessageId,
|
|
|
|
conversationId,
|
|
|
|
messageId: isContinued && messageId ? messageId : intermediateId,
|
|
|
|
thread_id,
|
|
|
|
error: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
const reuseFiles = (isRegenerate || resubmitFiles) && parentMessage?.files;
|
|
|
|
if (setFiles && reuseFiles && parentMessage.files?.length) {
|
|
|
|
currentMsg.files = parentMessage.files;
|
|
|
|
setFiles(new Map());
|
|
|
|
setFilesToDelete({});
|
|
|
|
} else if (setFiles && files && files.size > 0) {
|
|
|
|
currentMsg.files = Array.from(files.values()).map((file) => ({
|
|
|
|
file_id: file.file_id,
|
|
|
|
filepath: file.filepath,
|
|
|
|
type: file.type || '', // Ensure type is not undefined
|
|
|
|
height: file.height,
|
|
|
|
width: file.width,
|
|
|
|
}));
|
|
|
|
setFiles(new Map());
|
|
|
|
setFilesToDelete({});
|
|
|
|
}
|
|
|
|
|
|
|
|
// construct the placeholder response message
|
|
|
|
const generation = editedText ?? latestMessage?.text ?? '';
|
|
|
|
const responseText = isEditOrContinue ? generation : '';
|
|
|
|
|
|
|
|
const responseMessageId = editedMessageId ?? latestMessage?.messageId ?? null;
|
|
|
|
const initialResponse: TMessage = {
|
|
|
|
sender: responseSender,
|
|
|
|
text: responseText,
|
|
|
|
endpoint: endpoint ?? '',
|
|
|
|
parentMessageId: isRegenerate ? messageId : intermediateId,
|
|
|
|
messageId: responseMessageId ?? `${isRegenerate ? messageId : intermediateId}_`,
|
|
|
|
thread_id,
|
|
|
|
conversationId,
|
|
|
|
unfinished: false,
|
|
|
|
isCreatedByUser: false,
|
|
|
|
isEdited: isEditOrContinue,
|
|
|
|
iconURL: convo.iconURL,
|
|
|
|
error: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (isAssistantsEndpoint(endpoint)) {
|
|
|
|
initialResponse.model = conversation?.assistant_id ?? '';
|
|
|
|
initialResponse.text = '';
|
|
|
|
initialResponse.content = [
|
|
|
|
{
|
|
|
|
type: ContentTypes.TEXT,
|
|
|
|
[ContentTypes.TEXT]: {
|
|
|
|
value: responseText,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
];
|
|
|
|
} else {
|
|
|
|
setShowStopButton(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isContinued) {
|
|
|
|
currentMessages = currentMessages.filter((msg) => msg.messageId !== responseMessageId);
|
|
|
|
}
|
|
|
|
|
|
|
|
const submission: TSubmission = {
|
|
|
|
conversation: {
|
|
|
|
...conversation,
|
|
|
|
conversationId,
|
|
|
|
},
|
|
|
|
endpointOption,
|
|
|
|
userMessage: {
|
|
|
|
...currentMsg,
|
|
|
|
generation,
|
|
|
|
responseMessageId,
|
|
|
|
overrideParentMessageId: isRegenerate ? messageId : null,
|
|
|
|
},
|
|
|
|
messages: currentMessages,
|
|
|
|
isEdited: isEditOrContinue,
|
|
|
|
isContinued,
|
|
|
|
isRegenerate,
|
|
|
|
initialResponse,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (isRegenerate) {
|
|
|
|
setMessages([...submission.messages, initialResponse]);
|
|
|
|
} else {
|
|
|
|
setMessages([...submission.messages, currentMsg, initialResponse]);
|
|
|
|
}
|
|
|
|
if (index === 0 && setLatestMessage) {
|
|
|
|
setLatestMessage(initialResponse);
|
|
|
|
}
|
2024-08-08 14:52:12 -04:00
|
|
|
|
2024-06-25 03:02:38 -04:00
|
|
|
setSubmission(submission);
|
2024-08-21 18:18:45 -04:00
|
|
|
logger.dir('message_stream', submission, { depth: null });
|
2024-06-25 03:02:38 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
const regenerate = ({ parentMessageId }) => {
|
|
|
|
const messages = getMessages();
|
|
|
|
const parentMessage = messages?.find((element) => element.messageId == parentMessageId);
|
|
|
|
|
|
|
|
if (parentMessage && parentMessage.isCreatedByUser) {
|
|
|
|
ask({ ...parentMessage }, { isRegenerate: true });
|
|
|
|
} else {
|
|
|
|
console.error(
|
|
|
|
'Failed to regenerate the message: parentMessage not found or not created by user.',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
ask,
|
|
|
|
regenerate,
|
|
|
|
};
|
|
|
|
}
|