import { cn } from "@/lib/utils"; import { User, Bot, Copy, Check, RotateCcw, Pencil, Minimize2 } from "lucide-react"; import { useState } from "react"; import { ToolCallCard } from "./ToolCallCard"; import { ThinkingBlock, extractThinkingBlocks } from "./ThinkingBlock"; import { Streamdown } from "streamdown"; import type { ChatMessage, ContentSegment } from "@/hooks/useChat"; export function MessageBubble({ message, onRetry, onEdit, }: { message: ChatMessage; onRetry?: () => void; onEdit?: (content: string) => void; }) { const [copied, setCopied] = useState(false); const [isEditing, setIsEditing] = useState(false); const [editValue, setEditValue] = useState(message.content); const isUser = message.role === "user"; const isSystem = message.role === "system"; const copyContent = () => { if (message.content) { navigator.clipboard.writeText(message.content); setCopied(true); setTimeout(() => setCopied(false), 2000); } }; const handleEdit = () => { setIsEditing(true); setEditValue(message.content); }; const submitEdit = () => { if (onEdit && editValue.trim()) { onEdit(editValue.trim()); } setIsEditing(false); }; if (isSystem) { // Detect compact report (matches original format_compact_report) const isCompactReport = message.content.startsWith("Compact\n"); if (isCompactReport) { return (
{message.content}
); } return (
{message.content}
); } // ─── Segmented rendering for assistant messages ─────────────────── // If we have contentSegments, render them in order (interleaved text + tools). // Otherwise fall back to legacy rendering (all tools then all text). const hasSegments = !isUser && message.contentSegments && message.contentSegments.length > 0; const toolCalls = message.toolCalls || []; const renderSegmentedContent = () => { if (!message.contentSegments) return null; return message.contentSegments.map((segment: ContentSegment, i: number) => { if (segment.type === "text") { const text = segment.content?.trim(); if (!text) return null; return (
{segment.content}
); } if (segment.type === "tool") { const tool = toolCalls[segment.toolIndex]; if (!tool) return null; return (
); } return null; }); }; const renderLegacyContent = () => { return ( <> {/* Tool calls — show BEFORE message text */} {toolCalls.length > 0 && (
{toolCalls.map((tool) => ( ))}
)} {/* Message text — after tool calls */} {isEditing ? (