Tesseract / studio /src /components /ToolCallBlock.jsx
rkbosamia's picture
Tesseract major version upgrade - v2
9c49532
Raw
History Blame Contribute Delete
4.22 kB
import { useState } from 'react'
const TOOL_META = {
write_file: { icon: '✎', label: 'Write file', summarize: (a) => a.path },
read_file: { icon: '↗', label: 'Read file', summarize: (a) => a.path },
delete_file: { icon: '✕', label: 'Delete file', summarize: (a) => a.path },
list_dir: { icon: 'â–¤', label: 'List dir', summarize: (a) => a.path || '/' },
read_logs: { icon: '⌗', label: 'Read logs', summarize: (a) => `${a.kind}${a.lines ? ` (${a.lines})` : ''}` },
}
const STATUS_DOT = {
running: '#f59e0b',
ok: '#22c55e',
error: '#ef4444',
}
function tryPretty(s) {
if (typeof s !== 'string') return ''
try {
return JSON.stringify(JSON.parse(s), null, 2)
} catch {
return s
}
}
function ResultSummary({ result }) {
if (!result) return null
try {
const parsed = JSON.parse(result)
if (parsed.ok === false) return <span style={{ color: '#ef4444' }}>{parsed.error}</span>
// Compact summaries per tool
if (parsed.bytes !== undefined) return <span>{parsed.bytes} bytes written</span>
if (parsed.deleted) return <span>deleted</span>
if (parsed.entries) return <span>{parsed.count} entries</span>
if (parsed.lines !== undefined && parsed.content !== undefined) {
return <span>{parsed.lines} lines{parsed.truncated ? ' (truncated)' : ''}</span>
}
if (parsed.lines_shown !== undefined) {
return <span>{parsed.lines_shown}/{parsed.lines_total} log lines</span>
}
return <span>ok</span>
} catch {
return <span>ok</span>
}
}
export default function ToolCallBlock({ tool }) {
const [open, setOpen] = useState(false)
const meta = TOOL_META[tool.name] || { icon: 'âš™', label: tool.name, summarize: () => '' }
const args = tool.args || {}
const status = tool.status || 'running'
const dot = STATUS_DOT[status] || STATUS_DOT.running
return (
<div
className="my-1.5 rounded-md text-[11.5px]"
style={{
background: 'var(--surface-3)',
border: '1px solid var(--border)',
overflow: 'hidden',
}}
>
{/* Header — clickable */}
<button
onClick={() => setOpen((v) => !v)}
className="w-full flex items-center gap-2 px-2.5 py-1.5 mono text-left transition-colors hover:bg-[var(--surface-2)]"
style={{ color: 'var(--text-dim)' }}
>
<span
className="w-1.5 h-1.5 rounded-full shrink-0"
style={{
background: dot,
animation: status === 'running' ? 'pulse-live 1.4s ease-in-out infinite' : undefined,
}}
/>
<span style={{ color: 'var(--accent-hi)', minWidth: 16 }}>{meta.icon}</span>
<span style={{ color: 'var(--text-dim)' }}>{meta.label}</span>
<span className="truncate" style={{ color: 'var(--text)', flex: 1 }}>
{meta.summarize(args)}
</span>
<span className="text-[10px]" style={{ color: 'var(--text-faint)' }}>
{status === 'running' ? '…' : <ResultSummary result={tool.result} />}
</span>
<span style={{ color: 'var(--text-faint)', fontSize: 9 }}>
{open ? 'â–¾' : 'â–¸'}
</span>
</button>
{/* Body — args + result */}
{open && (
<div className="px-2.5 py-2 border-t mono text-[10.5px] leading-relaxed"
style={{ borderColor: 'var(--border)' }}>
<div className="mb-1.5 uppercase tracking-wider text-[9px]" style={{ color: 'var(--text-faint)' }}>
args
</div>
<pre
className="whitespace-pre-wrap break-all"
style={{ color: 'var(--text-dim)', maxHeight: 200, overflow: 'auto' }}
>{JSON.stringify(args, null, 2)}</pre>
{tool.result && (
<>
<div className="mt-3 mb-1.5 uppercase tracking-wider text-[9px]" style={{ color: 'var(--text-faint)' }}>
result
</div>
<pre
className="whitespace-pre-wrap break-all"
style={{ color: 'var(--text-dim)', maxHeight: 240, overflow: 'auto' }}
>{tryPretty(tool.result)}</pre>
</>
)}
</div>
)}
</div>
)
}