diff --git a/web/app/components/base/chat/chat/question.spec.tsx b/web/app/components/base/chat/chat/question.spec.tsx index 0c604d9438..f12f15ffba 100644 --- a/web/app/components/base/chat/chat/question.spec.tsx +++ b/web/app/components/base/chat/chat/question.spec.tsx @@ -245,6 +245,50 @@ describe('Question component', () => { expect(onRegenerate).not.toHaveBeenCalled() }) + it('should not confirm editing when Enter is pressed during IME composition', () => { + const onRegenerate = vi.fn() as unknown as OnRegenerate + + renderWithProvider(makeItem(), onRegenerate) + + fireEvent.click(screen.getByTestId('edit-btn')) + const textbox = screen.getByRole('textbox') + + fireEvent.compositionStart(textbox) + fireEvent.keyDown(textbox, { key: 'Enter', code: 'Enter' }) + + expect(onRegenerate).not.toHaveBeenCalled() + }) + + it('should keep Enter suppressed if a new composition starts before previous composition-end timer finishes', () => { + vi.useFakeTimers() + + try { + const onRegenerate = vi.fn() as unknown as OnRegenerate + renderWithProvider(makeItem(), onRegenerate) + + fireEvent.click(screen.getByTestId('edit-btn')) + const textbox = screen.getByRole('textbox') + + fireEvent.compositionStart(textbox) + fireEvent.compositionEnd(textbox) + fireEvent.compositionStart(textbox) + + vi.advanceTimersByTime(50) + + fireEvent.keyDown(textbox, { key: 'Enter', code: 'Enter' }) + expect(onRegenerate).not.toHaveBeenCalled() + + fireEvent.compositionEnd(textbox) + vi.advanceTimersByTime(50) + + fireEvent.keyDown(textbox, { key: 'Enter', code: 'Enter' }) + expect(onRegenerate).toHaveBeenCalledTimes(1) + } + finally { + vi.useRealTimers() + } + }) + it('should switch siblings when prev/next buttons are clicked', async () => { const user = userEvent.setup() const switchSibling = vi.fn() diff --git a/web/app/components/base/chat/chat/question.tsx b/web/app/components/base/chat/chat/question.tsx index 8c9074bfe2..cb9332dea1 100644 --- a/web/app/components/base/chat/chat/question.tsx +++ b/web/app/components/base/chat/chat/question.tsx @@ -57,6 +57,7 @@ const Question: FC = ({ const [contentWidth, setContentWidth] = useState(0) const contentRef = useRef(null) const isComposingRef = useRef(false) + const compositionEndTimerRef = useRef | null>(null) const handleEdit = useCallback(() => { setIsEditing(true) @@ -84,15 +85,26 @@ const Question: FC = ({ handleResend() }, [handleResend]) - const handleCompositionStart = useCallback(() => { - isComposingRef.current = true + const clearCompositionEndTimer = useCallback(() => { + if (!compositionEndTimerRef.current) + return + + clearTimeout(compositionEndTimerRef.current) + compositionEndTimerRef.current = null }, []) + const handleCompositionStart = useCallback(() => { + clearCompositionEndTimer() + isComposingRef.current = true + }, [clearCompositionEndTimer]) + const handleCompositionEnd = useCallback(() => { - setTimeout(() => { + clearCompositionEndTimer() + compositionEndTimerRef.current = setTimeout(() => { isComposingRef.current = false + compositionEndTimerRef.current = null }, 50) - }, []) + }, [clearCompositionEndTimer]) const handleSwitchSibling = useCallback((direction: 'prev' | 'next') => { if (direction === 'prev') { @@ -122,6 +134,12 @@ const Question: FC = ({ } }, []) + useEffect(() => { + return () => { + clearCompositionEndTimer() + } + }, [clearCompositionEndTimer]) + return (