mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 18:00:15 +01:00
🏄♂️ refactor: Optimize Reasoning UI & Token Streaming (#5546)
* ✨ feat: Implement Show Thinking feature; refactor: testing thinking render optimizations * ✨ feat: Refactor Thinking component styles and enhance Markdown rendering * chore: add back removed code, revert type changes * chore: Add back resetCounter effect to Markdown component for improved code block indexing * chore: bump @librechat/agents and google langchain packages * WIP: reasoning type updates * WIP: first pass, reasoning content blocks * chore: revert code * chore: bump @librechat/agents * refactor: optimize reasoning tag handling * style: ul indent padding * feat: add Reasoning component to handle reasoning display * feat: first pass, content reasoning part styling * refactor: add content placeholder for endpoints using new stream handler * refactor: only cache messages when requesting stream audio * fix: circular dep. * fix: add default param * refactor: tts, only request after message stream, fix chrome autoplay * style: update label for submitting state and add localization for 'Thinking...' * fix: improve global audio pause logic and reset active run ID * fix: handle artifact edge cases * fix: remove unnecessary console log from artifact update test * feat: add support for continued message handling with new streaming method --------- Co-authored-by: Marco Beretta <81851188+berry-13@users.noreply.github.com>
This commit is contained in:
parent
d60a149ad9
commit
591a019766
48 changed files with 1791 additions and 726 deletions
|
|
@ -6,12 +6,13 @@ import store from '~/store';
|
|||
function usePauseGlobalAudio(index = 0) {
|
||||
/* Global Audio Variables */
|
||||
const setAudioRunId = useSetRecoilState(store.audioRunFamily(index));
|
||||
const setActiveRunId = useSetRecoilState(store.activeRunFamily(index));
|
||||
const setGlobalIsPlaying = useSetRecoilState(store.globalAudioPlayingFamily(index));
|
||||
const setIsGlobalAudioFetching = useSetRecoilState(store.globalAudioFetchingFamily(index));
|
||||
const [globalAudioURL, setGlobalAudioURL] = useRecoilState(store.globalAudioURLFamily(index));
|
||||
|
||||
const pauseGlobalAudio = useCallback(() => {
|
||||
if (globalAudioURL) {
|
||||
if (globalAudioURL != null && globalAudioURL !== '') {
|
||||
const globalAudio = document.getElementById(globalAudioId);
|
||||
if (globalAudio) {
|
||||
console.log('Pausing global audio', globalAudioURL);
|
||||
|
|
@ -21,14 +22,16 @@ function usePauseGlobalAudio(index = 0) {
|
|||
URL.revokeObjectURL(globalAudioURL);
|
||||
setIsGlobalAudioFetching(false);
|
||||
setGlobalAudioURL(null);
|
||||
setActiveRunId(null);
|
||||
setAudioRunId(null);
|
||||
}
|
||||
}, [
|
||||
setAudioRunId,
|
||||
setActiveRunId,
|
||||
globalAudioURL,
|
||||
setGlobalAudioURL,
|
||||
setGlobalIsPlaying,
|
||||
setIsGlobalAudioFetching,
|
||||
setAudioRunId,
|
||||
]);
|
||||
|
||||
return { pauseGlobalAudio };
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import {
|
|||
Constants,
|
||||
QueryKeys,
|
||||
ContentTypes,
|
||||
EModelEndpoint,
|
||||
parseCompactConvo,
|
||||
isAssistantsEndpoint,
|
||||
EModelEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import { useSetRecoilState, useResetRecoilState, useRecoilValue } from 'recoil';
|
||||
import type {
|
||||
|
|
@ -31,6 +31,15 @@ const logChatRequest = (request: Record<string, unknown>) => {
|
|||
logger.log('=====================================');
|
||||
};
|
||||
|
||||
const usesContentStream = (endpoint: EModelEndpoint | undefined, endpointType?: string) => {
|
||||
if (endpointType === EModelEndpoint.custom) {
|
||||
return true;
|
||||
}
|
||||
if (endpoint === EModelEndpoint.openAI || endpoint === EModelEndpoint.azureOpenAI) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
export default function useChatFunctions({
|
||||
index = 0,
|
||||
files,
|
||||
|
|
@ -219,8 +228,8 @@ export default function useChatFunctions({
|
|||
unfinished: false,
|
||||
isCreatedByUser: false,
|
||||
isEdited: isEditOrContinue,
|
||||
iconURL: convo.iconURL,
|
||||
model: convo.model,
|
||||
iconURL: convo?.iconURL,
|
||||
model: convo?.model,
|
||||
error: false,
|
||||
};
|
||||
|
||||
|
|
@ -247,6 +256,17 @@ export default function useChatFunctions({
|
|||
},
|
||||
];
|
||||
setShowStopButton(true);
|
||||
} else if (usesContentStream(endpoint, endpointType)) {
|
||||
initialResponse.text = '';
|
||||
initialResponse.content = [
|
||||
{
|
||||
type: ContentTypes.TEXT,
|
||||
[ContentTypes.TEXT]: {
|
||||
value: responseText,
|
||||
},
|
||||
},
|
||||
];
|
||||
setShowStopButton(true);
|
||||
} else {
|
||||
setShowStopButton(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ function useTextToSpeechExternal({
|
|||
setDownloadFile(false);
|
||||
};
|
||||
|
||||
const { mutate: processAudio, isLoading: isProcessing } = useTextToSpeechMutation({
|
||||
const { mutate: processAudio } = useTextToSpeechMutation({
|
||||
onMutate: (variables) => {
|
||||
const inputText = (variables.get('input') ?? '') as string;
|
||||
if (inputText.length >= 4096) {
|
||||
|
|
@ -178,13 +178,14 @@ function useTextToSpeechExternal({
|
|||
promiseAudioRef.current = null;
|
||||
setIsSpeaking(false);
|
||||
}
|
||||
}, []);
|
||||
}, [setIsSpeaking]);
|
||||
|
||||
useEffect(() => cancelPromiseSpeech, [cancelPromiseSpeech]);
|
||||
|
||||
const isLoading = useMemo(() => {
|
||||
return isProcessing || (isLast && globalIsFetching && !globalIsPlaying);
|
||||
}, [isProcessing, globalIsFetching, globalIsPlaying, isLast]);
|
||||
const isLoading = useMemo(
|
||||
() => isLast && globalIsFetching && !globalIsPlaying,
|
||||
[globalIsFetching, globalIsPlaying, isLast],
|
||||
);
|
||||
|
||||
const { data: voicesData = [] } = useVoicesQuery();
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,11 @@ type TStepEvent = {
|
|||
|
||||
type MessageDeltaUpdate = { type: ContentTypes.TEXT; text: string; tool_call_ids?: string[] };
|
||||
|
||||
type ReasoningDeltaUpdate = { type: ContentTypes.THINK; think: string };
|
||||
|
||||
type AllContentTypes =
|
||||
| ContentTypes.TEXT
|
||||
| ContentTypes.THINK
|
||||
| ContentTypes.TOOL_CALL
|
||||
| ContentTypes.IMAGE_FILE
|
||||
| ContentTypes.IMAGE_URL
|
||||
|
|
@ -84,6 +87,18 @@ export default function useStepHandler({
|
|||
if (contentPart.tool_call_ids != null) {
|
||||
update.tool_call_ids = contentPart.tool_call_ids;
|
||||
}
|
||||
updatedContent[index] = update;
|
||||
} else if (
|
||||
contentType.startsWith(ContentTypes.THINK) &&
|
||||
ContentTypes.THINK in contentPart &&
|
||||
typeof contentPart.think === 'string'
|
||||
) {
|
||||
const currentContent = updatedContent[index] as ReasoningDeltaUpdate;
|
||||
const update: ReasoningDeltaUpdate = {
|
||||
type: ContentTypes.THINK,
|
||||
think: (currentContent.think || '') + contentPart.think,
|
||||
};
|
||||
|
||||
updatedContent[index] = update;
|
||||
} else if (contentType === ContentTypes.IMAGE_URL && 'image_url' in contentPart) {
|
||||
const currentContent = updatedContent[index] as {
|
||||
|
|
@ -215,6 +230,28 @@ export default function useStepHandler({
|
|||
|
||||
const updatedResponse = updateContent(response, runStep.index, contentPart);
|
||||
|
||||
messageMap.current.set(responseMessageId, updatedResponse);
|
||||
const currentMessages = getMessages() || [];
|
||||
setMessages([...currentMessages.slice(0, -1), updatedResponse]);
|
||||
}
|
||||
} else if (event === 'on_reasoning_delta') {
|
||||
const reasoningDelta = data as Agents.ReasoningDeltaEvent;
|
||||
const runStep = stepMap.current.get(reasoningDelta.id);
|
||||
const responseMessageId = runStep?.runId ?? '';
|
||||
|
||||
if (!runStep || !responseMessageId) {
|
||||
console.warn('No run step or runId found for reasoning delta event');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = messageMap.current.get(responseMessageId);
|
||||
if (response && reasoningDelta.delta.content != null) {
|
||||
const contentPart = Array.isArray(reasoningDelta.delta.content)
|
||||
? reasoningDelta.delta.content[0]
|
||||
: reasoningDelta.delta.content;
|
||||
|
||||
const updatedResponse = updateContent(response, runStep.index, contentPart);
|
||||
|
||||
messageMap.current.set(responseMessageId, updatedResponse);
|
||||
const currentMessages = getMessages() || [];
|
||||
setMessages([...currentMessages.slice(0, -1), updatedResponse]);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue