import { useState } from "react"; import Markdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { BookOpenText, Calculator, Check, ChevronDown, ChevronRight, Clock, CloudSun, FileSearch, Globe, Loader2, } from "lucide-react"; const TOOL_ICONS = { web_search: Globe, get_weather: CloudSun, calculator: Calculator, get_current_datetime: Clock, search_documents: FileSearch, }; const TOOL_LABELS = { web_search: "Searching the web", get_weather: "Checking the weather", calculator: "Calculating", get_current_datetime: "Checking the time", search_documents: "Searching your documents", }; function ToolChip({ tool }) { const Icon = TOOL_ICONS[tool.name] || Globe; const running = tool.status === "running"; const label = TOOL_LABELS[tool.name] || tool.name; const detail = tool.arguments?.query || tool.arguments?.location || tool.arguments?.expression || ""; return ( {running ? : } {label} {detail ? `: ${String(detail).slice(0, 60)}` : ""} ); } function Citations({ citations }) { const [open, setOpen] = useState(false); if (!citations?.length) return null; return (
{open && citations.map((citation, index) => (
{citation.source}
{citation.excerpt}
))}
); } export default function MessageBubble({ message }) { const isUser = message.role === "user"; return (
{isUser ? "You".slice(0, 1) : "S"}
{isUser ? "You" : "Synapse"}
{message.tools?.length > 0 && (
{message.tools.map((tool, index) => ( ))}
)}
{isUser ? ( {message.content} ) : ( {message.content} )}
{message.meta && (
{message.meta.model} {message.meta.input_tokens + message.meta.output_tokens} tokens ${Number(message.meta.cost_usd).toFixed(5)} {(message.meta.latency_ms / 1000).toFixed(1)}s
)}
); }