mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Add tool message types; show tool specific information in webview; separate command from output; add abort button to task card
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from "react"
|
||||
import { ClaudeMessage, ClaudeAsk, ClaudeSay } from "@shared/ExtensionMessage"
|
||||
import { ClaudeMessage, ClaudeAsk, ClaudeSay, ClaudeSayTool } from "@shared/ExtensionMessage"
|
||||
import { VSCodeButton, VSCodeProgressRing, VSCodeBadge } from "@vscode/webview-ui-toolkit/react"
|
||||
import { COMMAND_OUTPUT_STRING } from "../utilities/combineCommandSequences"
|
||||
|
||||
interface ChatRowProps {
|
||||
message: ClaudeMessage
|
||||
@@ -92,6 +93,7 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
|
||||
|
||||
const contentStyle: React.CSSProperties = {
|
||||
margin: 0,
|
||||
whiteSpace: "pre-line",
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
@@ -116,18 +118,58 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
|
||||
case "api_req_finished":
|
||||
return null // Hide this message type
|
||||
case "tool":
|
||||
//const tool = JSON.parse(message.text || "{}") as ClaudeSayTool
|
||||
const tool: ClaudeSayTool = {
|
||||
tool: "editedExistingFile",
|
||||
path: "/path/to/file",
|
||||
}
|
||||
switch (tool.tool) {
|
||||
case "editedExistingFile":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{icon}
|
||||
Edited File
|
||||
</div>
|
||||
<p>Path: {tool.path!}</p>
|
||||
<p>{tool.diff!}</p>
|
||||
</>
|
||||
)
|
||||
case "newFileCreated":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{icon}
|
||||
Created New File
|
||||
</div>
|
||||
<p>Path: {tool.path!}</p>
|
||||
<p>{tool.content!}</p>
|
||||
</>
|
||||
)
|
||||
case "readFile":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{icon}
|
||||
Read File
|
||||
</div>
|
||||
<p>Path: {tool.path!}</p>
|
||||
</>
|
||||
)
|
||||
case "listFiles":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{icon}
|
||||
Viewed Directory
|
||||
</div>
|
||||
<p>Path: {tool.path!}</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
break
|
||||
case "text":
|
||||
return (
|
||||
<>
|
||||
{title && (
|
||||
<div style={headerStyle}>
|
||||
{icon}
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<p style={contentStyle}>{message.text}</p>
|
||||
</>
|
||||
)
|
||||
return <p style={contentStyle}>{message.text}</p>
|
||||
case "error":
|
||||
return (
|
||||
<>
|
||||
@@ -167,6 +209,7 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
|
||||
</>
|
||||
)
|
||||
}
|
||||
break
|
||||
case "ask":
|
||||
switch (message.ask) {
|
||||
case "request_limit_reached":
|
||||
@@ -177,12 +220,23 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
|
||||
{title}
|
||||
</div>
|
||||
<p style={{ ...contentStyle, color: "var(--vscode-errorForeground)" }}>
|
||||
Your task has reached the maximum request limit (maxRequestsPerTask, you can change
|
||||
this in settings). Do you want to keep going or start a new task?
|
||||
{message.text}
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
case "command":
|
||||
const splitMessage = (text: string) => {
|
||||
const outputIndex = text.indexOf(COMMAND_OUTPUT_STRING)
|
||||
if (outputIndex === -1) {
|
||||
return { command: text, output: "" }
|
||||
}
|
||||
return {
|
||||
command: text.slice(0, outputIndex).trim(),
|
||||
output: text.slice(outputIndex + COMMAND_OUTPUT_STRING.length).trim(),
|
||||
}
|
||||
}
|
||||
|
||||
const { command, output } = splitMessage(message.text || "")
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
@@ -190,10 +244,16 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
|
||||
{title}
|
||||
</div>
|
||||
<div style={contentStyle}>
|
||||
<p>Claude would like to run this command. Do you allow this?</p>
|
||||
<pre style={contentStyle}>
|
||||
<code>{message.text}</code>
|
||||
</pre>
|
||||
<p style={contentStyle}>Claude Dev wants to execute the following command:</p>
|
||||
<p style={contentStyle}>{command}</p>
|
||||
{output && (
|
||||
<>
|
||||
<p style={{ ...contentStyle, fontWeight: "bold" }}>
|
||||
{COMMAND_OUTPUT_STRING}
|
||||
</p>
|
||||
<p style={contentStyle}>{output}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
@@ -240,9 +300,7 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
|
||||
}}>
|
||||
{renderContent()}
|
||||
{isExpanded && message.say === "api_req_started" && (
|
||||
<pre style={{ marginTop: "10px" }}>
|
||||
<code>{message.text}</code>
|
||||
</pre>
|
||||
<p style={{ marginTop: "10px" }}>{JSON.stringify(JSON.parse(message.text || "{}").request)}</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -33,7 +33,11 @@ const ChatView = ({ messages }: ChatViewProps) => {
|
||||
|
||||
const scrollToBottom = (instant: boolean = false) => {
|
||||
// https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move
|
||||
(messagesEndRef.current as any)?.scrollIntoView({ behavior: instant ? "instant" : "smooth", block: "nearest", inline: "start" })
|
||||
;(messagesEndRef.current as any)?.scrollIntoView({
|
||||
behavior: instant ? "instant" : "smooth",
|
||||
block: "nearest",
|
||||
inline: "start",
|
||||
})
|
||||
}
|
||||
|
||||
const handlePrimaryButtonClick = () => {
|
||||
@@ -49,8 +53,19 @@ const ChatView = ({ messages }: ChatViewProps) => {
|
||||
setSecondaryButtonText(undefined)
|
||||
}
|
||||
|
||||
// scroll to bottom when new message is added
|
||||
const visibleMessages = useMemo(
|
||||
() =>
|
||||
modifiedMessages.filter(
|
||||
(message) => !(message.type === "ask" && message.ask === "completion_result" && message.text === "")
|
||||
),
|
||||
[modifiedMessages]
|
||||
)
|
||||
useEffect(() => {
|
||||
scrollToBottom()
|
||||
}, [visibleMessages.length])
|
||||
|
||||
useEffect(() => {
|
||||
// if last message is an ask, show user ask UI
|
||||
|
||||
// if user finished a task, then start a new task with a new conversation history since in this moment that the extension is waiting for user response, the user could close the extension and the conversation history would be lost.
|
||||
@@ -110,6 +125,10 @@ const ChatView = ({ messages }: ChatViewProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleTaskCloseButtonClick = () => {
|
||||
vscode.postMessage({ type: "abortTask" })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (textAreaRef.current && !textAreaHeight) {
|
||||
setTextAreaHeight(textAreaRef.current.offsetHeight)
|
||||
@@ -158,6 +177,7 @@ const ChatView = ({ messages }: ChatViewProps) => {
|
||||
tokensIn={apiMetrics.totalTokensIn}
|
||||
tokensOut={apiMetrics.totalTokensOut}
|
||||
totalCost={apiMetrics.totalCost}
|
||||
onClose={handleTaskCloseButtonClick}
|
||||
/>
|
||||
<div
|
||||
className="scrollable"
|
||||
|
||||
@@ -1,32 +1,45 @@
|
||||
import React, { useState } from "react"
|
||||
import TextTruncate from "react-text-truncate"
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
interface TaskHeaderProps {
|
||||
taskText: string
|
||||
tokensIn: number
|
||||
tokensOut: number
|
||||
totalCost: number
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const TaskHeader: React.FC<TaskHeaderProps> = ({ taskText, tokensIn, tokensOut, totalCost }) => {
|
||||
const TaskHeader: React.FC<TaskHeaderProps> = ({ taskText, tokensIn, tokensOut, totalCost, onClose }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const toggleExpand = () => setIsExpanded(!isExpanded)
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "15px 15px 10px 15px",
|
||||
}}>
|
||||
<div style={{ padding: "15px 15px 10px 15px" }}>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "var(--vscode-badge-background)",
|
||||
color: "var(--vscode-badge-foreground)",
|
||||
borderRadius: "3px",
|
||||
padding: "8px",
|
||||
padding: "12px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}>
|
||||
<span style={{ fontWeight: "bold", fontSize: "16px" }}>Task</span>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
onClick={onClose}
|
||||
style={{ marginTop: "-5px", marginRight: "-5px" }}>
|
||||
<span className="codicon codicon-close"></span>
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
<div style={{ fontSize: "var(--vscode-font-size)", lineHeight: "1.5" }}>
|
||||
<TextTruncate
|
||||
line={isExpanded ? 0 : 3}
|
||||
@@ -58,20 +71,24 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({ taskText, tokensIn, tokensOut,
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<span style={{ fontWeight: "bold" }}>Tokens:</span>
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<span style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<i className="codicon codicon-arrow-down" style={{ fontSize: "12px", marginBottom: "-1px" }} />
|
||||
{tokensIn.toLocaleString()}
|
||||
</span>
|
||||
<span style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<i className="codicon codicon-arrow-up" style={{ fontSize: "12px", marginBottom: "-1px" }} />
|
||||
{tokensOut.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<span style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<i
|
||||
className="codicon codicon-arrow-down"
|
||||
style={{ fontSize: "12px", marginBottom: "-2px" }}
|
||||
/>
|
||||
{tokensIn.toLocaleString()}
|
||||
</span>
|
||||
<span style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<i
|
||||
className="codicon codicon-arrow-up"
|
||||
style={{ fontSize: "12px", marginBottom: "-2px" }}
|
||||
/>
|
||||
{tokensOut.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<span style={{ fontWeight: "bold" }}>API Cost:</span>
|
||||
<span>${totalCost.toFixed(4)}</span>
|
||||
</div>
|
||||
|
||||
@@ -27,6 +27,7 @@ export function combineCommandSequences(messages: ClaudeMessage[]): ClaudeMessag
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
if (messages[i].type === "ask" && messages[i].ask === "command") {
|
||||
let combinedText = messages[i].text || ""
|
||||
let didAddOutput = false
|
||||
let j = i + 1
|
||||
|
||||
while (j < messages.length) {
|
||||
@@ -35,6 +36,11 @@ export function combineCommandSequences(messages: ClaudeMessage[]): ClaudeMessag
|
||||
break
|
||||
}
|
||||
if (messages[j].type === "say" && messages[j].say === "command_output") {
|
||||
if (!didAddOutput) {
|
||||
// Add a newline before the first output
|
||||
combinedText += `\n${COMMAND_OUTPUT_STRING}`
|
||||
didAddOutput = true
|
||||
}
|
||||
combinedText += "\n" + (messages[j].text || "")
|
||||
}
|
||||
j++
|
||||
@@ -60,3 +66,4 @@ export function combineCommandSequences(messages: ClaudeMessage[]): ClaudeMessag
|
||||
return msg
|
||||
})
|
||||
}
|
||||
export const COMMAND_OUTPUT_STRING = "Output:"
|
||||
|
||||
Reference in New Issue
Block a user