import Markdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import type { Message } from '@/api/types'; import { Wrench, AlertCircle, Loader2 } from 'lucide-react'; interface Props { message: Message; } export function MessageBubble({ message }: Props) { if (message.role === 'tool') { return ; } const isUser = message.role === 'user'; const isStreaming = message.status === 'streaming'; const isFailed = message.status === 'failed'; return (
{isFailed && (
Failed
)} {message.tool_calls && message.tool_calls.length > 0 && (
{message.tool_calls.map((tc) => (
{tc.name} {truncateArgs(tc.arguments)}
))}
)} {message.content.trim() && (
{message.content}
)} {isStreaming && !message.content.trim() && (
Thinking...
)} {isStreaming && message.content.trim() && ( )}
); } function ToolResultBubble({ message }: Props) { const result = message.tool_results; if (!result) return null; const isError = result.error; const output = result.output || ''; const displayOutput = output.length > 300 ? output.slice(0, 300) + '...' : output; return (
{result.truncated && ( [truncated] )}
{displayOutput}
); } function truncateArgs(args: string): string { if (!args) return ''; try { const parsed = JSON.parse(args); const keys = Object.keys(parsed); if (keys.length === 0) return ''; const first = keys[0]!; const val = String(parsed[first]); const display = val.length > 40 ? val.slice(0, 40) + '...' : val; return `${first}: ${display}`; } catch { return args.length > 50 ? args.slice(0, 50) + '...' : args; } }