mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Add combineCommandSequences and combineApiRequests to group certain message types
This commit is contained in:
@@ -81,14 +81,15 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, cost }) => {
|
|||||||
<VSCodeProgressRing />
|
<VSCodeProgressRing />
|
||||||
)}
|
)}
|
||||||
{cost && <VSCodeTag>{cost}</VSCodeTag>}
|
{cost && <VSCodeTag>{cost}</VSCodeTag>}
|
||||||
</div>
|
|
||||||
<VSCodeButton
|
<VSCodeButton
|
||||||
appearance="icon"
|
appearance="icon"
|
||||||
aria-label="Toggle Details"
|
aria-label="Toggle Details"
|
||||||
onClick={() => setIsExpanded(!isExpanded)}>
|
onClick={() => setIsExpanded(!isExpanded)}>
|
||||||
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
|
<span
|
||||||
|
className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
|
||||||
</VSCodeButton>
|
</VSCodeButton>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
case "api_req_finished":
|
case "api_req_finished":
|
||||||
return null // Hide this message type
|
return null // Hide this message type
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { ClaudeAsk, ClaudeMessage, ExtensionMessage } from "@shared/ExtensionMessage"
|
import { ClaudeAsk, ClaudeMessage, ExtensionMessage } from "@shared/ExtensionMessage"
|
||||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||||
import { KeyboardEvent, useEffect, useRef, useState } from "react"
|
import { KeyboardEvent, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import DynamicTextArea from "react-textarea-autosize"
|
import DynamicTextArea from "react-textarea-autosize"
|
||||||
import { vscode } from "../utilities/vscode"
|
import { vscode } from "../utilities/vscode"
|
||||||
import { ClaudeAskResponse } from "@shared/WebviewMessage"
|
import { ClaudeAskResponse } from "@shared/WebviewMessage"
|
||||||
import ChatRow from "./ChatRow"
|
import ChatRow from "./ChatRow"
|
||||||
|
import { combineCommandSequences } from "../utilities/combineCommandSequences"
|
||||||
|
import { combineApiRequests } from "../utilities/combineApiRequests"
|
||||||
|
|
||||||
interface ChatViewProps {
|
interface ChatViewProps {
|
||||||
messages: ClaudeMessage[]
|
messages: ClaudeMessage[]
|
||||||
@@ -21,43 +23,68 @@ const ChatView = ({}: ChatViewProps) => {
|
|||||||
const baseDate = new Date("2024-07-08T00:00:00Z")
|
const baseDate = new Date("2024-07-08T00:00:00Z")
|
||||||
|
|
||||||
const messages: ClaudeMessage[] = [
|
const messages: ClaudeMessage[] = [
|
||||||
{ type: "say", say: "task", text: "type: say, say: task", ts: generateRandomTimestamp(baseDate, 1) },
|
{ type: "say", say: "task", text: "Starting task", ts: generateRandomTimestamp(baseDate, 1) },
|
||||||
{
|
{
|
||||||
type: "ask",
|
type: "ask",
|
||||||
ask: "request_limit_reached",
|
ask: "request_limit_reached",
|
||||||
text: "type: ask, ask: request_limit_reached",
|
text: "Request limit reached",
|
||||||
ts: generateRandomTimestamp(baseDate, 1),
|
ts: generateRandomTimestamp(baseDate, 2),
|
||||||
},
|
},
|
||||||
{ type: "ask", ask: "followup", text: "type: ask, ask: followup", ts: generateRandomTimestamp(baseDate, 1) },
|
{ type: "ask", ask: "followup", text: "Any additional questions?", ts: generateRandomTimestamp(baseDate, 3) },
|
||||||
{ type: "ask", ask: "command", text: "type: ask, ask: command", ts: generateRandomTimestamp(baseDate, 1) },
|
{ type: "say", say: "error", text: "An error occurred", ts: generateRandomTimestamp(baseDate, 4) },
|
||||||
{ type: "say", say: "error", text: "type: say, say: error", ts: generateRandomTimestamp(baseDate, 1) },
|
|
||||||
|
{ type: "say", say: "text", text: "Some general text", ts: generateRandomTimestamp(baseDate, 7) },
|
||||||
|
{ type: "say", say: "tool", text: "Using a tool", ts: generateRandomTimestamp(baseDate, 8) },
|
||||||
|
|
||||||
|
// First command sequence
|
||||||
|
{ type: "ask", ask: "command", text: "ls -l", ts: generateRandomTimestamp(baseDate, 9) },
|
||||||
|
{ type: "say", say: "command_output", text: "file1.txt", ts: generateRandomTimestamp(baseDate, 10) },
|
||||||
|
{ type: "say", say: "api_req_started", text: JSON.stringify({ request: "GET /api/data" }), ts: generateRandomTimestamp(baseDate, 5) },
|
||||||
|
{ type: "say", say: "command_output", text: "file2.txt", ts: generateRandomTimestamp(baseDate, 11) },
|
||||||
|
{ type: "say", say: "command_output", text: "directory1", ts: generateRandomTimestamp(baseDate, 12) },
|
||||||
|
|
||||||
|
{ type: "say", say: "text", text: "Interrupting text", ts: generateRandomTimestamp(baseDate, 13) },
|
||||||
|
{ type: "say", say: "api_req_finished", text: JSON.stringify({ cost: "GET /api/data" }), ts: generateRandomTimestamp(baseDate, 6) },
|
||||||
|
// Second command sequence
|
||||||
|
{ type: "ask", ask: "command", text: "pwd", ts: generateRandomTimestamp(baseDate, 14) },
|
||||||
|
{ type: "say", say: "command_output", text: "/home/user", ts: generateRandomTimestamp(baseDate, 15) },
|
||||||
|
|
||||||
|
{ type: "ask", ask: "completion_result", text: "Task completed", ts: generateRandomTimestamp(baseDate, 16) },
|
||||||
|
|
||||||
|
// Third command sequence (no output)
|
||||||
|
{ type: "ask", ask: "command", text: "echo Hello", ts: generateRandomTimestamp(baseDate, 17) },
|
||||||
|
|
||||||
|
// Testing combineApiRequests
|
||||||
|
{ type: "say", say: "text", text: "Final message", ts: generateRandomTimestamp(baseDate, 18) },
|
||||||
|
{ type: "ask", ask: "command", text: "ls -l", ts: generateRandomTimestamp(baseDate, 19) },
|
||||||
|
{ type: "say", say: "command_output", text: "file1.txt", ts: generateRandomTimestamp(baseDate, 20) },
|
||||||
{
|
{
|
||||||
type: "say",
|
type: "say",
|
||||||
say: "api_req_started",
|
say: "api_req_started",
|
||||||
text: "type: say, say: api_req_started",
|
text: JSON.stringify({ request: "GET /api/data" }),
|
||||||
ts: generateRandomTimestamp(baseDate, 1),
|
ts: generateRandomTimestamp(baseDate, 23),
|
||||||
},
|
},
|
||||||
|
{ type: "say", say: "command_output", text: "file2.txt", ts: generateRandomTimestamp(baseDate, 24) },
|
||||||
|
{ type: "say", say: "text", text: "Some random text", ts: generateRandomTimestamp(baseDate, 25) },
|
||||||
{
|
{
|
||||||
type: "say",
|
type: "say",
|
||||||
say: "api_req_finished",
|
say: "api_req_finished",
|
||||||
text: "type: say, say: api_req_finished",
|
text: JSON.stringify({ cost: 0.005 }),
|
||||||
ts: generateRandomTimestamp(baseDate, 1),
|
ts: generateRandomTimestamp(baseDate, 26),
|
||||||
},
|
},
|
||||||
{ type: "say", say: "text", text: "type: say, say: text", ts: generateRandomTimestamp(baseDate, 1) },
|
{ type: "ask", ask: "command", text: "pwd", ts: generateRandomTimestamp(baseDate, 27) },
|
||||||
{ type: "say", say: "tool", text: "type: say, say: tool", ts: generateRandomTimestamp(baseDate, 1) },
|
{ type: "say", say: "command_output", text: "/home/user", ts: generateRandomTimestamp(baseDate, 28) },
|
||||||
{
|
{
|
||||||
type: "say",
|
type: "say",
|
||||||
say: "command_output",
|
say: "api_req_started",
|
||||||
text: "type: say, say: command_output",
|
text: JSON.stringify({ request: "POST /api/update" }),
|
||||||
ts: generateRandomTimestamp(baseDate, 1),
|
ts: generateRandomTimestamp(baseDate, 29),
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "ask",
|
|
||||||
ask: "completion_result",
|
|
||||||
text: "type: ask, ask: completion_result",
|
|
||||||
ts: generateRandomTimestamp(baseDate, 1),
|
|
||||||
},
|
},
|
||||||
|
{ type: "say", say: "text", text: "Final message", ts: generateRandomTimestamp(baseDate, 30) },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages)), [messages])
|
||||||
|
|
||||||
const [inputValue, setInputValue] = useState("")
|
const [inputValue, setInputValue] = useState("")
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||||
const textAreaRef = useRef<HTMLTextAreaElement>(null)
|
const textAreaRef = useRef<HTMLTextAreaElement>(null)
|
||||||
@@ -140,7 +167,7 @@ const ChatView = ({}: ChatViewProps) => {
|
|||||||
backgroundColor: "gray",
|
backgroundColor: "gray",
|
||||||
}}>
|
}}>
|
||||||
<div style={{ flexGrow: 1, overflowY: "scroll", scrollbarWidth: "none" }}>
|
<div style={{ flexGrow: 1, overflowY: "scroll", scrollbarWidth: "none" }}>
|
||||||
{messages.map((message) => (
|
{modifiedMessages.map((message) => (
|
||||||
<ChatRow message={message} />
|
<ChatRow message={message} />
|
||||||
))}
|
))}
|
||||||
<div style={{ float: "left", clear: "both" }} ref={messagesEndRef} />
|
<div style={{ float: "left", clear: "both" }} ref={messagesEndRef} />
|
||||||
|
|||||||
62
webview-ui/src/utilities/combineApiRequests.ts
Normal file
62
webview-ui/src/utilities/combineApiRequests.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { ClaudeMessage } from "@shared/ExtensionMessage"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines API request start and finish messages in an array of ClaudeMessages.
|
||||||
|
*
|
||||||
|
* This function looks for pairs of 'api_req_started' and 'api_req_finished' messages.
|
||||||
|
* When it finds a pair, it combines them into a single 'api_req_combined' message.
|
||||||
|
* The JSON data in the text fields of both messages are merged.
|
||||||
|
*
|
||||||
|
* @param messages - An array of ClaudeMessage objects to process.
|
||||||
|
* @returns A new array of ClaudeMessage objects with API requests combined.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const messages = [
|
||||||
|
* { type: "say", say: "api_req_started", text: '{"request":"GET /api/data"}', ts: 1000 },
|
||||||
|
* { type: "say", say: "api_req_finished", text: '{"cost":0.005}', ts: 1001 }
|
||||||
|
* ];
|
||||||
|
* const result = combineApiRequests(messages);
|
||||||
|
* // Result: [{ type: "say", say: "api_req_started", text: '{"request":"GET /api/data","cost":0.005}', ts: 1000 }]
|
||||||
|
*/
|
||||||
|
export function combineApiRequests(messages: ClaudeMessage[]): ClaudeMessage[] {
|
||||||
|
const combinedApiRequests: ClaudeMessage[] = []
|
||||||
|
|
||||||
|
for (let i = 0; i < messages.length; i++) {
|
||||||
|
if (messages[i].type === "say" && messages[i].say === "api_req_started") {
|
||||||
|
let startedRequest = JSON.parse(messages[i].text || "{}")
|
||||||
|
let j = i + 1
|
||||||
|
|
||||||
|
while (j < messages.length) {
|
||||||
|
if (messages[j].type === "say" && messages[j].say === "api_req_finished") {
|
||||||
|
let finishedRequest = JSON.parse(messages[j].text || "{}")
|
||||||
|
let combinedRequest = { ...startedRequest, ...finishedRequest }
|
||||||
|
|
||||||
|
combinedApiRequests.push({
|
||||||
|
...messages[i],
|
||||||
|
text: JSON.stringify(combinedRequest),
|
||||||
|
})
|
||||||
|
|
||||||
|
i = j // Skip to the api_req_finished message
|
||||||
|
break
|
||||||
|
}
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j === messages.length) {
|
||||||
|
// If no matching api_req_finished found, keep the original api_req_started
|
||||||
|
combinedApiRequests.push(messages[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace original api_req_started and remove api_req_finished
|
||||||
|
return messages
|
||||||
|
.filter((msg) => !(msg.type === "say" && msg.say === "api_req_finished"))
|
||||||
|
.map((msg) => {
|
||||||
|
if (msg.type === "say" && msg.say === "api_req_started") {
|
||||||
|
const combinedRequest = combinedApiRequests.find((req) => req.ts === msg.ts)
|
||||||
|
return combinedRequest || msg
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
})
|
||||||
|
}
|
||||||
62
webview-ui/src/utilities/combineCommandSequences.ts
Normal file
62
webview-ui/src/utilities/combineCommandSequences.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { ClaudeMessage } from "@shared/ExtensionMessage"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines sequences of command and command_output messages in an array of ClaudeMessages.
|
||||||
|
*
|
||||||
|
* This function processes an array of ClaudeMessage objects, looking for sequences
|
||||||
|
* where a 'command' message is followed by one or more 'command_output' messages.
|
||||||
|
* When such a sequence is found, it combines them into a single message, merging
|
||||||
|
* their text contents.
|
||||||
|
*
|
||||||
|
* @param messages - An array of ClaudeMessage objects to process.
|
||||||
|
* @returns A new array of ClaudeMessage objects with command sequences combined.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const messages: ClaudeMessage[] = [
|
||||||
|
* { type: 'ask', ask: 'command', text: 'ls', ts: 1625097600000 },
|
||||||
|
* { type: 'say', say: 'command_output', text: 'file1.txt', ts: 1625097601000 },
|
||||||
|
* { type: 'say', say: 'command_output', text: 'file2.txt', ts: 1625097602000 }
|
||||||
|
* ];
|
||||||
|
* const result = simpleCombineCommandSequences(messages);
|
||||||
|
* // Result: [{ type: 'ask', ask: 'command', text: 'ls\nfile1.txt\nfile2.txt', ts: 1625097600000 }]
|
||||||
|
*/
|
||||||
|
export function combineCommandSequences(messages: ClaudeMessage[]): ClaudeMessage[] {
|
||||||
|
const combinedCommands: ClaudeMessage[] = []
|
||||||
|
|
||||||
|
// First pass: combine commands with their outputs
|
||||||
|
for (let i = 0; i < messages.length; i++) {
|
||||||
|
if (messages[i].type === "ask" && messages[i].ask === "command") {
|
||||||
|
let combinedText = messages[i].text || ""
|
||||||
|
let j = i + 1
|
||||||
|
|
||||||
|
while (j < messages.length) {
|
||||||
|
if (messages[j].type === "ask" && messages[j].ask === "command") {
|
||||||
|
// Stop if we encounter the next command
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (messages[j].type === "say" && messages[j].say === "command_output") {
|
||||||
|
combinedText += "\n" + (messages[j].text || "")
|
||||||
|
}
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
|
||||||
|
combinedCommands.push({
|
||||||
|
...messages[i],
|
||||||
|
text: combinedText,
|
||||||
|
})
|
||||||
|
|
||||||
|
i = j - 1 // Move to the index just before the next command or end of array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: remove command_outputs and replace original commands with combined ones
|
||||||
|
return messages
|
||||||
|
.filter((msg) => !(msg.type === "say" && msg.say === "command_output"))
|
||||||
|
.map((msg) => {
|
||||||
|
if (msg.type === "ask" && msg.ask === "command") {
|
||||||
|
const combinedCommand = combinedCommands.find((cmd) => cmd.ts === msg.ts)
|
||||||
|
return combinedCommand || msg
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user