feat: make AI chat tool use blocks expandable with input/output details

Tool call blocks in AI Chat are now clickable and expandable to show
tool name, input parameters (pretty-printed JSON), and output/result.
Collapsed by default, keyboard accessible (Tab/Enter/Space/Esc).

Backend: Rust stream events now carry tool input (accumulated from
input_json_delta chunks) and tool output (from tool_result events).
Frontend: AiActionCard is a disclosure widget with aria-expanded,
error output shown in red, long content truncated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Test
2026-03-03 21:08:48 +01:00
parent ba7d2f1acc
commit ceee8b04ea
7 changed files with 597 additions and 89 deletions

View File

@@ -63,23 +63,54 @@ export function useAiAgent(vaultPath: string, contextPrompt?: string) {
update(m => ({ ...m, response: (m.response ?? '') + text }))
},
onToolStart: (toolName, toolId) => {
onToolStart: (toolName, toolId, input) => {
if (abortRef.current.aborted) return
setStatus('tool-executing')
update(m => {
const existing = m.actions.find(a => a.toolId === toolId)
if (existing) {
// Re-emitted with input data — update the existing action
return {
...m,
actions: m.actions.map(a =>
a.toolId === toolId ? { ...a, input: input ?? a.input } : a,
),
}
}
return {
...m,
actions: [...m.actions, {
tool: toolName,
toolId,
label: formatToolLabel(toolName, toolId),
status: 'pending' as const,
input,
}],
}
})
},
onToolDone: (toolId, output) => {
if (abortRef.current.aborted) return
update(m => ({
...m,
actions: [...m.actions, {
tool: toolName,
label: formatToolLabel(toolName, toolId),
status: 'pending' as const,
}],
actions: m.actions.map(a =>
a.toolId === toolId ? { ...a, status: 'done' as const, output } : a,
),
}))
},
onError: (error) => {
if (abortRef.current.aborted) return
setStatus('error')
update(m => ({ ...m, isStreaming: false, response: (m.response ?? '') + `\nError: ${error}` }))
update(m => ({
...m,
isStreaming: false,
response: (m.response ?? '') + `\nError: ${error}`,
actions: m.actions.map(a =>
a.status === 'pending' ? { ...a, status: 'error' as const } : a,
),
}))
},
onDone: () => {