diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 90901c84e9..77e1d88df7 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -8,6 +8,8 @@ import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" import useSound from "use-sound" import { LRUCache } from "lru-cache" +import { useDebounceEffect } from "@src/utils/useDebounceEffect" + import type { ClineAsk, ClineMessage } from "@roo-code/types" import { ClineSayBrowserAction, ClineSayTool, ExtensionMessage } from "@roo/ExtensionMessage" @@ -154,6 +156,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction(null) const clineAskRef = useRef(clineAsk) useEffect(() => { @@ -408,6 +411,12 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + if (isHidden) { + everVisibleMessagesTsRef.current.clear() + } + }, [isHidden]) + useEffect(() => () => everVisibleMessagesTsRef.current.clear(), []) useEffect(() => { @@ -714,17 +723,15 @@ const ChatViewComponent: React.ForwardRefRenderFunction textAreaRef.current?.focus()) - useEffect(() => { - const timer = setTimeout(() => { + useDebounceEffect( + () => { if (!isHidden && !sendingDisabled && !enableButtons) { textAreaRef.current?.focus() } - }, 50) - - return () => { - clearTimeout(timer) - } - }, [isHidden, sendingDisabled, enableButtons]) + }, + 50, + [isHidden, sendingDisabled, enableButtons] + ) const visibleMessages = useMemo(() => { const newVisibleMessages = modifiedMessages.filter((message) => { @@ -1086,6 +1093,14 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + return () => { + if (scrollToBottomSmooth && typeof (scrollToBottomSmooth as any).cancel === 'function') { + (scrollToBottomSmooth as any).cancel() + } + } + }, [scrollToBottomSmooth]) + const scrollToBottomAuto = useCallback(() => { virtuosoRef.current?.scrollTo({ top: Number.MAX_SAFE_INTEGER, @@ -1124,13 +1139,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction { - let timerId: NodeJS.Timeout | undefined + let timer: NodeJS.Timeout | undefined if (!disableAutoScrollRef.current) { - timerId = setTimeout(() => scrollToBottomSmooth(), 50) + timer = setTimeout(() => scrollToBottomSmooth(), 50) } return () => { - if (timerId) { - clearTimeout(timerId) + if (timer) { + clearTimeout(timer) } } }, [groupedMessages.length, scrollToBottomSmooth]) @@ -1151,21 +1166,23 @@ const ChatViewComponent: React.ForwardRefRenderFunction { // Only show the warning when there's a task but no visible messages yet - if (task && modifiedMessages.length === 0 && !isStreaming) { + if (task && modifiedMessages.length === 0 && !isStreaming && !isHidden) { const timer = setTimeout(() => { setShowCheckpointWarning(true) }, 5000) // 5 seconds return () => clearTimeout(timer) + } else { + setShowCheckpointWarning(false) } - }, [task, modifiedMessages.length, isStreaming]) + }, [task, modifiedMessages.length, isStreaming, isHidden]) // Effect to hide the checkpoint warning when messages appear useEffect(() => { - if (modifiedMessages.length > 0 || isStreaming) { + if (modifiedMessages.length > 0 || isStreaming || isHidden) { setShowCheckpointWarning(false) } - }, [modifiedMessages.length, isStreaming]) + }, [modifiedMessages.length, isStreaming, isHidden]) const placeholderText = task ? t("chat:typeMessage") : t("chat:typeTask") @@ -1239,31 +1256,26 @@ const ChatViewComponent: React.ForwardRefRenderFunction { - // Only proceed if we have an ask and buttons are enabled. + if (autoApproveTimeoutRef.current) { + clearTimeout(autoApproveTimeoutRef.current) + autoApproveTimeoutRef.current = null + } + if (!clineAsk || !enableButtons) { return } const autoApprove = async () => { if (lastMessage?.ask && isAutoApproved(lastMessage)) { - // Note that `isAutoApproved` can only return true if - // lastMessage is an ask of type "browser_action_launch", - // "use_mcp_server", "command", or "tool". - - // Add delay for write operations. if (lastMessage.ask === "tool" && isWriteToolAction(lastMessage)) { - await new Promise((resolve) => setTimeout(resolve, writeDelayMs)) - if (!isMountedRef.current) { - return - } + await new Promise((resolve) => { + autoApproveTimeoutRef.current = setTimeout(resolve, writeDelayMs) + }) } - vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" }) + if (autoApproveTimeoutRef.current === null || autoApproveTimeoutRef.current) { + vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" }) - // This is copied from `handlePrimaryButtonClick`, which we used - // to call from `autoApprove`. I'm not sure how many of these - // things are actually needed. - if (isMountedRef.current) { setSendingDisabled(true) setClineAsk(undefined) setEnableButtons(false) @@ -1271,6 +1283,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + if (autoApproveTimeoutRef.current) { + clearTimeout(autoApproveTimeoutRef.current) + autoApproveTimeoutRef.current = null + } + } }, [ clineAsk, enableButtons,