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 (
{JSON.stringify(event.args, null, 2)}
{formatResult(event.result)}