import type { ModelMessage } from 'ai';
import { Streamdown } from 'streamdown';
import { DataInspector } from '../ui/data-inspector';
interface ConversationViewProps {
messages: ModelMessage[];
}
export function ConversationView({ messages }: ConversationViewProps) {
if (messages.length === 0) {
return (
No messages
);
}
return (
{messages.map((message, index) => (
))}
);
}
function MessageBubble({ message }: { message: ModelMessage }) {
const role = message.role;
const parts = parseContent(message.content);
const style = getRoleStyle(role);
return (
{/* Role header */}
{role}
{/* Content */}
{parts.map((part, i) => (
))}
);
}
interface ParsedPart {
type: 'text' | 'tool-call' | 'tool-result';
text?: string;
toolName?: string;
input?: unknown;
output?: unknown;
}
function ContentPart({ part, role }: { part: ParsedPart; role: string }) {
if (part.type === 'text') {
if (!part.text) return null;
// Use Streamdown for assistant messages (they often contain markdown)
if (role === 'assistant') {
return (
{part.text}
);
}
return (
{part.text}
);
}
if (part.type === 'tool-call') {
return (
🔧
{part.toolName}
{part.input != null && (
{typeof part.input === 'string' ? (
{part.input}
) : (
)}
)}
);
}
if (part.type === 'tool-result') {
return (
✓
{part.toolName} result
{part.output != null && (
{typeof part.output === 'string' ? (
{part.output}
) : (
)}
)}
);
}
return null;
}
function getRoleStyle(role: string) {
switch (role) {
case 'user':
return {
bg: 'var(--ds-blue-100)',
border: 'var(--ds-blue-300)',
label: 'var(--ds-blue-700)',
};
case 'assistant':
return {
bg: 'var(--ds-gray-100)',
border: 'var(--ds-gray-300)',
label: 'var(--ds-gray-700)',
};
case 'system':
return {
bg: 'var(--ds-amber-100)',
border: 'var(--ds-amber-300)',
label: 'var(--ds-amber-700)',
};
case 'tool':
return {
bg: 'var(--ds-green-50)',
border: 'var(--ds-green-300)',
label: 'var(--ds-green-700)',
};
default:
return {
bg: 'var(--ds-gray-100)',
border: 'var(--ds-gray-300)',
label: 'var(--ds-gray-700)',
};
}
}
function parseContent(content: unknown): ParsedPart[] {
if (typeof content === 'string') {
return [{ type: 'text', text: content }];
}
if (Array.isArray(content)) {
return content.map((part): ParsedPart => {
if (typeof part === 'string') {
return { type: 'text', text: part };
}
if (part?.type === 'text') {
return { type: 'text', text: String(part.text ?? '') };
}
if (part?.type === 'tool-call') {
return {
type: 'tool-call',
toolName: part.toolName,
input: part.input,
};
}
if (part?.type === 'tool-result') {
return {
type: 'tool-result',
toolName: part.toolName,
output: part.output,
};
}
return { type: 'text', text: '' };
});
}
return [];
}