mirror of
https://github.com/cline/cline.git
synced 2026-09-11 16:42:40 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7df3fb9fd |
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState, useRef, useEffect, useCallback } from "react"
|
||||
import React, { useMemo, useRef, useEffect, useCallback } from "react"
|
||||
import { Virtuoso } from "react-virtuoso"
|
||||
import { ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { combineApiRequests } from "@shared/combineApiRequests"
|
||||
@@ -10,21 +10,28 @@ import { COLOR_WHITE, COLOR_GRAY, COLOR_DARK_GRAY, COLOR_BEIGE, COLOR_BLUE, COLO
|
||||
const TIMELINE_HEIGHT = "18px"
|
||||
const BLOCK_WIDTH = "9px"
|
||||
const BLOCK_GAP = "3px"
|
||||
const TOOLTIP_MARGIN = 32 // 32px margin on each side
|
||||
|
||||
export interface EnrichedClineMessage extends ClineMessage {
|
||||
_blockColor: string
|
||||
_tooltipDesc: string
|
||||
_tooltipContentPreview: string
|
||||
_tooltipTimestamp: string
|
||||
}
|
||||
|
||||
interface TaskTimelineProps {
|
||||
messages: ClineMessage[]
|
||||
}
|
||||
|
||||
const getBlockColor = (message: ClineMessage): string => {
|
||||
// Helper functions for enriching messages (logic adapted from TaskTimelineTooltip.tsx)
|
||||
const getEnrichedMessageDescription = (message: ClineMessage): string => {
|
||||
if (message.type === "say") {
|
||||
switch (message.say) {
|
||||
case "task":
|
||||
return COLOR_WHITE // White for system prompt
|
||||
return "Task Message"
|
||||
case "user_feedback":
|
||||
return COLOR_WHITE // White for user feedback
|
||||
return "User Message"
|
||||
case "text":
|
||||
return COLOR_GRAY // Gray for assistant responses
|
||||
return "Assistant Response"
|
||||
case "tool":
|
||||
if (message.text) {
|
||||
try {
|
||||
@@ -35,35 +42,38 @@ const getBlockColor = (message: ClineMessage): string => {
|
||||
toolData.tool === "listFilesRecursive" ||
|
||||
toolData.tool === "listCodeDefinitionNames" ||
|
||||
toolData.tool === "searchFiles"
|
||||
) {
|
||||
return COLOR_BEIGE // Beige for file read operations
|
||||
} else if (toolData.tool === "editedExistingFile" || toolData.tool === "newFileCreated") {
|
||||
return COLOR_BLUE // Blue for file edit/create operations
|
||||
}
|
||||
)
|
||||
return `File Read: ${toolData.tool}`
|
||||
if (toolData.tool === "editedExistingFile") return `File Edit: ${toolData.path || "Unknown file"}`
|
||||
if (toolData.tool === "newFileCreated") return `New File: ${toolData.path || "Unknown file"}`
|
||||
return `Tool: ${toolData.tool}`
|
||||
} catch (e) {
|
||||
// JSON parse error here
|
||||
return "Tool Use"
|
||||
}
|
||||
}
|
||||
return COLOR_BEIGE // Default beige for tool use
|
||||
return "Tool Use"
|
||||
case "command":
|
||||
return "Terminal Command"
|
||||
case "command_output":
|
||||
return COLOR_PURPLE // Red for terminal commands
|
||||
return "Terminal Output"
|
||||
case "browser_action":
|
||||
return "Browser Action"
|
||||
case "browser_action_result":
|
||||
return COLOR_PURPLE // Purple for browser actions
|
||||
return "Browser Result"
|
||||
case "completion_result":
|
||||
return COLOR_GREEN // Green for task success
|
||||
return "Task Completed"
|
||||
case "checkpoint_created":
|
||||
return "Checkpoint Created"
|
||||
default:
|
||||
return COLOR_DARK_GRAY // Dark gray for unknown
|
||||
return message.say || "Unknown"
|
||||
}
|
||||
} else if (message.type === "ask") {
|
||||
switch (message.ask) {
|
||||
case "followup":
|
||||
return COLOR_GRAY // Gray for user messages
|
||||
return "User Message"
|
||||
case "plan_mode_respond":
|
||||
return COLOR_GRAY // Gray for planning responses
|
||||
return "Planning Response"
|
||||
case "tool":
|
||||
// Match the color of the tool approval with the tool type
|
||||
if (message.text) {
|
||||
try {
|
||||
const toolData = JSON.parse(message.text)
|
||||
@@ -73,85 +83,193 @@ const getBlockColor = (message: ClineMessage): string => {
|
||||
toolData.tool === "listFilesRecursive" ||
|
||||
toolData.tool === "listCodeDefinitionNames" ||
|
||||
toolData.tool === "searchFiles"
|
||||
) {
|
||||
return COLOR_BEIGE // Beige for file read operations
|
||||
} else if (toolData.tool === "editedExistingFile" || toolData.tool === "newFileCreated") {
|
||||
return COLOR_BLUE // Blue for file edit/create operations
|
||||
}
|
||||
)
|
||||
return `File Read Approval: ${toolData.tool}`
|
||||
if (toolData.tool === "editedExistingFile")
|
||||
return `File Edit Approval: ${toolData.path || "Unknown file"}`
|
||||
if (toolData.tool === "newFileCreated") return `New File Approval: ${toolData.path || "Unknown file"}`
|
||||
return `Tool Approval: ${toolData.tool}`
|
||||
} catch (e) {
|
||||
// JSON parse error here
|
||||
return "Tool Approval"
|
||||
}
|
||||
}
|
||||
return COLOR_BEIGE // Default beige for tool approvals
|
||||
return "Tool Approval"
|
||||
case "command":
|
||||
return COLOR_PURPLE // Red for command approvals (same as terminal commands)
|
||||
return "Terminal Command Approval"
|
||||
case "browser_action_launch":
|
||||
return COLOR_PURPLE // Purple for browser launch approvals (same as browser actions)
|
||||
return "Browser Action Approval"
|
||||
default:
|
||||
return COLOR_DARK_GRAY // Dark gray for unknown
|
||||
return message.ask || "Unknown"
|
||||
}
|
||||
}
|
||||
return COLOR_WHITE // Default color
|
||||
return "Unknown Message Type"
|
||||
}
|
||||
|
||||
const getEnrichedMessageContent = (message: ClineMessage): string => {
|
||||
if (message.text) {
|
||||
if (message.type === "ask" && message.ask === "plan_mode_respond" && message.text) {
|
||||
try {
|
||||
const planData = JSON.parse(message.text)
|
||||
return planData.response || message.text
|
||||
} catch (e) {
|
||||
return message.text
|
||||
}
|
||||
} else if (message.type === "say" && message.say === "tool" && message.text) {
|
||||
try {
|
||||
const toolData = JSON.parse(message.text)
|
||||
return JSON.stringify(toolData, null, 2)
|
||||
} catch (e) {
|
||||
return message.text
|
||||
}
|
||||
}
|
||||
if (message.text.length > 200) return message.text.substring(0, 200) + "..."
|
||||
return message.text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
const getEnrichedMessageTimestamp = (message: ClineMessage): string => {
|
||||
if (message.ts) {
|
||||
const messageDate = new Date(message.ts)
|
||||
const today = new Date()
|
||||
const todayDate = new Date(today.getFullYear(), today.getMonth(), today.getDate())
|
||||
const messageDateOnly = new Date(messageDate.getFullYear(), messageDate.getMonth(), messageDate.getDate())
|
||||
const time = messageDate.toLocaleTimeString([], { hour: "numeric", minute: "2-digit", hour12: true })
|
||||
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
||||
const monthName = monthNames[messageDate.getMonth()]
|
||||
if (messageDateOnly.getTime() === todayDate.getTime()) return `${time}`
|
||||
if (messageDate.getFullYear() === today.getFullYear()) return `${monthName} ${messageDate.getDate()} ${time}`
|
||||
return `${monthName} ${messageDate.getDate()}, ${messageDate.getFullYear()} ${time}`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
const getEnrichedBlockColor = (message: ClineMessage): string => {
|
||||
if (message.type === "say") {
|
||||
switch (message.say) {
|
||||
case "task":
|
||||
return COLOR_WHITE
|
||||
case "user_feedback":
|
||||
return COLOR_WHITE
|
||||
case "text":
|
||||
return COLOR_GRAY
|
||||
case "tool":
|
||||
if (message.text) {
|
||||
try {
|
||||
const toolData = JSON.parse(message.text)
|
||||
if (
|
||||
toolData.tool === "readFile" ||
|
||||
toolData.tool === "listFilesTopLevel" ||
|
||||
toolData.tool === "listFilesRecursive" ||
|
||||
toolData.tool === "listCodeDefinitionNames" ||
|
||||
toolData.tool === "searchFiles"
|
||||
)
|
||||
return COLOR_BEIGE
|
||||
if (toolData.tool === "editedExistingFile" || toolData.tool === "newFileCreated") return COLOR_BLUE
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
return COLOR_BEIGE
|
||||
case "command":
|
||||
case "command_output":
|
||||
return COLOR_PURPLE
|
||||
case "browser_action":
|
||||
case "browser_action_result":
|
||||
return COLOR_PURPLE
|
||||
case "completion_result":
|
||||
return COLOR_GREEN
|
||||
default:
|
||||
return COLOR_DARK_GRAY
|
||||
}
|
||||
} else if (message.type === "ask") {
|
||||
switch (message.ask) {
|
||||
case "followup":
|
||||
return COLOR_GRAY
|
||||
case "plan_mode_respond":
|
||||
return COLOR_GRAY
|
||||
case "tool":
|
||||
if (message.text) {
|
||||
try {
|
||||
const toolData = JSON.parse(message.text)
|
||||
if (
|
||||
toolData.tool === "readFile" ||
|
||||
toolData.tool === "listFilesTopLevel" ||
|
||||
toolData.tool === "listFilesRecursive" ||
|
||||
toolData.tool === "listCodeDefinitionNames" ||
|
||||
toolData.tool === "searchFiles"
|
||||
)
|
||||
return COLOR_BEIGE
|
||||
if (toolData.tool === "editedExistingFile" || toolData.tool === "newFileCreated") return COLOR_BLUE
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
return COLOR_BEIGE
|
||||
case "command":
|
||||
return COLOR_PURPLE
|
||||
case "browser_action_launch":
|
||||
return COLOR_PURPLE
|
||||
default:
|
||||
return COLOR_DARK_GRAY
|
||||
}
|
||||
}
|
||||
return COLOR_WHITE
|
||||
}
|
||||
|
||||
const TaskTimeline: React.FC<TaskTimelineProps> = ({ messages }) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const scrollableRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const taskTimelinePropsMessages = useMemo(() => {
|
||||
if (messages.length <= 1) return []
|
||||
|
||||
const processed = combineApiRequests(combineCommandSequences(messages.slice(1)))
|
||||
|
||||
return processed.filter((msg) => {
|
||||
// Filter out standard "say" events we don't want to show
|
||||
if (
|
||||
msg.type === "say" &&
|
||||
(msg.say === "api_req_started" ||
|
||||
msg.say === "api_req_finished" ||
|
||||
msg.say === "api_req_retried" ||
|
||||
msg.say === "deleted_api_reqs" ||
|
||||
msg.say === "checkpoint_created" ||
|
||||
(msg.say === "text" && (!msg.text || msg.text.trim() === "")))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Filter out "ask" events we don't want to show, including the duplicate completion_result
|
||||
if (
|
||||
msg.type === "ask" &&
|
||||
(msg.ask === "resume_task" || msg.ask === "resume_completed_task" || msg.ask === "completion_result") // Filter out the duplicate completion_result "ask" message
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}, [messages])
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollableRef.current && taskTimelinePropsMessages.length > 0) {
|
||||
scrollableRef.current.scrollLeft = scrollableRef.current.scrollWidth
|
||||
}
|
||||
}, [taskTimelinePropsMessages])
|
||||
|
||||
// Calculate the item size (width of block + gap)
|
||||
const itemWidth = parseInt(BLOCK_WIDTH.replace("px", "")) + parseInt(BLOCK_GAP.replace("px", ""))
|
||||
|
||||
// Virtuoso requires a reference to scroll to the end
|
||||
const virtuosoRef = useRef<any>(null)
|
||||
|
||||
// Render a timeline block
|
||||
const enrichedTimelineMessages: EnrichedClineMessage[] = useMemo(() => {
|
||||
if (messages.length <= 1) return []
|
||||
|
||||
const processedAndFiltered = combineApiRequests(combineCommandSequences(messages.slice(1)))
|
||||
.filter((msg) => {
|
||||
if (
|
||||
msg.type === "say" &&
|
||||
(msg.say === "api_req_started" ||
|
||||
msg.say === "api_req_finished" ||
|
||||
msg.say === "api_req_retried" ||
|
||||
msg.say === "deleted_api_reqs" ||
|
||||
msg.say === "checkpoint_created" ||
|
||||
(msg.say === "text" && (!msg.text || msg.text.trim() === "")))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
msg.type === "ask" &&
|
||||
(msg.ask === "resume_task" || msg.ask === "resume_completed_task" || msg.ask === "completion_result")
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
.map((msg) => ({
|
||||
...msg,
|
||||
_blockColor: getEnrichedBlockColor(msg),
|
||||
_tooltipDesc: getEnrichedMessageDescription(msg),
|
||||
_tooltipContentPreview: getEnrichedMessageContent(msg),
|
||||
_tooltipTimestamp: getEnrichedMessageTimestamp(msg),
|
||||
}))
|
||||
return processedAndFiltered
|
||||
}, [messages])
|
||||
|
||||
const TimelineBlock = useCallback(
|
||||
(index: number) => {
|
||||
const message = taskTimelinePropsMessages[index]
|
||||
const message = enrichedTimelineMessages[index]
|
||||
return (
|
||||
<TaskTimelineTooltip message={message}>
|
||||
<TaskTimelineTooltip
|
||||
message={message} // Pass original message for now, will update tooltip to use enriched props
|
||||
blockColor={message._blockColor}
|
||||
tooltipDesc={message._tooltipDesc}
|
||||
tooltipContentPreview={message._tooltipContentPreview}
|
||||
tooltipTimestamp={message._tooltipTimestamp}>
|
||||
<div
|
||||
style={{
|
||||
width: BLOCK_WIDTH,
|
||||
height: "100%",
|
||||
backgroundColor: getBlockColor(message),
|
||||
backgroundColor: message._blockColor, // Use pre-computed color
|
||||
flexShrink: 0,
|
||||
cursor: "pointer",
|
||||
marginRight: BLOCK_GAP,
|
||||
@@ -160,20 +278,19 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ messages }) => {
|
||||
</TaskTimelineTooltip>
|
||||
)
|
||||
},
|
||||
[taskTimelinePropsMessages],
|
||||
[enrichedTimelineMessages],
|
||||
)
|
||||
|
||||
// Scroll to the end when messages change
|
||||
useEffect(() => {
|
||||
if (virtuosoRef.current && taskTimelinePropsMessages.length > 0) {
|
||||
if (virtuosoRef.current && enrichedTimelineMessages.length > 0) {
|
||||
virtuosoRef.current.scrollToIndex({
|
||||
index: taskTimelinePropsMessages.length - 1,
|
||||
index: enrichedTimelineMessages.length - 1,
|
||||
align: "end",
|
||||
})
|
||||
}
|
||||
}, [taskTimelinePropsMessages])
|
||||
}, [enrichedTimelineMessages])
|
||||
|
||||
if (taskTimelinePropsMessages.length === 0) {
|
||||
if (enrichedTimelineMessages.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -207,11 +324,11 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ messages }) => {
|
||||
height: TIMELINE_HEIGHT,
|
||||
width: "100%",
|
||||
}}
|
||||
totalCount={taskTimelinePropsMessages.length}
|
||||
totalCount={enrichedTimelineMessages.length}
|
||||
itemContent={TimelineBlock}
|
||||
horizontalDirection={true}
|
||||
increaseViewportBy={12}
|
||||
fixedItemHeight={itemWidth}
|
||||
// No fixedItemHeight/Width needed as Virtuoso will use the rendered item's dimensions
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,232 +1,26 @@
|
||||
import React from "react"
|
||||
import { ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { COLOR_WHITE, COLOR_GRAY, COLOR_DARK_GRAY, COLOR_BEIGE, COLOR_BLUE, COLOR_RED, COLOR_PURPLE, COLOR_GREEN } from "./colors"
|
||||
import { ClineMessage } from "@shared/ExtensionMessage" // Keep if original message object is needed for other props or future use
|
||||
import { Tooltip } from "@heroui/react"
|
||||
|
||||
// Color mapping for different message types
|
||||
|
||||
interface TaskTimelineTooltipProps {
|
||||
message: ClineMessage
|
||||
message: ClineMessage // Keep original message for any other potential data needed by tooltip or its children
|
||||
children: React.ReactNode
|
||||
blockColor: string
|
||||
tooltipDesc: string
|
||||
tooltipContentPreview: string
|
||||
tooltipTimestamp: string
|
||||
}
|
||||
|
||||
const TaskTimelineTooltip = ({ message, children }: TaskTimelineTooltipProps) => {
|
||||
const getMessageDescription = (message: ClineMessage): string => {
|
||||
if (message.type === "say") {
|
||||
switch (message.say) {
|
||||
// TODO: Need to confirm these classifcations with design
|
||||
case "task":
|
||||
return "Task Message"
|
||||
case "user_feedback":
|
||||
return "User Message"
|
||||
case "text":
|
||||
return "Assistant Response"
|
||||
case "tool":
|
||||
if (message.text) {
|
||||
try {
|
||||
const toolData = JSON.parse(message.text)
|
||||
if (
|
||||
toolData.tool === "readFile" ||
|
||||
toolData.tool === "listFilesTopLevel" ||
|
||||
toolData.tool === "listFilesRecursive" ||
|
||||
toolData.tool === "listCodeDefinitionNames" ||
|
||||
toolData.tool === "searchFiles"
|
||||
) {
|
||||
return `File Read: ${toolData.tool}`
|
||||
} else if (toolData.tool === "editedExistingFile") {
|
||||
return `File Edit: ${toolData.path || "Unknown file"}`
|
||||
} else if (toolData.tool === "newFileCreated") {
|
||||
return `New File: ${toolData.path || "Unknown file"}`
|
||||
}
|
||||
return `Tool: ${toolData.tool}`
|
||||
} catch (e) {
|
||||
return "Tool Use"
|
||||
}
|
||||
}
|
||||
return "Tool Use"
|
||||
case "command":
|
||||
return "Terminal Command"
|
||||
case "command_output":
|
||||
return "Terminal Output"
|
||||
case "browser_action":
|
||||
return "Browser Action"
|
||||
case "browser_action_result":
|
||||
return "Browser Result"
|
||||
case "completion_result":
|
||||
return "Task Completed"
|
||||
case "checkpoint_created":
|
||||
return "Checkpoint Created"
|
||||
default:
|
||||
return message.say || "Unknown"
|
||||
}
|
||||
} else if (message.type === "ask") {
|
||||
switch (message.ask) {
|
||||
case "followup":
|
||||
return "User Message"
|
||||
case "plan_mode_respond":
|
||||
return "Planning Response"
|
||||
case "tool":
|
||||
if (message.text) {
|
||||
try {
|
||||
const toolData = JSON.parse(message.text)
|
||||
if (
|
||||
toolData.tool === "readFile" ||
|
||||
toolData.tool === "listFilesTopLevel" ||
|
||||
toolData.tool === "listFilesRecursive" ||
|
||||
toolData.tool === "listCodeDefinitionNames" ||
|
||||
toolData.tool === "searchFiles"
|
||||
) {
|
||||
return `File Read Approval: ${toolData.tool}`
|
||||
} else if (toolData.tool === "editedExistingFile") {
|
||||
return `File Edit Approval: ${toolData.path || "Unknown file"}`
|
||||
} else if (toolData.tool === "newFileCreated") {
|
||||
return `New File Approval: ${toolData.path || "Unknown file"}`
|
||||
}
|
||||
return `Tool Approval: ${toolData.tool}`
|
||||
} catch (e) {
|
||||
return "Tool Approval"
|
||||
}
|
||||
}
|
||||
return "Tool Approval"
|
||||
case "command":
|
||||
return "Terminal Command Approval"
|
||||
case "browser_action_launch":
|
||||
return "Browser Action Approval"
|
||||
default:
|
||||
return message.ask || "Unknown"
|
||||
}
|
||||
}
|
||||
return "Unknown Message Type"
|
||||
}
|
||||
|
||||
const getMessageContent = (message: ClineMessage): string => {
|
||||
if (message.text) {
|
||||
if (message.type === "ask" && message.ask === "plan_mode_respond" && message.text) {
|
||||
try {
|
||||
const planData = JSON.parse(message.text)
|
||||
return planData.response || message.text
|
||||
} catch (e) {
|
||||
return message.text
|
||||
}
|
||||
} else if (message.type === "say" && message.say === "tool" && message.text) {
|
||||
try {
|
||||
const toolData = JSON.parse(message.text)
|
||||
return JSON.stringify(toolData, null, 2)
|
||||
} catch (e) {
|
||||
return message.text
|
||||
}
|
||||
}
|
||||
|
||||
if (message.text.length > 200) {
|
||||
return message.text.substring(0, 200) + "..."
|
||||
}
|
||||
return message.text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
const getTimestamp = (message: ClineMessage): string => {
|
||||
if (message.ts) {
|
||||
const messageDate = new Date(message.ts)
|
||||
const today = new Date()
|
||||
|
||||
const todayDate = new Date(today.getFullYear(), today.getMonth(), today.getDate())
|
||||
const messageDateOnly = new Date(messageDate.getFullYear(), messageDate.getMonth(), messageDate.getDate())
|
||||
|
||||
const time = messageDate.toLocaleTimeString([], { hour: "numeric", minute: "2-digit", hour12: true })
|
||||
|
||||
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
||||
const monthName = monthNames[messageDate.getMonth()]
|
||||
|
||||
if (messageDateOnly.getTime() === todayDate.getTime()) {
|
||||
return `${time}`
|
||||
} else if (messageDate.getFullYear() === today.getFullYear()) {
|
||||
return `${monthName} ${messageDate.getDate()} ${time}`
|
||||
} else {
|
||||
return `${monthName} ${messageDate.getDate()}, ${messageDate.getFullYear()} ${time}`
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Get color for the indicator based on message type
|
||||
const getMessageColor = (message: ClineMessage): string => {
|
||||
if (message.type === "say") {
|
||||
switch (message.say) {
|
||||
case "task":
|
||||
return COLOR_WHITE // White for system prompt
|
||||
case "user_feedback":
|
||||
return COLOR_WHITE // White for user feedback
|
||||
case "text":
|
||||
return COLOR_GRAY // Gray for assistant responses
|
||||
case "tool":
|
||||
if (message.text) {
|
||||
try {
|
||||
const toolData = JSON.parse(message.text)
|
||||
if (
|
||||
toolData.tool === "readFile" ||
|
||||
toolData.tool === "listFilesTopLevel" ||
|
||||
toolData.tool === "listFilesRecursive" ||
|
||||
toolData.tool === "listCodeDefinitionNames" ||
|
||||
toolData.tool === "searchFiles"
|
||||
) {
|
||||
return COLOR_BEIGE // Beige for file read operations
|
||||
} else if (toolData.tool === "editedExistingFile" || toolData.tool === "newFileCreated") {
|
||||
return COLOR_BLUE // Blue for file edit/create operations
|
||||
}
|
||||
} catch (e) {
|
||||
// JSON parse error here
|
||||
}
|
||||
}
|
||||
return COLOR_BEIGE // Default beige for tool use
|
||||
case "command":
|
||||
case "command_output":
|
||||
return COLOR_PURPLE // Red for terminal commands
|
||||
case "browser_action":
|
||||
case "browser_action_result":
|
||||
return COLOR_PURPLE // Purple for browser actions
|
||||
case "completion_result":
|
||||
return COLOR_GREEN // Green for task success
|
||||
default:
|
||||
return COLOR_DARK_GRAY // Dark gray for unknown
|
||||
}
|
||||
} else if (message.type === "ask") {
|
||||
switch (message.ask) {
|
||||
case "followup":
|
||||
return COLOR_GRAY // Gray for user messages
|
||||
case "plan_mode_respond":
|
||||
return COLOR_GRAY // Gray for planning responses
|
||||
case "tool":
|
||||
// Match the color of the tool approval with the tool type
|
||||
if (message.text) {
|
||||
try {
|
||||
const toolData = JSON.parse(message.text)
|
||||
if (
|
||||
toolData.tool === "readFile" ||
|
||||
toolData.tool === "listFilesTopLevel" ||
|
||||
toolData.tool === "listFilesRecursive" ||
|
||||
toolData.tool === "listCodeDefinitionNames" ||
|
||||
toolData.tool === "searchFiles"
|
||||
) {
|
||||
return COLOR_BEIGE // Beige for file read operations
|
||||
} else if (toolData.tool === "editedExistingFile" || toolData.tool === "newFileCreated") {
|
||||
return COLOR_BLUE // Blue for file edit/create operations
|
||||
}
|
||||
} catch (e) {
|
||||
// JSON parse error here
|
||||
}
|
||||
}
|
||||
return COLOR_BEIGE // Default beige for tool approvals
|
||||
case "command":
|
||||
return COLOR_PURPLE // Red for command approvals (same as terminal commands)
|
||||
case "browser_action_launch":
|
||||
return COLOR_PURPLE // Purple for browser launch approvals (same as browser actions)
|
||||
default:
|
||||
return COLOR_DARK_GRAY // Dark gray for unknown
|
||||
}
|
||||
}
|
||||
return COLOR_DARK_GRAY // Default dark gray
|
||||
}
|
||||
const TaskTimelineTooltip = ({
|
||||
message, // Keep message prop if it's used for other things not pre-computed
|
||||
children,
|
||||
blockColor,
|
||||
tooltipDesc,
|
||||
tooltipContentPreview,
|
||||
tooltipTimestamp,
|
||||
}: TaskTimelineTooltipProps) => {
|
||||
// Helper functions (getMessageDescription, getMessageContent, getTimestamp, getMessageColor)
|
||||
// are now removed as their logic is pre-computed in TaskTimeline.tsx and passed as props.
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
@@ -241,21 +35,21 @@ const TaskTimelineTooltip = ({ message, children }: TaskTimelineTooltipProps) =>
|
||||
minWidth: "10px", // Ensure fixed width
|
||||
minHeight: "10px", // Ensure fixed height
|
||||
borderRadius: "50%",
|
||||
backgroundColor: getMessageColor(message),
|
||||
backgroundColor: blockColor, // Use prop
|
||||
marginRight: "8px",
|
||||
display: "inline-block",
|
||||
flexShrink: 0, // Prevent shrinking when space is limited
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
{getMessageDescription(message)}
|
||||
{tooltipDesc} {/* Use prop */}
|
||||
</div>
|
||||
{getTimestamp(message) && (
|
||||
{tooltipTimestamp /* Use prop */ && (
|
||||
<span className="font-normal text-tiny" style={{ fontWeight: "normal", fontSize: "10px" }}>
|
||||
{getTimestamp(message)}
|
||||
{tooltipTimestamp} {/* Use prop */}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{getMessageContent(message) && (
|
||||
{tooltipContentPreview /* Use prop */ && (
|
||||
<div
|
||||
style={{
|
||||
whiteSpace: "pre-wrap",
|
||||
@@ -269,7 +63,7 @@ const TaskTimelineTooltip = ({ message, children }: TaskTimelineTooltipProps) =>
|
||||
borderRadius: "2px",
|
||||
scrollbarWidth: "none",
|
||||
}}>
|
||||
{getMessageContent(message)}
|
||||
{tooltipContentPreview} {/* Use prop */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user