Files
Atomizer/atomizer-dashboard/frontend/src/components/chat/ChatMessage.tsx
Anto01 ba0b9a1fae feat(dashboard): Enhanced chat, spec management, and Claude integration
Backend:
- spec.py: New AtomizerSpec REST API endpoints
- spec_manager.py: SpecManager service for unified config
- interview_engine.py: Study creation interview logic
- claude.py: Enhanced Claude API with context
- optimization.py: Extended optimization endpoints
- context_builder.py, session_manager.py: Improved services

Frontend:
- Chat components: Enhanced message rendering, tool call cards
- Hooks: useClaudeCode, useSpecWebSocket, improved useChat
- Pages: Updated Dashboard, Analysis, Insights, Setup, Home
- Components: ParallelCoordinatesPlot, ParetoPlot improvements
- App.tsx: Route updates for canvas/studio

Infrastructure:
- vite.config.ts: Build configuration updates
- start/stop-dashboard.bat: Script improvements
2026-01-20 13:10:47 -05:00

102 lines
2.9 KiB
TypeScript

import React from 'react';
import { Bot, User } from 'lucide-react';
import { MarkdownRenderer } from '../MarkdownRenderer';
import { ToolCallCard, ToolCall } from './ToolCallCard';
export interface Message {
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
timestamp: Date;
isStreaming?: boolean;
toolCalls?: ToolCall[];
}
interface ChatMessageProps {
message: Message;
}
export const ChatMessage: React.FC<ChatMessageProps> = ({ message }) => {
const isAssistant = message.role === 'assistant';
const isSystem = message.role === 'system';
// System messages are displayed centered with special styling
if (isSystem) {
return (
<div className="flex justify-center my-2">
<div className="px-3 py-1 bg-dark-700/50 rounded-full text-xs text-dark-400 border border-dark-600">
{message.content}
</div>
</div>
);
}
return (
<div
className={`flex gap-3 ${isAssistant ? '' : 'flex-row-reverse'}`}
>
{/* Avatar */}
<div
className={`flex-shrink-0 w-8 h-8 rounded-lg flex items-center justify-center ${
isAssistant
? 'bg-primary-500/20 text-primary-400'
: 'bg-dark-600 text-dark-300'
}`}
>
{isAssistant ? (
<Bot className="w-4 h-4" />
) : (
<User className="w-4 h-4" />
)}
</div>
{/* Message Content */}
<div
className={`flex-1 min-w-0 ${isAssistant ? 'pr-8' : 'pl-8'}`}
>
<div
className={`rounded-lg px-3 py-2 ${
isAssistant
? 'bg-dark-700/50 text-dark-100'
: 'bg-primary-500/10 text-dark-100 border border-primary-500/20'
}`}
>
{isAssistant ? (
<div className="prose prose-invert prose-sm max-w-none">
{/* Tool calls */}
{message.toolCalls && message.toolCalls.length > 0 && (
<div className="not-prose -mx-1">
{message.toolCalls.map((toolCall, idx) => (
<ToolCallCard key={idx} toolCall={toolCall} />
))}
</div>
)}
{/* Message content */}
{message.content && (
<MarkdownRenderer content={message.content} />
)}
{message.isStreaming && (
<span className="inline-block w-2 h-4 bg-primary-400 animate-pulse ml-1" />
)}
</div>
) : (
<p className="text-sm whitespace-pre-wrap">{message.content}</p>
)}
</div>
{/* Timestamp */}
<div
className={`text-xs text-dark-500 mt-1 ${
isAssistant ? 'text-left' : 'text-right'
}`}
>
{message.timestamp.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
})}
</div>
</div>
</div>
);
};