|
|
@@ -1,10 +1,10 @@
|
|
|
-import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
|
|
-import debounce from "debounce"
|
|
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"
|
|
|
import { useDeepCompareEffect, useEvent, useMount } from "react-use"
|
|
|
+import debounce from "debounce"
|
|
|
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"
|
|
|
import removeMd from "remove-markdown"
|
|
|
import { Trans } from "react-i18next"
|
|
|
+import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
|
|
|
|
|
import {
|
|
|
ClineAsk,
|
|
|
@@ -28,6 +28,7 @@ import { validateCommand } from "@src/utils/command-validation"
|
|
|
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
|
|
|
|
|
import TelemetryBanner from "../common/TelemetryBanner"
|
|
|
+import { useTaskSearch } from "../history/useTaskSearch"
|
|
|
import HistoryPreview from "../history/HistoryPreview"
|
|
|
import RooHero from "@src/components/welcome/RooHero"
|
|
|
import RooTips from "@src/components/welcome/RooTips"
|
|
|
@@ -38,7 +39,7 @@ import ChatTextArea from "./ChatTextArea"
|
|
|
import TaskHeader from "./TaskHeader"
|
|
|
import AutoApproveMenu from "./AutoApproveMenu"
|
|
|
import SystemPromptWarning from "./SystemPromptWarning"
|
|
|
-import { useTaskSearch } from "../history/useTaskSearch"
|
|
|
+import { CheckpointWarning } from "./CheckpointWarning"
|
|
|
|
|
|
export interface ChatViewProps {
|
|
|
isHidden: boolean
|
|
|
@@ -267,7 +268,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
}
|
|
|
break
|
|
|
case "say":
|
|
|
- // don't want to reset since there could be a "say" after an "ask" while ask is waiting for response
|
|
|
+ // Don't want to reset since there could be a "say" after
|
|
|
+ // an "ask" while ask is waiting for response.
|
|
|
switch (lastMessage.say) {
|
|
|
case "api_req_retry_delayed":
|
|
|
setTextAreaDisabled(true)
|
|
|
@@ -301,13 +303,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
}
|
|
|
break
|
|
|
}
|
|
|
- } else {
|
|
|
- // this would get called after sending the first message, so we have to watch messages.length instead
|
|
|
- // No messages, so user has to submit a task
|
|
|
- // setTextAreaDisabled(false)
|
|
|
- // setClineAsk(undefined)
|
|
|
- // setPrimaryButtonText(undefined)
|
|
|
- // setSecondaryButtonText(undefined)
|
|
|
}
|
|
|
}, [lastMessage, secondLastMessage])
|
|
|
|
|
|
@@ -321,23 +316,32 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
}
|
|
|
}, [messages.length])
|
|
|
|
|
|
- useEffect(() => {
|
|
|
- setExpandedRows({})
|
|
|
- }, [task?.ts])
|
|
|
+ useEffect(() => setExpandedRows({}), [task?.ts])
|
|
|
|
|
|
const isStreaming = useMemo(() => {
|
|
|
- const isLastAsk = !!modifiedMessages.at(-1)?.ask // checking clineAsk isn't enough since messages effect may be called again for a tool for example, set clineAsk to its value, and if the next message is not an ask then it doesn't reset. This is likely due to how much more often we're updating messages as compared to before, and should be resolved with optimizations as it's likely a rendering bug. but as a final guard for now, the cancel button will show if the last message is not an ask
|
|
|
+ // Checking clineAsk isn't enough since messages effect may be called
|
|
|
+ // again for a tool for example, set clineAsk to its value, and if the
|
|
|
+ // next message is not an ask then it doesn't reset. This is likely due
|
|
|
+ // to how much more often we're updating messages as compared to before,
|
|
|
+ // and should be resolved with optimizations as it's likely a rendering
|
|
|
+ // bug. But as a final guard for now, the cancel button will show if the
|
|
|
+ // last message is not an ask.
|
|
|
+ const isLastAsk = !!modifiedMessages.at(-1)?.ask
|
|
|
+
|
|
|
const isToolCurrentlyAsking =
|
|
|
isLastAsk && clineAsk !== undefined && enableButtons && primaryButtonText !== undefined
|
|
|
+
|
|
|
if (isToolCurrentlyAsking) {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
const isLastMessagePartial = modifiedMessages.at(-1)?.partial === true
|
|
|
+
|
|
|
if (isLastMessagePartial) {
|
|
|
return true
|
|
|
} else {
|
|
|
const lastApiReqStarted = findLast(modifiedMessages, (message) => message.say === "api_req_started")
|
|
|
+
|
|
|
if (
|
|
|
lastApiReqStarted &&
|
|
|
lastApiReqStarted.text !== null &&
|
|
|
@@ -345,9 +349,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
lastApiReqStarted.say === "api_req_started"
|
|
|
) {
|
|
|
const cost = JSON.parse(lastApiReqStarted.text).cost
|
|
|
+
|
|
|
if (cost === undefined) {
|
|
|
- // api request has not finished yet
|
|
|
- return true
|
|
|
+ return true // API request has not finished yet.
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -371,6 +375,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
const handleSendMessage = useCallback(
|
|
|
(text: string, images: string[]) => {
|
|
|
text = text.trim()
|
|
|
+
|
|
|
if (text || images.length > 0) {
|
|
|
if (messages.length === 0) {
|
|
|
vscode.postMessage({ type: "newTask", text, images })
|
|
|
@@ -391,6 +396,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
// There is no other case that a textfield should be enabled.
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
handleChatReset()
|
|
|
}
|
|
|
},
|
|
|
@@ -412,16 +418,15 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
[inputValue, selectedImages],
|
|
|
)
|
|
|
|
|
|
- const startNewTask = useCallback(() => {
|
|
|
- vscode.postMessage({ type: "clearTask" })
|
|
|
- }, [])
|
|
|
+ const startNewTask = useCallback(() => vscode.postMessage({ type: "clearTask" }), [])
|
|
|
|
|
|
- /*
|
|
|
- This logic depends on the useEffect[messages] above to set clineAsk, after which buttons are shown and we then send an askResponse to the extension.
|
|
|
- */
|
|
|
+ // This logic depends on the useEffect[messages] above to set clineAsk,
|
|
|
+ // after which buttons are shown and we then send an askResponse to the
|
|
|
+ // extension.
|
|
|
const handlePrimaryButtonClick = useCallback(
|
|
|
(text?: string, images?: string[]) => {
|
|
|
const trimmedInput = text?.trim()
|
|
|
+
|
|
|
switch (clineAsk) {
|
|
|
case "api_req_failed":
|
|
|
case "command":
|
|
|
@@ -454,6 +459,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
vscode.postMessage({ type: "terminalOperation", terminalOperation: "continue" })
|
|
|
break
|
|
|
}
|
|
|
+
|
|
|
setTextAreaDisabled(true)
|
|
|
setClineAsk(undefined)
|
|
|
setEnableButtons(false)
|
|
|
@@ -508,15 +514,11 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
[clineAsk, startNewTask, isStreaming],
|
|
|
)
|
|
|
|
|
|
- const handleTaskCloseButtonClick = useCallback(() => {
|
|
|
- startNewTask()
|
|
|
- }, [startNewTask])
|
|
|
+ const handleTaskCloseButtonClick = useCallback(() => startNewTask(), [startNewTask])
|
|
|
|
|
|
const { info: model } = useSelectedModel(apiConfiguration)
|
|
|
|
|
|
- const selectImages = useCallback(() => {
|
|
|
- vscode.postMessage({ type: "selectImages" })
|
|
|
- }, [])
|
|
|
+ const selectImages = useCallback(() => vscode.postMessage({ type: "selectImages" }), [])
|
|
|
|
|
|
const shouldDisableImages =
|
|
|
!model?.supportsImages || textAreaDisabled || selectedImages.length >= MAX_IMAGES_PER_MESSAGE
|
|
|
@@ -524,6 +526,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
const handleMessage = useCallback(
|
|
|
(e: MessageEvent) => {
|
|
|
const message: ExtensionMessage = e.data
|
|
|
+
|
|
|
switch (message.type) {
|
|
|
case "action":
|
|
|
switch (message.action!) {
|
|
|
@@ -564,7 +567,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
break
|
|
|
}
|
|
|
}
|
|
|
- // textAreaRef.current is not explicitly required here since react gaurantees that ref will be stable across re-renders, and we're not using its value but its reference.
|
|
|
+ // textAreaRef.current is not explicitly required here since React
|
|
|
+ // guarantees that ref will be stable across re-renders, and we're
|
|
|
+ // not using its value but its reference.
|
|
|
},
|
|
|
[
|
|
|
isHidden,
|
|
|
@@ -580,10 +585,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
|
|
|
useEvent("message", handleMessage)
|
|
|
|
|
|
- useMount(() => {
|
|
|
- // NOTE: the vscode window needs to be focused for this to work
|
|
|
- textAreaRef.current?.focus()
|
|
|
- })
|
|
|
+ // NOTE: the VSCode window needs to be focused for this to work.
|
|
|
+ useMount(() => textAreaRef.current?.focus())
|
|
|
|
|
|
useEffect(() => {
|
|
|
const timer = setTimeout(() => {
|
|
|
@@ -591,6 +594,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
textAreaRef.current?.focus()
|
|
|
}
|
|
|
}, 50)
|
|
|
+
|
|
|
return () => {
|
|
|
clearTimeout(timer)
|
|
|
}
|
|
|
@@ -600,23 +604,28 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
return modifiedMessages.filter((message) => {
|
|
|
switch (message.ask) {
|
|
|
case "completion_result":
|
|
|
- // don't show a chat row for a completion_result ask without text. This specific type of message only occurs if cline wants to execute a command as part of its completion result, in which case we interject the completion_result tool with the execute_command tool.
|
|
|
+ // Don't show a chat row for a completion_result ask without
|
|
|
+ // text. This specific type of message only occurs if cline
|
|
|
+ // wants to execute a command as part of its completion
|
|
|
+ // result, in which case we interject the completion_result
|
|
|
+ // tool with the execute_command tool.
|
|
|
if (message.text === "") {
|
|
|
return false
|
|
|
}
|
|
|
break
|
|
|
- case "api_req_failed": // this message is used to update the latest api_req_started that the request failed
|
|
|
+ case "api_req_failed": // This message is used to update the latest `api_req_started` that the request failed.
|
|
|
case "resume_task":
|
|
|
case "resume_completed_task":
|
|
|
return false
|
|
|
}
|
|
|
switch (message.say) {
|
|
|
- case "api_req_finished": // combineApiRequests removes this from modifiedMessages anyways
|
|
|
- case "api_req_retried": // this message is used to update the latest api_req_started that the request was retried
|
|
|
- case "api_req_deleted": // aggregated api_req metrics from deleted messages
|
|
|
+ case "api_req_finished": // `combineApiRequests` removes this from `modifiedMessages` anyways.
|
|
|
+ case "api_req_retried": // This message is used to update the latest `api_req_started` that the request was retried.
|
|
|
+ case "api_req_deleted": // Aggregated `api_req` metrics from deleted messages.
|
|
|
return false
|
|
|
case "api_req_retry_delayed":
|
|
|
- // Only show the retry message if it's the last message or the last messages is api_req_retry_delayed+resume_task
|
|
|
+ // Only show the retry message if it's the last message or
|
|
|
+ // the last messages is api_req_retry_delayed+resume_task.
|
|
|
const last1 = modifiedMessages.at(-1)
|
|
|
const last2 = modifiedMessages.at(-2)
|
|
|
if (last1?.ask === "resume_task" && last2 === message) {
|
|
|
@@ -624,7 +633,10 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
}
|
|
|
return message === last1
|
|
|
case "text":
|
|
|
- // Sometimes cline returns an empty text message, we don't want to render these. (We also use a say text for user messages, so in case they just sent images we still render that)
|
|
|
+ // Sometimes cline returns an empty text message, we don't
|
|
|
+ // want to render these. (We also use a say text for user
|
|
|
+ // messages, so in case they just sent images we still
|
|
|
+ // render that.)
|
|
|
if ((message.text ?? "") === "" && (message.images?.length ?? 0) === 0) {
|
|
|
return false
|
|
|
}
|
|
|
@@ -641,7 +653,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
if (!message.text) {
|
|
|
return true
|
|
|
}
|
|
|
+
|
|
|
const tool = JSON.parse(message.text)
|
|
|
+
|
|
|
return [
|
|
|
"readFile",
|
|
|
"listFiles",
|
|
|
@@ -651,6 +665,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
"searchFiles",
|
|
|
].includes(tool.tool)
|
|
|
}
|
|
|
+
|
|
|
return false
|
|
|
}, [])
|
|
|
|
|
|
@@ -659,7 +674,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
if (!message.text) {
|
|
|
return true
|
|
|
}
|
|
|
+
|
|
|
const tool = JSON.parse(message.text)
|
|
|
+
|
|
|
return [
|
|
|
"editedExistingFile",
|
|
|
"appliedDiff",
|
|
|
@@ -668,6 +685,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
"insertContent",
|
|
|
].includes(tool.tool)
|
|
|
}
|
|
|
+
|
|
|
return false
|
|
|
}, [])
|
|
|
|
|
|
@@ -677,19 +695,22 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
if (!message.text) {
|
|
|
return true
|
|
|
}
|
|
|
+
|
|
|
const mcpServerUse = JSON.parse(message.text) as { type: string; serverName: string; toolName: string }
|
|
|
+
|
|
|
if (mcpServerUse.type === "use_mcp_tool") {
|
|
|
const server = mcpServers?.find((s: McpServer) => s.name === mcpServerUse.serverName)
|
|
|
const tool = server?.tools?.find((t: McpTool) => t.name === mcpServerUse.toolName)
|
|
|
return tool?.alwaysAllow || false
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
return false
|
|
|
},
|
|
|
[mcpServers],
|
|
|
)
|
|
|
|
|
|
- // Check if a command message is allowed
|
|
|
+ // Check if a command message is allowed.
|
|
|
const isAllowedCommand = useCallback(
|
|
|
(message: ClineMessage | undefined): boolean => {
|
|
|
if (message?.type !== "ask") return false
|
|
|
@@ -717,6 +738,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
// For read/write operations, check if it's outside workspace and if we have permission for that
|
|
|
if (message.ask === "tool") {
|
|
|
let tool: any = {}
|
|
|
+
|
|
|
try {
|
|
|
tool = JSON.parse(message.text || "{}")
|
|
|
} catch (error) {
|
|
|
@@ -731,6 +753,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
if (tool.content === "create_mode") {
|
|
|
return alwaysAllowModeSwitch
|
|
|
}
|
|
|
+
|
|
|
if (tool.content === "create_mcp_server") {
|
|
|
return alwaysAllowMcp
|
|
|
}
|
|
|
@@ -776,9 +799,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
)
|
|
|
|
|
|
useEffect(() => {
|
|
|
- // this ensures the first message is not read, future user messages are labelled as user_feedback
|
|
|
+ // This ensures the first message is not read, future user messages are
|
|
|
+ // labeled as `user_feedback`.
|
|
|
if (lastMessage && messages.length > 1) {
|
|
|
- //console.log(JSON.stringify(lastMessage))
|
|
|
if (
|
|
|
lastMessage.text && // has text
|
|
|
(lastMessage.say === "text" || lastMessage.say === "completion_result") && // is a text message
|
|
|
@@ -804,9 +827,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Only execute when isStreaming changes from true to false
|
|
|
+ // Only execute when isStreaming changes from true to false.
|
|
|
if (wasStreaming && !isStreaming && lastMessage) {
|
|
|
- // Play appropriate sound based on lastMessage content
|
|
|
+ // Play appropriate sound based on lastMessage content.
|
|
|
if (lastMessage.type === "ask") {
|
|
|
// Don't play sounds for auto-approved actions
|
|
|
if (!isAutoApproved(lastMessage)) {
|
|
|
@@ -834,18 +857,21 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- // Update previous value
|
|
|
+
|
|
|
+ // Update previous value.
|
|
|
setWasStreaming(isStreaming)
|
|
|
}, [isStreaming, lastMessage, wasStreaming, isAutoApproved, messages.length])
|
|
|
|
|
|
const isBrowserSessionMessage = (message: ClineMessage): boolean => {
|
|
|
- // which of visible messages are browser session messages, see above
|
|
|
+ // Which of visible messages are browser session messages, see above.
|
|
|
if (message.type === "ask") {
|
|
|
return ["browser_action_launch"].includes(message.ask!)
|
|
|
}
|
|
|
+
|
|
|
if (message.type === "say") {
|
|
|
return ["api_req_started", "text", "browser_action", "browser_action_result"].includes(message.say!)
|
|
|
}
|
|
|
+
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
@@ -864,20 +890,24 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
|
|
|
visibleMessages.forEach((message) => {
|
|
|
if (message.ask === "browser_action_launch") {
|
|
|
- // complete existing browser session if any
|
|
|
+ // Complete existing browser session if any.
|
|
|
endBrowserSession()
|
|
|
- // start new
|
|
|
+ // Start new.
|
|
|
isInBrowserSession = true
|
|
|
currentGroup.push(message)
|
|
|
} else if (isInBrowserSession) {
|
|
|
- // end session if api_req_started is cancelled
|
|
|
+ // End session if `api_req_started` is cancelled.
|
|
|
|
|
|
if (message.say === "api_req_started") {
|
|
|
- // get last api_req_started in currentGroup to check if it's cancelled. If it is then this api req is not part of the current browser session
|
|
|
+ // Get last `api_req_started` in currentGroup to check if
|
|
|
+ // it's cancelled. If it is then this api req is not part
|
|
|
+ // of the current browser session.
|
|
|
const lastApiReqStarted = [...currentGroup].reverse().find((m) => m.say === "api_req_started")
|
|
|
+
|
|
|
if (lastApiReqStarted?.text !== null && lastApiReqStarted?.text !== undefined) {
|
|
|
const info = JSON.parse(lastApiReqStarted.text)
|
|
|
const isCancelled = info.cancelReason !== null && info.cancelReason !== undefined
|
|
|
+
|
|
|
if (isCancelled) {
|
|
|
endBrowserSession()
|
|
|
result.push(message)
|
|
|
@@ -918,27 +948,20 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
|
|
|
const scrollToBottomSmooth = useMemo(
|
|
|
() =>
|
|
|
- debounce(
|
|
|
- () => {
|
|
|
- virtuosoRef.current?.scrollTo({
|
|
|
- top: Number.MAX_SAFE_INTEGER,
|
|
|
- behavior: "smooth",
|
|
|
- })
|
|
|
- },
|
|
|
- 10,
|
|
|
- { immediate: true },
|
|
|
- ),
|
|
|
+ debounce(() => virtuosoRef.current?.scrollTo({ top: Number.MAX_SAFE_INTEGER, behavior: "smooth" }), 10, {
|
|
|
+ immediate: true,
|
|
|
+ }),
|
|
|
[],
|
|
|
)
|
|
|
|
|
|
const scrollToBottomAuto = useCallback(() => {
|
|
|
virtuosoRef.current?.scrollTo({
|
|
|
top: Number.MAX_SAFE_INTEGER,
|
|
|
- behavior: "auto", // instant causes crash
|
|
|
+ behavior: "auto", // Instant causes crash.
|
|
|
})
|
|
|
}, [])
|
|
|
|
|
|
- // scroll when user toggles certain rows
|
|
|
+ // Scroll when user toggles certain rows.
|
|
|
const toggleRowExpansion = useCallback(
|
|
|
(ts: number) => {
|
|
|
const isCollapsing = expandedRows[ts] ?? false
|
|
|
@@ -955,29 +978,23 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
lastGroup?.say === "api_req_started" &&
|
|
|
!expandedRows[lastGroup.ts]
|
|
|
|
|
|
- setExpandedRows((prev) => ({
|
|
|
- ...prev,
|
|
|
- [ts]: !prev[ts],
|
|
|
- }))
|
|
|
+ setExpandedRows((prev) => ({ ...prev, [ts]: !prev[ts] }))
|
|
|
|
|
|
- // disable auto scroll when user expands row
|
|
|
+ // Disable auto scroll when user expands row
|
|
|
if (!isCollapsing) {
|
|
|
disableAutoScrollRef.current = true
|
|
|
}
|
|
|
|
|
|
if (isCollapsing && isAtBottom) {
|
|
|
- const timer = setTimeout(() => {
|
|
|
- scrollToBottomAuto()
|
|
|
- }, 0)
|
|
|
+ const timer = setTimeout(() => scrollToBottomAuto(), 0)
|
|
|
return () => clearTimeout(timer)
|
|
|
} else if (isLast || isSecondToLast) {
|
|
|
if (isCollapsing) {
|
|
|
if (isSecondToLast && !isLastCollapsedApiReq) {
|
|
|
return
|
|
|
}
|
|
|
- const timer = setTimeout(() => {
|
|
|
- scrollToBottomAuto()
|
|
|
- }, 0)
|
|
|
+
|
|
|
+ const timer = setTimeout(() => scrollToBottomAuto(), 0)
|
|
|
return () => clearTimeout(timer)
|
|
|
} else {
|
|
|
const timer = setTimeout(() => {
|
|
|
@@ -986,6 +1003,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
align: "start",
|
|
|
})
|
|
|
}, 0)
|
|
|
+
|
|
|
return () => clearTimeout(timer)
|
|
|
}
|
|
|
}
|
|
|
@@ -999,9 +1017,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
if (isTaller) {
|
|
|
scrollToBottomSmooth()
|
|
|
} else {
|
|
|
- setTimeout(() => {
|
|
|
- scrollToBottomAuto()
|
|
|
- }, 0)
|
|
|
+ setTimeout(() => scrollToBottomAuto(), 0)
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
@@ -1010,22 +1026,23 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
|
|
|
useEffect(() => {
|
|
|
if (!disableAutoScrollRef.current) {
|
|
|
- setTimeout(() => {
|
|
|
- scrollToBottomSmooth()
|
|
|
- }, 50)
|
|
|
- // return () => clearTimeout(timer) // dont cleanup since if visibleMessages.length changes it cancels.
|
|
|
+ setTimeout(() => scrollToBottomSmooth(), 50)
|
|
|
+ // Don't cleanup since if visibleMessages.length changes it cancels.
|
|
|
+ // return () => clearTimeout(timer)
|
|
|
}
|
|
|
}, [groupedMessages.length, scrollToBottomSmooth])
|
|
|
|
|
|
const handleWheel = useCallback((event: Event) => {
|
|
|
const wheelEvent = event as WheelEvent
|
|
|
+
|
|
|
if (wheelEvent.deltaY && wheelEvent.deltaY < 0) {
|
|
|
if (scrollContainerRef.current?.contains(wheelEvent.target as Node)) {
|
|
|
- // user scrolled up
|
|
|
+ // User scrolled up
|
|
|
disableAutoScrollRef.current = true
|
|
|
}
|
|
|
}
|
|
|
}, [])
|
|
|
+
|
|
|
useEvent("wheel", handleWheel, window, { passive: true }) // passive improves scrolling performance
|
|
|
|
|
|
// Effect to handle showing the checkpoint warning after a delay
|
|
|
@@ -1047,40 +1064,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
}
|
|
|
}, [modifiedMessages.length, isStreaming])
|
|
|
|
|
|
- // Checkpoint warning component
|
|
|
- const CheckpointWarningMessage = useCallback(
|
|
|
- () => (
|
|
|
- <div className="flex items-center p-3 my-3 bg-vscode-inputValidation-warningBackground border border-vscode-inputValidation-warningBorder rounded">
|
|
|
- <span className="codicon codicon-loading codicon-modifier-spin mr-2" />
|
|
|
- <span className="text-vscode-foreground">
|
|
|
- <Trans
|
|
|
- i18nKey="chat:checkpoint.initializingWarning"
|
|
|
- components={{
|
|
|
- settingsLink: (
|
|
|
- <VSCodeLink
|
|
|
- href="#"
|
|
|
- onClick={(e) => {
|
|
|
- e.preventDefault()
|
|
|
- window.postMessage(
|
|
|
- {
|
|
|
- type: "action",
|
|
|
- action: "settingsButtonClicked",
|
|
|
- values: { section: "checkpoints" },
|
|
|
- },
|
|
|
- "*",
|
|
|
- )
|
|
|
- }}
|
|
|
- className="inline px-0.5"
|
|
|
- />
|
|
|
- ),
|
|
|
- }}
|
|
|
- />
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- ),
|
|
|
- [],
|
|
|
- )
|
|
|
-
|
|
|
const placeholderText = task ? t("chat:typeMessage") : t("chat:typeTask")
|
|
|
|
|
|
const itemContent = useCallback(
|
|
|
@@ -1142,15 +1125,18 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
)
|
|
|
|
|
|
useEffect(() => {
|
|
|
- // Only proceed if we have an ask and buttons are enabled
|
|
|
- if (!clineAsk || !enableButtons) return
|
|
|
+ // Only proceed if we have an ask and buttons are enabled.
|
|
|
+ if (!clineAsk || !enableButtons) {
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
const autoApprove = async () => {
|
|
|
if (isAutoApproved(lastMessage)) {
|
|
|
- // Add delay for write operations
|
|
|
+ // Add delay for write operations.
|
|
|
if (lastMessage?.ask === "tool" && isWriteToolAction(lastMessage)) {
|
|
|
await new Promise((resolve) => setTimeout(resolve, writeDelayMs))
|
|
|
}
|
|
|
+
|
|
|
handlePrimaryButtonClick()
|
|
|
}
|
|
|
}
|
|
|
@@ -1235,17 +1221,15 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
onClose={handleTaskCloseButtonClick}
|
|
|
/>
|
|
|
|
|
|
- {/* System prompt override warning */}
|
|
|
{hasSystemPromptOverride && (
|
|
|
<div className="px-3">
|
|
|
<SystemPromptWarning />
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
|
- {/* Checkpoint warning message */}
|
|
|
{showCheckpointWarning && (
|
|
|
<div className="px-3">
|
|
|
- <CheckpointWarningMessage />
|
|
|
+ <CheckpointWarning />
|
|
|
</div>
|
|
|
)}
|
|
|
</>
|