Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 50 additions & 31 deletions webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -154,6 +156,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
ttl: 1000 * 60 * 15, // 15 minutes TTL for long-running tasks
}),
)
const autoApproveTimeoutRef = useRef<NodeJS.Timeout | null>(null)

const clineAskRef = useRef(clineAsk)
useEffect(() => {
Expand Down Expand Up @@ -408,6 +411,12 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
everVisibleMessagesTsRef.current.clear() // Clear for new task
}, [task?.ts])

useEffect(() => {
if (isHidden) {
everVisibleMessagesTsRef.current.clear()
}
}, [isHidden])

useEffect(() => () => everVisibleMessagesTsRef.current.clear(), [])

useEffect(() => {
Expand Down Expand Up @@ -714,17 +723,15 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
// NOTE: the VSCode window needs to be focused for this to work.
useMount(() => 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) => {
Expand Down Expand Up @@ -1086,6 +1093,14 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
[],
)

useEffect(() => {
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,
Expand Down Expand Up @@ -1124,13 +1139,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
)

useEffect(() => {
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])
Expand All @@ -1151,21 +1166,23 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
// Effect to handle showing the checkpoint warning after a delay
useEffect(() => {
// 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")

Expand Down Expand Up @@ -1239,38 +1256,40 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
)

useEffect(() => {
// 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<void>((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)
}
}
}
autoApprove()

return () => {
if (autoApproveTimeoutRef.current) {
clearTimeout(autoApproveTimeoutRef.current)
autoApproveTimeoutRef.current = null
}
}
}, [
clineAsk,
enableButtons,
Expand Down
Loading