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
102 lines
2.9 KiB
TypeScript
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>
|
|
);
|
|
};
|