import React, { useState, useEffect } from "react"; import { ChevronRight, ChevronDown, Terminal, FileText, FolderTree, Wrench, AlertCircle, Globe, Search, Pencil, Trash2, Move, FileSearch, Compass, Sparkles, History, MousePointer2, Eye, EyeOff, Copy, Check, ImageIcon, CheckSquare } from "lucide-react"; import { toast } from "sonner"; import { hasImagePayload, hasToolPreview } from "../lib/resolveToolPreview"; import { mergeImageFields } from "../lib/imagePreview"; import GeneratedImagePreview from "./GeneratedImagePreview"; import { getFileContentFromToolEvent, isCopyableFileToolEvent } from "../lib/filePreview"; import ChatPreviewBubble from "./ChatPreviewBubble"; import ImageGeneratingPlaceholder from "./ImageGeneratingPlaceholder"; const ICONS = { exec_shell: Terminal, read_file: FileText, write_file: FileText, edit_file: Pencil, list_dir: FolderTree, grep: Search, find_files: FileSearch, delete_path: Trash2, move_path: Move, web_search: Search, web_fetch: Globe, browser_visit: Compass, browser_open: Compass, browser_click: MousePointer2, browser_type: Compass, browser_fill: Compass, browser_snapshot: Compass, browser_scroll: Compass, browser_press: Compass, browser_close: Compass, emo_reflect: Sparkles, emo_think: Sparkles, emo_todo: CheckSquare, emo_remember: Sparkles, emo_introspect: Sparkles, emo_read_self: Sparkles, emo_edit_self: Sparkles, emo_list_self_saves: History, emo_restore_self: History, generate_image: ImageIcon, }; const COLORS = { exec_shell: "#06B6D4", read_file: "#A855F7", write_file: "#EC4899", edit_file: "#F472B6", list_dir: "#F59E0B", grep: "#22D3EE", find_files: "#818CF8", delete_path: "#EF4444", move_path: "#FB923C", web_search: "#10B981", web_fetch: "#3B82F6", browser_visit: "#6366F1", browser_open: "#6366F1", browser_click: "#38BDF8", browser_type: "#818CF8", browser_fill: "#818CF8", emo_reflect: "#E879F9", emo_think: "#E879F9", emo_todo: "#34D399", emo_remember: "#C084FC", emo_introspect: "#A855F7", emo_read_self: "#E879F9", emo_edit_self: "#E879F9", emo_list_self_saves: "#C084FC", emo_restore_self: "#C084FC", generate_image: "#E879F9", }; export const ToolCallCard = ({ event, liveHtmlByPath = {}, showCopyCode = false }) => { const [open, setOpen] = useState(event.state !== "done"); const [copied, setCopied] = useState(false); const isImageGen = event.tool === "generate_image"; const imageFields = mergeImageFields(event.inlinePreview, event.result); const hasImageData = isImageGen && hasImagePayload(imageFields); const canPreview = hasToolPreview(event) || hasImageData || Boolean( event.args?.url && [ "browser_visit", "browser_open", "web_fetch", "browser_click", "browser_snapshot", "browser_scroll", "browser_press", "browser_type", "browser_fill", ].includes(event.tool), ); const [previewOpen, setPreviewOpen] = useState( canPreview && (event.state === "done" || ["browser_visit", "browser_open"].includes(event.tool)), ); const Icon = ICONS[event.tool] || Wrench; const color = COLORS[event.tool] || "var(--mode-color)"; const isError = event.state === "error" || (event.result && event.result.ok === false); const isExecuting = event.state === "executing"; const showImageGenLoading = isImageGen && isExecuting; const fileContent = showCopyCode && isCopyableFileToolEvent(event) ? getFileContentFromToolEvent(event, liveHtmlByPath) : ""; const canCopyCode = Boolean(fileContent); const handleCopyCode = async () => { if (!fileContent) return; try { await navigator.clipboard.writeText(fileContent); setCopied(true); toast.success("Code copié"); setTimeout(() => setCopied(false), 2000); } catch { toast.error("Impossible de copier"); } }; useEffect(() => { if (isImageGen && isExecuting) { setPreviewOpen(true); } else if (event.state === "done" && canPreview) { setPreviewOpen(true); } }, [event.state, canPreview, event.inlinePreview, event.result, isImageGen, isExecuting]); const summary = (() => { if (isExecuting) return "En cours…"; if (isError) return event.result?.error || "erreur"; if (event.result?.exit_code !== undefined) return `code ${event.result.exit_code}`; if (event.result?.count !== undefined) return `${event.result.count} résultats`; if (event.result?.results !== undefined) return `${event.result.results.length} résultats`; if (event.result?.text !== undefined && event.result?.url !== undefined) return `${event.result.text.length} car.`; if (event.result?.content !== undefined) return `${event.result.content.length} car.`; if (event.result?.matches !== undefined) return `${event.result.matches.length} correspondances`; if (event.result?.entries !== undefined) return `${event.result.entries.length} entrées`; if (event.result?.replacements !== undefined) return `${event.result.replacements} remplacement(s)`; if (event.result?.files !== undefined && event.result?.dirs !== undefined) return `${event.result.files.length} fichiers · ${event.result.dirs.length} dossiers`; if (event.result?.files !== undefined && Array.isArray(event.result.files)) return `${event.result.files.length} fichiers`; return "ok"; })(); return (
{canCopyCode && event.state === "done" && !isError && ( )} {canPreview && event.state === "done" && !isError && ( )}
{open && (
{event.args && Object.keys(event.args).length > 0 && (
Arguments
                  {JSON.stringify(event.args, null, 2)}
                
)} {event.result && (
Résultat
                  {formatResult(event.result)}
                
)}
)}
{previewOpen && showImageGenLoading && ( )} {previewOpen && isImageGen && event.state === "done" && !isError && !showImageGenLoading && ( )} {previewOpen && !isImageGen && canPreview && !showImageGenLoading && ( )}
); }; function formatArgs(tool, args) { if (!args) return ""; if (tool === "exec_shell") return args.cmd || ""; if (tool === "read_file" || tool === "list_dir" || tool === "delete_path") return args.path || ""; if (tool === "write_file") return `${args.path} (${(args.content || "").length} car.)`; if (tool === "edit_file") return args.path || ""; if (tool === "grep") return `"${args.pattern || ""}" in ${args.path || "."}`; if (tool === "find_files") return args.pattern || ""; if (tool === "move_path") return `${args.from} → ${args.to}`; if (tool === "web_search") return `"${args.query || ""}"${args.focus && args.focus !== "general" ? ` [${args.focus}]` : ""}`; if (tool === "web_fetch" || tool === "browser_visit" || tool === "browser_open") return args.url || ""; if (tool.startsWith("browser_")) { if (args.x != null && args.y != null) return `@${args.x},${args.y}`; if (args.ref != null) return `ref=${args.ref}`; if (args.selector) return args.selector; if (args.text) return `"${String(args.text).slice(0, 40)}"`; return args.url || ""; } if (tool === "emo_reflect" || tool === "emo_think") return (args.thought || "").slice(0, 60); if (tool === "emo_todo") return (args.action || "todo") + (args.text ? `: ${args.text}` : ""); if (tool === "emo_remember") return (args.content || "").slice(0, 60); if (tool === "emo_edit_self") return `${args.section || ""} (${(args.content || "").length} car.)`; if (tool === "emo_read_self") return args.section || "tout"; if (tool === "emo_restore_self") return (args.version_id || "").slice(0, 8); if (tool === "generate_image") return (args.prompt || "").slice(0, 80); return JSON.stringify(args); } function formatResult(result) { if (!result) return ""; if (result.ok === false) return `ERREUR : ${result.error}`; if (result.stdout !== undefined || result.stderr !== undefined) { let out = ""; if (result.stdout) out += result.stdout; if (result.stderr) out += (out ? "\n--- stderr ---\n" : "") + result.stderr; return out || "(aucune sortie)"; } if (result.results !== undefined) { return result.results.map((r, i) => { const dom = r.domain ? ` (${r.domain})` : ""; return `${i + 1}. ${r.title}${dom}\n ${r.url}\n ${r.snippet || ""}`; }).join("\n\n").slice(0, 4000); } if (result.text !== undefined && result.url !== undefined) { let s = `📄 ${result.title || "(sans titre)"}\n🔗 ${result.url}\n\n`; s += result.text.slice(0, 3000); if (result.truncated) s += "\n\n... [tronqué]"; if (result.links?.length) { s += `\n\n--- ${result.links.length} liens ---\n`; s += result.links.slice(0, 10).map((l) => `· ${l.text} → ${l.url}`).join("\n"); } return s; } if (result.content !== undefined) return result.content.slice(0, 4000); if (result.matches !== undefined) { return result.matches.map((m) => `${m.file}:${m.line}: ${m.text}`).join("\n").slice(0, 4000); } if (result.entries !== undefined) { return result.entries.map((e) => `${e.is_dir ? "[dir]" : " "} ${e.path}`).join("\n").slice(0, 4000); } if (result.files !== undefined && result.dirs !== undefined) { return `📁 ${result.path}\n\n` + result.dirs.map((d) => `[dir] ${d}`).join("\n") + "\n" + result.files.map((f) => ` ${f}`).join("\n"); } if (result.files !== undefined && Array.isArray(result.files)) { return result.files.join("\n").slice(0, 4000); } return JSON.stringify(result, null, 2); } export default ToolCallCard;