mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 01:40:15 +01:00
🌿 feat: Multi-response Streaming (#3191)
* chore: comment back handlePlusCommand * chore: ignore .git dir * refactor: pass newConversation to `useSelectMention` refactor: pass newConversation to Mention component refactor: useChatFunctions for modular use of `ask` and `regenerate` refactor: set latest message only for the first index in useChatFunctions refactor: pass setLatestMessage to useChatFunctions refactor: Pass setSubmission to useChatFunctions for submission handling refactor: consolidate event handlers to separate hook from useSSE WIP: additional response handlers feat: responsive added convo, clears on new chat/navigating to chat, assistants excluded feat: Add conversationByKeySelector to select any conversation by index WIP: handle second submission with messages paired to root * style: surface-primary-contrast * refactor: remove unnecessary console.log statement in useChatFunctions * refactor: Consolidate imports in ChatForm and Input hooks * refactor: compositional usage of useSSE for multiple streams * WIP: set latest 'multi' message * WIP: first pass, added response streaming * pass: performant multi-message stream * fix: styling and message render * second pass: modular, performant multi-stream * fix: align parentMessageId of multiMessage * refactor: move resetting latestMultiMessage * chore: update footer text in Chat component * fix: stop button styling * fix: handle abortMessage request for multi-response * clear messages but bug with latest message reset present * fix: add delay for additional message generation * fix: access LAST_CONVO_SETUP by index * style: add div to prevent layout shift before hover buttons render * chore: Update Message component styling for card messages * chore: move hook use order * fix: abort middleware using unsent field from req.body * feat: support multi-response stream from initial message * refactor: buildTree function to improve readability and remove unused code * feat: add logger for frontend dev * refactor: use depth to track if message is really last in its branch * fix(buildTree): default export * fix: share parent message Id and avoid duplication error for multi-response streams * fix: prevent addedConvo reset to response convo * feat: allow setting multi message as latest message to control which to respond to * chore: wrap setSiblingIdxRev with useCallback * chore: styling and allow editing messages * style: styling fixes * feat: Add "AddMultiConvo" component to Chat Header * feat: prevent clearing added convos on endpoint, preset, mention, or modelSpec switch * fix: message styling fixes, mainly related to code blocks * fix: stop button visibility logic * fix: Handle edge case in abortMiddleware for non-existant `abortControllers` * refactor: optimize/memoize icons * chore(GoogleClient): change info to debug logs * style: active message styling * style: prevent layout shift due to placeholder row * chore: remove unused code * fix: Update BaseClient to handle optional request body properties * fix(ci): `onStart` now accepts 2 args, the 2nd being responseMessageId * chore: bump data-provider
This commit is contained in:
parent
eef894e608
commit
156c52e293
72 changed files with 2697 additions and 1326 deletions
182
client/src/hooks/Chat/useChatHelpers.ts
Normal file
182
client/src/hooks/Chat/useChatHelpers.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
import { useCallback, useState } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryKeys } from 'librechat-data-provider';
|
||||
import { useRecoilState, useResetRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { useGetMessagesByConvoId } from 'librechat-data-provider/react-query';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import useChatFunctions from '~/hooks/Chat/useChatFunctions';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import useNewConvo from '~/hooks/useNewConvo';
|
||||
import store from '~/store';
|
||||
|
||||
// this to be set somewhere else
|
||||
export default function useChatHelpers(index = 0, paramId?: string) {
|
||||
const clearAllSubmissions = store.useClearSubmissionState();
|
||||
const [files, setFiles] = useRecoilState(store.filesByIndex(index));
|
||||
const [filesLoading, setFilesLoading] = useState(false);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { isAuthenticated } = useAuthContext();
|
||||
|
||||
const { newConversation } = useNewConvo(index);
|
||||
const { useCreateConversationAtom } = store;
|
||||
const { conversation, setConversation } = useCreateConversationAtom(index);
|
||||
const { conversationId } = conversation ?? {};
|
||||
|
||||
const queryParam = paramId === 'new' ? paramId : conversationId ?? paramId ?? '';
|
||||
|
||||
/* Messages: here simply to fetch, don't export and use `getMessages()` instead */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { data: _messages } = useGetMessagesByConvoId(conversationId ?? '', {
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
const resetLatestMessage = useResetRecoilState(store.latestMessageFamily(index));
|
||||
const [isSubmitting, setIsSubmitting] = useRecoilState(store.isSubmittingFamily(index));
|
||||
const [latestMessage, setLatestMessage] = useRecoilState(store.latestMessageFamily(index));
|
||||
const setSiblingIdx = useSetRecoilState(
|
||||
store.messagesSiblingIdxFamily(latestMessage?.parentMessageId ?? null),
|
||||
);
|
||||
|
||||
const setMessages = useCallback(
|
||||
(messages: TMessage[]) => {
|
||||
queryClient.setQueryData<TMessage[]>([QueryKeys.messages, queryParam], messages);
|
||||
if (queryParam === 'new') {
|
||||
queryClient.setQueryData<TMessage[]>([QueryKeys.messages, conversationId], messages);
|
||||
}
|
||||
},
|
||||
[queryParam, queryClient, conversationId],
|
||||
);
|
||||
|
||||
const getMessages = useCallback(() => {
|
||||
return queryClient.getQueryData<TMessage[]>([QueryKeys.messages, queryParam]);
|
||||
}, [queryParam, queryClient]);
|
||||
|
||||
/* Conversation */
|
||||
// const setActiveConvos = useSetRecoilState(store.activeConversations);
|
||||
|
||||
// const setConversation = useCallback(
|
||||
// (convoUpdate: TConversation) => {
|
||||
// _setConversation(prev => {
|
||||
// const { conversationId: convoId } = prev ?? { conversationId: null };
|
||||
// const { conversationId: currentId } = convoUpdate;
|
||||
// if (currentId && convoId && convoId !== 'new' && convoId !== currentId) {
|
||||
// // for now, we delete the prev convoId from activeConversations
|
||||
// const newActiveConvos = { [currentId]: true };
|
||||
// setActiveConvos(newActiveConvos);
|
||||
// }
|
||||
// return convoUpdate;
|
||||
// });
|
||||
// },
|
||||
// [_setConversation, setActiveConvos],
|
||||
// );
|
||||
|
||||
const setSubmission = useSetRecoilState(store.submissionByIndex(index));
|
||||
|
||||
const { ask, regenerate } = useChatFunctions({
|
||||
index,
|
||||
files,
|
||||
setFiles,
|
||||
getMessages,
|
||||
setMessages,
|
||||
isSubmitting,
|
||||
conversation,
|
||||
latestMessage,
|
||||
setSubmission,
|
||||
setLatestMessage,
|
||||
});
|
||||
|
||||
const continueGeneration = () => {
|
||||
if (!latestMessage) {
|
||||
console.error('Failed to regenerate the message: latestMessage not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = getMessages();
|
||||
|
||||
const parentMessage = messages?.find(
|
||||
(element) => element.messageId == latestMessage.parentMessageId,
|
||||
);
|
||||
|
||||
if (parentMessage && parentMessage.isCreatedByUser) {
|
||||
ask({ ...parentMessage }, { isContinued: true, isRegenerate: true, isEdited: true });
|
||||
} else {
|
||||
console.error(
|
||||
'Failed to regenerate the message: parentMessage not found, or not created by user.',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const stopGenerating = () => clearAllSubmissions();
|
||||
|
||||
const handleStopGenerating = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
stopGenerating();
|
||||
};
|
||||
|
||||
const handleRegenerate = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
const parentMessageId = latestMessage?.parentMessageId;
|
||||
if (!parentMessageId) {
|
||||
console.error('Failed to regenerate the message: parentMessageId not found.');
|
||||
return;
|
||||
}
|
||||
regenerate({ parentMessageId });
|
||||
};
|
||||
|
||||
const handleContinue = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
continueGeneration();
|
||||
setSiblingIdx(0);
|
||||
};
|
||||
|
||||
const [showBingToneSetting, setShowBingToneSetting] = useRecoilState(
|
||||
store.showBingToneSettingFamily(index),
|
||||
);
|
||||
const [showPopover, setShowPopover] = useRecoilState(store.showPopoverFamily(index));
|
||||
const [abortScroll, setAbortScroll] = useRecoilState(store.abortScrollFamily(index));
|
||||
const [preset, setPreset] = useRecoilState(store.presetByIndex(index));
|
||||
const [optionSettings, setOptionSettings] = useRecoilState(store.optionSettingsFamily(index));
|
||||
const [showAgentSettings, setShowAgentSettings] = useRecoilState(
|
||||
store.showAgentSettingsFamily(index),
|
||||
);
|
||||
|
||||
return {
|
||||
newConversation,
|
||||
conversation,
|
||||
setConversation,
|
||||
// getConvos,
|
||||
// setConvos,
|
||||
isSubmitting,
|
||||
setIsSubmitting,
|
||||
getMessages,
|
||||
setMessages,
|
||||
setSiblingIdx,
|
||||
latestMessage,
|
||||
setLatestMessage,
|
||||
resetLatestMessage,
|
||||
ask,
|
||||
index,
|
||||
regenerate,
|
||||
stopGenerating,
|
||||
handleStopGenerating,
|
||||
handleRegenerate,
|
||||
handleContinue,
|
||||
showPopover,
|
||||
setShowPopover,
|
||||
abortScroll,
|
||||
setAbortScroll,
|
||||
showBingToneSetting,
|
||||
setShowBingToneSetting,
|
||||
preset,
|
||||
setPreset,
|
||||
optionSettings,
|
||||
setOptionSettings,
|
||||
showAgentSettings,
|
||||
setShowAgentSettings,
|
||||
files,
|
||||
setFiles,
|
||||
filesLoading,
|
||||
setFilesLoading,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue