fabro/apps/fabro-web/app/components/tool-use.tsx
Bryan Helmkamp 9b5bc8ede4
style(run-stages): copy affordance, collapsed long output, sticky header
- extract a shared CopyButton into components/ui.tsx and drop the
  install wizard's local duplicate
- sticky stage header at the top of the turn stream so users always
  know which stage they're reading as they scroll
- copy-to-clipboard button on System, Assistant, and Command blocks;
  revealed on hover/focus
- stdout/stderr longer than 20 lines collapse to the last 20 with a
  "Show N earlier lines" expander
- bump the [10px] labels in tool-use.tsx to [11px] for readability

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 17:27:25 -04:00

54 lines
2.1 KiB
TypeScript

import { useState } from "react";
import { ChevronRightIcon } from "@heroicons/react/20/solid";
import { WrenchScrewdriverIcon } from "@heroicons/react/24/outline";
export interface ToolUse {
id: string;
toolName: string;
input: string;
result: string;
isError: boolean;
durationMs?: number;
}
export function ToolRow({ tool }: { tool: ToolUse }) {
const [open, setOpen] = useState(false);
return (
<div className="border-b border-line last:border-b-0">
<button
type="button"
onClick={() => setOpen((v) => !v)}
className="flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left transition-colors hover:bg-overlay"
>
<ChevronRightIcon className={`size-3 shrink-0 text-fg-muted transition-transform duration-150 ${open ? "rotate-90" : ""}`} />
<WrenchScrewdriverIcon className="size-3.5 shrink-0 text-fg-muted" />
<span className="font-mono text-xs text-fg-3">{tool.toolName}</span>
{tool.durationMs != null && <span className="text-[11px] text-fg-muted">{tool.durationMs}ms</span>}
<span className="truncate font-mono text-xs text-fg-muted">{tool.input}</span>
</button>
{open && (
<div className="space-y-px bg-overlay px-2.5 pb-2 pt-1">
<div className="rounded bg-overlay px-2.5 py-2">
<div className="mb-1 text-[11px] font-medium uppercase tracking-wider text-fg-muted">Input</div>
<pre className="whitespace-pre-wrap font-mono text-xs leading-relaxed text-fg-3">{tool.input}</pre>
</div>
<div className="rounded bg-overlay px-2.5 py-2">
<div className="mb-1 text-[11px] font-medium uppercase tracking-wider text-fg-muted">Result</div>
<pre className={`whitespace-pre-wrap font-mono text-xs leading-relaxed ${tool.isError ? "text-coral" : "text-fg-3"}`}>{tool.result}</pre>
</div>
</div>
)}
</div>
);
}
export function ToolBlock({ tools }: { tools: ToolUse[] }) {
return (
<div className="rounded-md border border-line bg-overlay overflow-hidden">
{tools.map((tool) => (
<ToolRow key={tool.id} tool={tool} />
))}
</div>
);
}