'use client' import Image from 'next/image' import { useState } from 'react' import { ChatMessage } from '@/store' import { detectTextDirection } from '@/lib/chat-utils' const AGENT_COLORS: Record = { coordinator: { bg: 'bg-purple-500/10', text: 'text-purple-400', border: 'border-purple-500/20' }, aegis: { bg: 'bg-red-500/10', text: 'text-red-400', border: 'border-red-500/20' }, research: { bg: 'bg-green-500/10', text: 'text-green-400', border: 'border-green-500/20' }, design: { bg: 'bg-pink-500/10', text: 'text-pink-400', border: 'border-pink-500/20' }, quant: { bg: 'bg-yellow-500/10', text: 'text-yellow-400', border: 'border-yellow-500/20' }, ops: { bg: 'bg-orange-500/10', text: 'text-orange-400', border: 'border-orange-500/20' }, reviewer: { bg: 'bg-teal-500/10', text: 'text-teal-400', border: 'border-teal-500/20' }, content: { bg: 'bg-indigo-500/10', text: 'text-indigo-400', border: 'border-indigo-500/20' }, seo: { bg: 'bg-cyan-500/10', text: 'text-cyan-400', border: 'border-cyan-500/20' }, security: { bg: 'bg-rose-500/10', text: 'text-rose-400', border: 'border-rose-500/20' }, ai: { bg: 'bg-violet-500/10', text: 'text-violet-400', border: 'border-violet-500/20' }, 'frontend-dev': { bg: 'bg-sky-500/10', text: 'text-sky-400', border: 'border-sky-500/20' }, 'backend-dev': { bg: 'bg-emerald-500/10', text: 'text-emerald-400', border: 'border-emerald-500/20' }, 'solana-dev': { bg: 'bg-amber-500/10', text: 'text-amber-400', border: 'border-amber-500/20' }, system: { bg: 'bg-muted/50', text: 'text-muted-foreground', border: 'border-border' }, human: { bg: 'bg-primary/10', text: 'text-primary', border: 'border-primary/20' }, } function getAgentTheme(name: string) { return AGENT_COLORS[name.toLowerCase()] || { bg: 'bg-muted/50', text: 'text-muted-foreground', border: 'border-border' } } function formatTime(timestamp: number): string { const date = new Date(timestamp * 1000) return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) } function asRecord(value: unknown): Record { return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record) : {} } // Simple markdown-lite: bold, italic, code, links function renderContent(text: string) { // Split by code blocks first const parts = text.split(/(```[\s\S]*?```|`[^`]+`)/g) return parts.map((part, i) => { // Multi-line code block if (part.startsWith('```') && part.endsWith('```')) { const code = part.slice(3, -3).replace(/^\w+\n/, '') // strip language hint return (
          {code}
        
) } // Inline code if (part.startsWith('`') && part.endsWith('`')) { return ( {part.slice(1, -1)} ) } // Regular text with bold/italic return ( {part.split(/(\*\*[^*]+\*\*|\*[^*]+\*)/g).map((segment, j) => { if (segment.startsWith('**') && segment.endsWith('**')) { return {segment.slice(2, -2)} } if (segment.startsWith('*') && segment.endsWith('*')) { return {segment.slice(1, -1)} } return segment })} ) }) } interface MessageBubbleProps { message: ChatMessage isHuman: boolean isGrouped: boolean } function ToolCallBubble({ message }: { message: ChatMessage }) { const [expanded, setExpanded] = useState(false) const meta = asRecord(message.metadata) const toolName = typeof meta.toolName === 'string' ? meta.toolName : 'unknown_tool' const toolArgs = meta.toolArgs const toolOutput = meta.toolOutput const toolStatus = meta.toolStatus === 'running' || meta.toolStatus === 'success' || meta.toolStatus === 'error' ? meta.toolStatus : undefined const durationMs = typeof meta.durationMs === 'number' ? meta.durationMs : undefined const theme = getAgentTheme(message.from_agent) const statusIcon = toolStatus === 'running' ? '...' : toolStatus === 'error' ? 'x' : '>' const statusColor = toolStatus === 'running' ? 'text-yellow-400' : toolStatus === 'error' ? 'text-red-400' : 'text-green-400' return (
{expanded && (
{toolArgs != null && (
Args
                  {typeof toolArgs === 'string' ? toolArgs : JSON.stringify(toolArgs, null, 2)}
                
)} {toolOutput != null && (
Output
                  {typeof toolOutput === 'string' ? toolOutput : JSON.stringify(toolOutput, null, 2)}
                
)}
)}
) } export function MessageBubble({ message, isHuman, isGrouped }: MessageBubbleProps) { const isSystem = message.message_type === 'system' const isHandoff = message.message_type === 'handoff' const isCommand = message.message_type === 'command' const isToolCall = message.message_type === 'tool_call' const theme = getAgentTheme(message.from_agent) if (isSystem) { return (
{message.content}
) } if (isHandoff) { return (
{message.from_agent} handed off to {message.to_agent}
) } if (isToolCall) { return } return (
{/* Avatar */} {!isGrouped ? (
{message.from_agent.charAt(0).toUpperCase()}
) : (
)} {/* Content */}
{/* Name + recipient */} {!isGrouped && (
{message.from_agent} {message.to_agent && ( {message.to_agent} )} {formatTime(message.created_at)}
)} {/* Bubble */}
{/* Attachment thumbnails */} {message.attachments && message.attachments.length > 0 && (
{message.attachments.map((att, idx) => ( att.type.startsWith('image/') ? ( {att.name} ) : (
{att.name} {att.size < 1024 ? `${att.size} B` : att.size < 1024 * 1024 ? `${(att.size / 1024).toFixed(1)} KB` : `${(att.size / (1024 * 1024)).toFixed(1)} MB`}
) ))}
)} {isCommand ? (
{message.content}
) : (
{renderContent(message.content)}
)}
) }