forked from bots-garden/ori
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 1 | /** |
| 2 | * ToolCallCard shows one tool call the agent is running, in the spirit of | |
| 3 | * Zed's agent panel: a collapsible card with the tool kind, a live status, | |
| 4 | * touched locations, and the call's output (text, or a file diff). | |
| 5 | * | |
| 6 | * @example | |
| 7 | * <ToolCallCard call={{ toolCallId: "c1", title: "Reading main.go", | |
| 8 | * toolKind: "read", status: "in_progress", contents: [], locations: [] }} /> | |
| 9 | */ | |
| 10 | ||
| 11 | import type { ToolCall } from "../reducer"; | |
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 12 | import { InlineText } from "./InlineText"; |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 13 | import type { AcpToolCallContent } from "../protocol"; |
| 14 | import { Markdown } from "./Markdown"; | |
| Add copy-paste functionality for code blocks and completion widgets | 15 | import { CopyButton } from "./CopyButton"; |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 16 | |
| 17 | const kindIcons: Record<string, string> = { | |
| 18 | read: "📖", | |
| 19 | edit: "✏️", | |
| 20 | delete: "🗑️", | |
| 21 | move: "📦", | |
| 22 | search: "🔍", | |
| 23 | execute: "⚡", | |
| 24 | think: "💭", | |
| 25 | fetch: "🌐", | |
| 26 | switch_mode: "🔀", | |
| 27 | other: "🔧", | |
| 28 | }; | |
| 29 | ||
| 30 | const statusLabels: Record<string, string> = { | |
| 31 | pending: "pending", | |
| 32 | in_progress: "running", | |
| 33 | completed: "done", | |
| 34 | failed: "failed", | |
| 35 | }; | |
| 36 | ||
| 37 | export function ToolCallCard({ call }: { call: ToolCall }) { | |
| Add copy-paste functionality for code blocks and completion widgets | 38 | // Extract all textual content for copy button |
| 39 | const getToolContent = (): string => { | |
| 40 | return call.contents | |
| 41 | .map((content) => { | |
| 42 | if (content.type === "content" && content.content?.text) { | |
| 43 | return content.content.text; | |
| 44 | } | |
| 45 | if (content.type === "diff") { | |
| 46 | const parts = []; | |
| 47 | if (content.path) parts.push(`Path: ${content.path}`); | |
| 48 | if (content.oldText) parts.push(`Old:\n${content.oldText}`); | |
| 49 | if (content.newText) parts.push(`New:\n${content.newText}`); | |
| 50 | return parts.join("\n\n"); | |
| 51 | } | |
| 52 | return ""; | |
| 53 | }) | |
| 54 | .filter(Boolean) | |
| 55 | .join("\n\n"); | |
| 56 | }; | |
| 57 | ||
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 58 | return ( |
| 59 | <details className={`tool tool-${call.status}`}> | |
| 60 | <summary className="tool-summary"> | |
| 61 | <span className="tool-icon" aria-hidden> | |
| 62 | {kindIcons[call.toolKind] ?? kindIcons.other} | |
| 63 | </span> | |
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 64 | <span className="tool-title"> |
| 65 | <InlineText text={call.title} /> | |
| 66 | </span> | |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 67 | <span className={`tool-status tool-status-${call.status}`}> |
| 68 | {statusLabels[call.status] ?? call.status} | |
| 69 | </span> | |
| Add copy-paste functionality for code blocks and completion widgets | 70 | {getToolContent() && ( |
| 71 | <CopyButton content={getToolContent()} label="Copy tool output" /> | |
| 72 | )} | |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 73 | </summary> |
| 74 | <div className="tool-body"> | |
| 75 | {call.locations.length > 0 && ( | |
| 76 | <ul className="tool-locations"> | |
| 77 | {call.locations.map((location, index) => ( | |
| 78 | // biome-ignore lint/suspicious/noArrayIndexKey: tool-call locations carry no id and the list is replaced wholesale on every update. | |
| 79 | <li key={index}> | |
| 80 | <code> | |
| 81 | {location.path} | |
| 82 | {location.line != null ? `:${location.line}` : ""} | |
| 83 | </code> | |
| 84 | </li> | |
| 85 | ))} | |
| 86 | </ul> | |
| 87 | )} | |
| 88 | {call.contents.map((content, index) => ( | |
| 89 | // biome-ignore lint/suspicious/noArrayIndexKey: tool-call contents carry no id and the list is replaced wholesale on every update. | |
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 90 | <ToolContent key={index} content={content} toolKind={call.toolKind} /> |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 91 | ))} |
| 92 | </div> | |
| 93 | </details> | |
| 94 | ); | |
| 95 | } | |
| 96 | ||
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 97 | function ToolContent({ |
| 98 | content, | |
| 99 | toolKind, | |
| 100 | }: { content: AcpToolCallContent; toolKind: string }) { | |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 101 | switch (content.type) { |
| 102 | case "content": | |
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 103 | return ( |
| 104 | <TextToolContent text={content.content?.text} toolKind={toolKind} /> | |
| 105 | ); | |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 106 | case "diff": |
| 107 | return ( | |
| 108 | <div className="tool-diff"> | |
| 109 | <code className="tool-diff-path">{content.path}</code> | |
| 110 | {content.oldText != null && ( | |
| 111 | <pre className="tool-diff-old"> | |
| 112 | {prefixLines(content.oldText, "- ")} | |
| 113 | </pre> | |
| 114 | )} | |
| 115 | <pre className="tool-diff-new"> | |
| 116 | {prefixLines(content.newText ?? "", "+ ")} | |
| 117 | </pre> | |
| 118 | </div> | |
| 119 | ); | |
| 120 | case "terminal": | |
| 121 | return <p className="tool-terminal">terminal: {content.terminalId}</p>; | |
| 122 | default: | |
| 123 | return null; | |
| 124 | } | |
| 125 | } | |
| 126 | ||
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 127 | /** |
| 128 | * TextToolContent renders a tool call's textual output. System-command | |
| 129 | * output (kind "execute") is verbatim text: it goes into a monospace block, | |
| 130 | * never through the markdown pipeline. | |
| 131 | */ | |
| 132 | function TextToolContent({ | |
| 133 | text, | |
| 134 | toolKind, | |
| 135 | }: { text: string | undefined; toolKind: string }) { | |
| 136 | if (!text) { | |
| 137 | return null; | |
| 138 | } | |
| 139 | if (toolKind === "execute") { | |
| 140 | return <pre className="tool-output">{text}</pre>; | |
| 141 | } | |
| 142 | return <Markdown text={text} />; | |
| 143 | } | |
| 144 | ||
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 145 | function prefixLines(text: string, prefix: string): string { |
| 146 | return text | |
| 147 | .split("\n") | |
| 148 | .map((line) => prefix + line) | |
| 149 | .join("\n"); | |
| 150 | } |