import Markdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import type { Message, ToolResult } from '@/api/types'; import { Wrench, AlertCircle, Loader2 } from 'lucide-react'; import { AskUserInputCard } from './AskUserInputCard'; interface Props { message: Message; chatId: string; toolResultsMap: Record; } export function MessageBubble({ message, chatId }: 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) => { if (tc.name === 'ask_user_input') { const result = message.tool_results ?? null; return ( ); } return (
{tc.name} {truncateArgs(tc.args)}
); })}
)} {message.content.trim() && (
{message.content}
)} {isStreaming && !message.content.trim() && (
Thinking...
)} {isStreaming && message.content.trim() && ( )}
); } function ToolResultBubble({ message }: { message: Message }) { const result = message.tool_results; if (!result) return null; const isError = result.error; const output = result.output != null ? String(result.output) : ''; const displayOutput = output.length > 300 ? output.slice(0, 300) + '...' : output; return (
{result.truncated && ( [truncated] )}
{displayOutput}
); } function truncateArgs(args: unknown): string { if (!args) return ''; try { if (typeof args === 'object' && args !== null) { const obj = args as Record; const keys = Object.keys(obj); if (keys.length === 0) return ''; const first = keys[0]!; const val = String(obj[first] ?? ''); const display = val.length > 40 ? val.slice(0, 40) + '...' : val; return `${first}: ${display}`; } const str = String(args); return str.length > 50 ? str.slice(0, 50) + '...' : str; } catch { return String(args).length > 50 ? String(args).slice(0, 50) + '...' : String(args); } }