import React, { useState } from "react";
import { Copy, Check } from "lucide-react";
import { toast } from "sonner";
import EmoEyes from "./EmoEyes";
import ToolCallCard from "./ToolCallCard";
import LiveHtmlPreview from "./LiveHtmlPreview";
import { cleanDisplayText } from "../lib/messageClean";
import { buildImagePreviewSrc, buildImagePreviewPair } from "../lib/resolveToolPreview";
const MOOD_LABELS = {
neutre: "Neutre",
amusee: "Amusée",
concentree: "Concentrée",
sarcastique: "Sarcastique",
ironique: "Ironique",
enthousiaste: "Enthousiaste",
agacee: "Agacée",
curieuse: "Curieuse",
pensive: "Pensive",
};
const RichContent = ({ text, images, showCopyCode = false }) => {
if (images?.length) {
return (
{images.map((img, i) => (
)
))}
{text ?
: null}
);
}
return ;
};
const CodeBlock = ({ inner, lang, showCopyCode }) => {
const [copied, setCopied] = useState(false);
const isHtml = ["html", "htm"].includes((lang || "").toLowerCase());
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(inner);
setCopied(true);
toast.success("Code copié");
setTimeout(() => setCopied(false), 2000);
} catch {
toast.error("Impossible de copier");
}
};
return (
{showCopyCode && (
)}
{lang && (
{lang}
)}
{inner}
{showCopyCode && isHtml && inner.trim() && (
)}
);
};
const RichText = ({ text, showCopyCode = false }) => {
if (!text) return null;
const parts = text.split(/(```[\s\S]*?```|!\[[^\]]*\]\([^)]+\))/g);
return (
{parts.map((part, i) => {
if (part.startsWith("```")) {
const inner = part.replace(/^```(\w*)\n?/, "").replace(/```$/, "");
const lang = part.match(/^```(\w+)/)?.[1] || "";
return (
);
}
const imgMatch = part.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
if (imgMatch) {
const [, alt, src] = imgMatch;
const resolved = src.startsWith("data:") || src.startsWith("http")
? src
: `data:image/jpeg;base64,${src}`;
return (

);
}
const segments = part.split(/(`[^`]+`)/g);
return (
{segments.map((seg, j) => {
if (seg.startsWith("`") && seg.endsWith("`")) {
return (
{seg.slice(1, -1)}
);
}
return {seg};
})}
);
})}
);
};
function normalizeToolEvent(t, i) {
const args = t.arguments || t.args || {};
const tool = t.tool || t.name;
if (t.tool && t.state && (t.args || t.arguments) && t.inlinePreview) {
return { ...t, tool, args };
}
const result = {
ok: true,
path: args.path,
url: args.url,
content: args.content,
title: args.title,
};
let inlinePreview = t.inlinePreview || null;
if (!inlinePreview && tool === "web_search" && t.result?.results?.length) {
inlinePreview = {
type: "browser",
action: "search",
query: t.result.query || args.query || "",
results: (t.result.results || []).slice(0, 8),
};
}
if (
!inlinePreview &&
["browser_visit", "browser_open", "web_fetch", "browser_click", "browser_snapshot", "browser_scroll", "browser_press", "browser_type", "browser_fill"].includes(tool) &&
(t.result?.url || args.url || t.result?.screenshot_base64)
) {
inlinePreview = {
type: "browser",
action: tool === "browser_open" ? "control" : "visit",
url: t.result?.url || args.url,
title: t.result?.title,
preview: t.result?.preview || t.result?.text,
screenshot_base64: t.result?.screenshot_base64,
elements: t.result?.elements || [],
session_id: t.result?.session_id || "default",
};
}
if (!inlinePreview && tool === "edit_file" && args.path && t.result?.content) {
inlinePreview = {
type: "file",
path: args.path,
preview: String(t.result.content).slice(0, 50000),
language: (args.path.split(".").pop() || "").toLowerCase(),
};
}
if (!inlinePreview && tool === "write_file" && args.path && args.content) {
inlinePreview = {
type: "file",
path: args.path,
preview: String(args.content).slice(0, 50000),
language: (args.path.split(".").pop() || "").toLowerCase(),
};
}
if (!inlinePreview && tool === "generate_image") {
const res = t.result;
if (res?.ok !== false && (res?.image_url || res?.image_base64 || res?.has_image)) {
inlinePreview = {
type: "image",
image_url: res.image_url,
image_base64: res.image_base64,
mime: res.mime || "image/png",
title: args.prompt || res.prompt || res.subject || "Image générée",
has_image: res.has_image,
};
}
}
return {
id: t.id || `hist-${i}`,
tool,
args,
state: t.state || "done",
result: t.result || result,
inlinePreview,
};
}
export const ChatMessage = ({ message, isStreaming, liveHtmlByPath = {}, showCopyCode = false }) => {
const isUser = message.role === "user";
if (isUser) {
return (
);
}
const mood = message.mood;
return (
Émo
{mood && mood !== "neutre" && (
{MOOD_LABELS[mood] || mood}
)}
{message.verified === "true" && (
Vérifié
)}
{message.verified === "partial" && (
Partiel
)}
{isStreaming && (
)}
{((message.tool_calls_live && message.tool_calls_live.length > 0) ||
(message.tool_calls && message.tool_calls.length > 0)) && (
{(message.tool_calls_live || message.tool_calls).map((t, i) => (
))}
)}
{(message.content || message.images?.length) && (
)}
);
};
export default ChatMessage;