import { useState, useEffect, memo } from 'react'; import { createPortal } from 'react-dom'; import { CheckCircle, ChevronRight, ChevronDown, Clock, Loader, Loader2, XCircle, Copy, Check, Brain, Maximize2, Download, File, FileText, Image, Music, Video, X, } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; import type { Components } from 'react-markdown'; import remarkGfm from 'remark-gfm'; import type { ChatMessage, Attachment, TaskToolResult } from '../../types/protocol'; import { ToolDetailModal } from './ToolDetailModal'; // 模块级常量:保持引用稳定,避免流式渲染期间每帧重建 // (react-markdown 收到新的 components/plugins 引用会重走内部映射与解析)。 const REMARK_PLUGINS = [remarkGfm]; const MARKDOWN_COMPONENTS: Components = { // 自定义代码块渲染 code({ className, children, ...props }) { const isInline = !className; if (isInline) { return ( {children} ); } return (
        
          {children}
        
      
); }, // 标题样式 h1: ({ children }) => (

{children}

), h2: ({ children }) => (

{children}

), h3: ({ children }) => (

{children}

), // 段落 p: ({ children }) =>

{children}

, // 列表 ul: ({ children }) => ( ), ol: ({ children }) => (
    {children}
), li: ({ children }) =>
  • {children}
  • , // 链接 a: ({ href, children }) => ( {children} ), // 表格 table: ({ children }) => ( {children}
    ), thead: ({ children }) => {children}, th: ({ children }) => ( {children} ), td: ({ children }) => ( {children} ), // 引用块 blockquote: ({ children }) => (
    {children}
    ), // 分隔线 hr: () =>
    , // 加粗和斜体 strong: ({ children }) => ( {children} ), em: ({ children }) => {children}, }; // 状态图标组件 function StatusIcon({ status, size = 14, }: { status: string; size?: number; }) { const iconClass = `transition-all duration-300`; switch (status) { case 'calling': case 'running': return ( ); case 'result': case 'success': case 'completed': return ( ); case 'failed': return ( ); case 'timeout': return ( ); case 'pending': case 'interrupted': case 'cancelled': return ( ); default: return null; } } interface MessageBubbleProps { message: ChatMessage; onNavigateToSubAgent?: (taskId: string, description: string, subagentType?: string) => void; showThinking?: boolean; /** 主代理当前生效模型(Task 卡片差异显示:子代理模型不同才展示模型徽章) */ effectiveModel?: { provider: string; model: string } | null; } function getAttachmentIcon(mediaType: string) { switch (mediaType) { case 'image': return ; case 'audio': return ; case 'video': return