mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 08:47:44 +00:00
Handle unhandled rejections more gracefully. (#4417)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
This commit is contained in:
@@ -4,7 +4,13 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useReducer,
|
||||
useRef,
|
||||
useTransition,
|
||||
} from 'react';
|
||||
import { ConsoleMessageItem } from '../types.js';
|
||||
|
||||
export interface UseConsoleMessagesReturn {
|
||||
@@ -13,75 +19,90 @@ export interface UseConsoleMessagesReturn {
|
||||
clearConsoleMessages: () => void;
|
||||
}
|
||||
|
||||
export function useConsoleMessages(): UseConsoleMessagesReturn {
|
||||
const [consoleMessages, setConsoleMessages] = useState<ConsoleMessageItem[]>(
|
||||
[],
|
||||
);
|
||||
const messageQueueRef = useRef<ConsoleMessageItem[]>([]);
|
||||
const messageQueueTimeoutRef = useRef<number | null>(null);
|
||||
type Action =
|
||||
| { type: 'ADD_MESSAGES'; payload: ConsoleMessageItem[] }
|
||||
| { type: 'CLEAR' };
|
||||
|
||||
const processMessageQueue = useCallback(() => {
|
||||
if (messageQueueRef.current.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newMessagesToAdd = messageQueueRef.current;
|
||||
messageQueueRef.current = [];
|
||||
|
||||
setConsoleMessages((prevMessages) => {
|
||||
const newMessages = [...prevMessages];
|
||||
newMessagesToAdd.forEach((queuedMessage) => {
|
||||
function consoleMessagesReducer(
|
||||
state: ConsoleMessageItem[],
|
||||
action: Action,
|
||||
): ConsoleMessageItem[] {
|
||||
switch (action.type) {
|
||||
case 'ADD_MESSAGES': {
|
||||
const newMessages = [...state];
|
||||
for (const queuedMessage of action.payload) {
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (
|
||||
newMessages.length > 0 &&
|
||||
newMessages[newMessages.length - 1].type === queuedMessage.type &&
|
||||
newMessages[newMessages.length - 1].content === queuedMessage.content
|
||||
lastMessage &&
|
||||
lastMessage.type === queuedMessage.type &&
|
||||
lastMessage.content === queuedMessage.content
|
||||
) {
|
||||
newMessages[newMessages.length - 1].count =
|
||||
(newMessages[newMessages.length - 1].count || 1) + 1;
|
||||
// Create a new object for the last message to ensure React detects
|
||||
// the change, preventing mutation of the existing state object.
|
||||
newMessages[newMessages.length - 1] = {
|
||||
...lastMessage,
|
||||
count: lastMessage.count + 1,
|
||||
};
|
||||
} else {
|
||||
newMessages.push({ ...queuedMessage, count: 1 });
|
||||
}
|
||||
});
|
||||
}
|
||||
return newMessages;
|
||||
});
|
||||
|
||||
messageQueueTimeoutRef.current = null; // Allow next scheduling
|
||||
}, []);
|
||||
|
||||
const scheduleQueueProcessing = useCallback(() => {
|
||||
if (messageQueueTimeoutRef.current === null) {
|
||||
messageQueueTimeoutRef.current = setTimeout(
|
||||
processMessageQueue,
|
||||
0,
|
||||
) as unknown as number;
|
||||
}
|
||||
}, [processMessageQueue]);
|
||||
case 'CLEAR':
|
||||
return [];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export function useConsoleMessages(): UseConsoleMessagesReturn {
|
||||
const [consoleMessages, dispatch] = useReducer(consoleMessagesReducer, []);
|
||||
const messageQueueRef = useRef<ConsoleMessageItem[]>([]);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const [, startTransition] = useTransition();
|
||||
|
||||
const processQueue = useCallback(() => {
|
||||
if (messageQueueRef.current.length > 0) {
|
||||
const messagesToProcess = messageQueueRef.current;
|
||||
messageQueueRef.current = [];
|
||||
startTransition(() => {
|
||||
dispatch({ type: 'ADD_MESSAGES', payload: messagesToProcess });
|
||||
});
|
||||
}
|
||||
timeoutRef.current = null;
|
||||
}, []);
|
||||
|
||||
const handleNewMessage = useCallback(
|
||||
(message: ConsoleMessageItem) => {
|
||||
messageQueueRef.current.push(message);
|
||||
scheduleQueueProcessing();
|
||||
if (!timeoutRef.current) {
|
||||
// Batch updates using a timeout. 16ms is a reasonable delay to batch
|
||||
// rapid-fire messages without noticeable lag.
|
||||
timeoutRef.current = setTimeout(processQueue, 16);
|
||||
}
|
||||
},
|
||||
[scheduleQueueProcessing],
|
||||
[processQueue],
|
||||
);
|
||||
|
||||
const clearConsoleMessages = useCallback(() => {
|
||||
setConsoleMessages([]);
|
||||
if (messageQueueTimeoutRef.current !== null) {
|
||||
clearTimeout(messageQueueTimeoutRef.current);
|
||||
messageQueueTimeoutRef.current = null;
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = null;
|
||||
}
|
||||
messageQueueRef.current = [];
|
||||
startTransition(() => {
|
||||
dispatch({ type: 'CLEAR' });
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(
|
||||
() =>
|
||||
// Cleanup on unmount
|
||||
() => {
|
||||
if (messageQueueTimeoutRef.current !== null) {
|
||||
clearTimeout(messageQueueTimeoutRef.current);
|
||||
}
|
||||
},
|
||||
() => () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user