Fix slow typing in chat input with long running sessions (#3722)
This commit is contained in:
@@ -239,6 +239,7 @@ export default function ChatInput({
|
||||
const [isInGlobalHistory, setIsInGlobalHistory] = useState(false);
|
||||
const [hasUserTyped, setHasUserTyped] = useState(false);
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const timeoutRefsRef = useRef<Set<ReturnType<typeof setTimeout>>>(new Set());
|
||||
|
||||
// Use shared file drop hook for ChatInput
|
||||
const {
|
||||
@@ -441,25 +442,50 @@ export default function ChatInput({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [numTokens, toolCount, tokenLimit, isTokenLimitLoaded, addAlert, clearAlerts]);
|
||||
|
||||
// Cleanup effect for component unmount - prevent memory leaks
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Clear any pending timeouts from image processing
|
||||
setPastedImages((currentImages) => {
|
||||
currentImages.forEach((img) => {
|
||||
if (img.filePath) {
|
||||
try {
|
||||
window.electron.deleteTempFile(img.filePath);
|
||||
} catch (error) {
|
||||
console.error('Error deleting temp file:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
return [];
|
||||
});
|
||||
|
||||
// Clear all tracked timeouts
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const timeouts = timeoutRefsRef.current;
|
||||
timeouts.forEach((timeoutId) => {
|
||||
window.clearTimeout(timeoutId);
|
||||
});
|
||||
timeouts.clear();
|
||||
|
||||
// Clear alerts to prevent memory leaks
|
||||
clearAlerts();
|
||||
};
|
||||
}, [clearAlerts]);
|
||||
|
||||
const maxHeight = 10 * 24;
|
||||
|
||||
// Debounced function to update actual value
|
||||
const debouncedSetValue = useMemo(
|
||||
() =>
|
||||
debounce((value: string) => {
|
||||
setValue(value);
|
||||
}, 150),
|
||||
[setValue]
|
||||
);
|
||||
// Immediate function to update actual value - no debounce for better responsiveness
|
||||
const updateValue = React.useCallback((value: string) => {
|
||||
setValue(value);
|
||||
}, []);
|
||||
|
||||
// Debounced autosize function
|
||||
const debouncedAutosize = useMemo(
|
||||
() =>
|
||||
debounce((element: HTMLTextAreaElement) => {
|
||||
element.style.height = '0px'; // Reset height
|
||||
const scrollHeight = element.scrollHeight;
|
||||
element.style.height = Math.min(scrollHeight, maxHeight) + 'px';
|
||||
}, 150),
|
||||
}, 50),
|
||||
[maxHeight]
|
||||
);
|
||||
|
||||
@@ -481,7 +507,7 @@ export default function ChatInput({
|
||||
const cursorPosition = evt.target.selectionStart;
|
||||
|
||||
setDisplayValue(val); // Update display immediately
|
||||
debouncedSetValue(val); // Debounce the actual state update
|
||||
updateValue(val); // Update actual value immediately for better responsiveness
|
||||
debouncedSaveDraft(val); // Save draft with debounce
|
||||
// Mark that the user has typed something
|
||||
setHasUserTyped(true);
|
||||
@@ -544,10 +570,12 @@ export default function ChatInput({
|
||||
},
|
||||
]);
|
||||
|
||||
// Remove the error message after 5 seconds
|
||||
setTimeout(() => {
|
||||
// Remove the error message after 5 seconds with cleanup tracking
|
||||
const timeoutId = setTimeout(() => {
|
||||
setPastedImages((prev) => prev.filter((img) => !img.id.startsWith('error-')));
|
||||
timeoutRefsRef.current.delete(timeoutId);
|
||||
}, 5000);
|
||||
timeoutRefsRef.current.add(timeoutId);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -568,10 +596,12 @@ export default function ChatInput({
|
||||
error: `Image too large (${Math.round(file.size / (1024 * 1024))}MB). Maximum ${MAX_IMAGE_SIZE_MB}MB allowed.`,
|
||||
});
|
||||
|
||||
// Remove the error message after 5 seconds
|
||||
setTimeout(() => {
|
||||
// Remove the error message after 5 seconds with cleanup tracking
|
||||
const timeoutId = setTimeout(() => {
|
||||
setPastedImages((prev) => prev.filter((img) => img.id !== errorId));
|
||||
timeoutRefsRef.current.delete(timeoutId);
|
||||
}, 5000);
|
||||
timeoutRefsRef.current.add(timeoutId);
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -636,11 +666,10 @@ export default function ChatInput({
|
||||
// Cleanup debounced functions on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
debouncedSetValue.cancel?.();
|
||||
debouncedAutosize.cancel?.();
|
||||
debouncedSaveDraft.cancel?.();
|
||||
};
|
||||
}, [debouncedSetValue, debouncedAutosize, debouncedSaveDraft]);
|
||||
}, [debouncedAutosize, debouncedSaveDraft]);
|
||||
|
||||
// Handlers for composition events, which are crucial for proper IME behavior
|
||||
const handleCompositionStart = () => {
|
||||
|
||||
Reference in New Issue
Block a user