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"; | |
| 15 | ||
| 16 | const kindIcons: Record<string, string> = { | |
| 17 | read: "📖", | |
| 18 | edit: "✏️", | |
| 19 | delete: "🗑️", | |
| 20 | move: "📦", | |
| 21 | search: "🔍", | |
| 22 | execute: "⚡", | |
| 23 | think: "💭", | |
| 24 | fetch: "🌐", | |
| 25 | switch_mode: "🔀", | |
| 26 | other: "🔧", | |
| 27 | }; | |
| 28 | ||
| 29 | const statusLabels: Record<string, string> = { | |
| 30 | pending: "pending", | |
| 31 | in_progress: "running", | |
| 32 | completed: "done", | |
| 33 | failed: "failed", | |
| 34 | }; | |
| 35 | ||
| 36 | export function ToolCallCard({ call }: { call: ToolCall }) { | |
| 37 | return ( | |
| 38 | <details className={`tool tool-${call.status}`}> | |
| 39 | <summary className="tool-summary"> | |
| 40 | <span className="tool-icon" aria-hidden> | |
| 41 | {kindIcons[call.toolKind] ?? kindIcons.other} | |
| 42 | </span> | |
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 43 | <span className="tool-title"> |
| 44 | <InlineText text={call.title} /> | |
| 45 | </span> | |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 46 | <span className={`tool-status tool-status-${call.status}`}> |
| 47 | {statusLabels[call.status] ?? call.status} | |
| 48 | </span> | |
| 49 | </summary> | |
| 50 | <div className="tool-body"> | |
| 51 | {call.locations.length > 0 && ( | |
| 52 | <ul className="tool-locations"> | |
| 53 | {call.locations.map((location, index) => ( | |
| 54 | // biome-ignore lint/suspicious/noArrayIndexKey: tool-call locations carry no id and the list is replaced wholesale on every update. | |
| 55 | <li key={index}> | |
| 56 | <code> | |
| 57 | {location.path} | |
| 58 | {location.line != null ? `:${location.line}` : ""} | |
| 59 | </code> | |
| 60 | </li> | |
| 61 | ))} | |
| 62 | </ul> | |
| 63 | )} | |
| 64 | {call.contents.map((content, index) => ( | |
| 65 | // 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 | 66 | <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 | 67 | ))} |
| 68 | </div> | |
| 69 | </details> | |
| 70 | ); | |
| 71 | } | |
| 72 | ||
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 73 | function ToolContent({ |
| 74 | content, | |
| 75 | toolKind, | |
| 76 | }: { 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 | 77 | switch (content.type) { |
| 78 | case "content": | |
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 79 | return ( |
| 80 | <TextToolContent text={content.content?.text} toolKind={toolKind} /> | |
| 81 | ); | |
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 82 | case "diff": |
| 83 | return ( | |
| 84 | <div className="tool-diff"> | |
| 85 | <code className="tool-diff-path">{content.path}</code> | |
| 86 | {content.oldText != null && ( | |
| 87 | <pre className="tool-diff-old"> | |
| 88 | {prefixLines(content.oldText, "- ")} | |
| 89 | </pre> | |
| 90 | )} | |
| 91 | <pre className="tool-diff-new"> | |
| 92 | {prefixLines(content.newText ?? "", "+ ")} | |
| 93 | </pre> | |
| 94 | </div> | |
| 95 | ); | |
| 96 | case "terminal": | |
| 97 | return <p className="tool-terminal">terminal: {content.terminalId}</p>; | |
| 98 | default: | |
| 99 | return null; | |
| 100 | } | |
| 101 | } | |
| 102 | ||
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 103 | /** |
| 104 | * TextToolContent renders a tool call's textual output. System-command | |
| 105 | * output (kind "execute") is verbatim text: it goes into a monospace block, | |
| 106 | * never through the markdown pipeline. | |
| 107 | */ | |
| 108 | function TextToolContent({ | |
| 109 | text, | |
| 110 | toolKind, | |
| 111 | }: { text: string | undefined; toolKind: string }) { | |
| 112 | if (!text) { | |
| 113 | return null; | |
| 114 | } | |
| 115 | if (toolKind === "execute") { | |
| 116 | return <pre className="tool-output">{text}</pre>; | |
| 117 | } | |
| 118 | return <Markdown text={text} />; | |
| 119 | } | |
| 120 | ||
| ✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS | 121 | function prefixLines(text: string, prefix: string): string { |
| 122 | return text | |
| 123 | .split("\n") | |
| 124 | .map((line) => prefix + line) | |
| 125 | .join("\n"); | |
| 126 | } |