deepsite-ai-coding / components /conversation-panel.tsx
likhonsheikh's picture
Upload folder using huggingface_hub
60e402b verified
'use client';
import { useState, useEffect } from 'react';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { MessageSquare, Copy, ThumbsUp, ThumbsDown, RefreshCw } from 'lucide-react';
import { cn } from '@/lib/utils';
interface ConversationPanelProps {
completion: string;
isLoading: boolean;
className?: string;
}
interface Message {
id: string;
role: 'user' | 'assistant';
content: string;
timestamp: Date;
feedback?: 'positive' | 'negative';
}
export function ConversationPanel({ completion, isLoading, className }: ConversationPanelProps) {
const [messages, setMessages] = useState<Message[]>([]);
const [currentMessage, setCurrentMessage] = useState<string>('');
// Update messages when completion changes
useEffect(() => {
if (completion) {
const newMessage: Message = {
id: Date.now().toString(),
role: 'assistant',
content: completion,
timestamp: new Date(),
};
setMessages(prev => [...prev, newMessage]);
}
}, [completion]);
// Update current message during streaming
useEffect(() => {
if (isLoading && completion) {
setCurrentMessage(completion);
} else if (!isLoading) {
setCurrentMessage('');
}
}, [completion, isLoading]);
const handleCopy = (content: string) => {
navigator.clipboard.writeText(content);
};
const handleFeedback = (messageId: string, feedback: 'positive' | 'negative') => {
setMessages(prev => prev.map(msg =>
msg.id === messageId ? { ...msg, feedback } : msg
));
};
const formatCode = (content: string) => {
// Simple code block formatting
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
let formatted = content;
let match;
const parts: Array<{ type: 'text' | 'code'; content: string; language?: string }> = [];
let lastIndex = 0;
while ((match = codeBlockRegex.exec(content)) !== null) {
if (match.index > lastIndex) {
parts.push({ type: 'text', content: content.slice(lastIndex, match.index) });
}
parts.push({
type: 'code',
content: match[2].trim(),
language: match[1] || 'javascript'
});
lastIndex = match.index + match[0].length;
}
if (lastIndex < content.length) {
parts.push({ type: 'text', content: content.slice(lastIndex) });
}
return parts.length > 0 ? parts : [{ type: 'text', content }];
};
return (
<div className={cn("flex flex-col h-full", className)}>
{/* Messages */}
<div className="flex-1 overflow-y-auto space-y-4 mb-4 max-h-[400px] code-area">
{messages.length === 0 && !isLoading && (
<div className="flex items-center justify-center h-full text-muted-foreground">
<div className="text-center">
<MessageSquare className="w-12 h-12 mx-auto mb-4 opacity-50" />
<p className="text-lg font-medium mb-2">Start a conversation</p>
<p className="text-sm">
Ask me to help with your code, generate functions, debug issues, or discuss programming concepts.
</p>
</div>
</div>
)}
{messages.map((message) => (
<Card key={message.id} className="relative">
<CardContent className="p-4">
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-2">
<Badge
variant={message.role === 'assistant' ? 'default' : 'secondary'}
className="text-xs"
>
{message.role === 'assistant' ? 'AI Assistant' : 'You'}
</Badge>
<span className="text-xs text-muted-foreground">
{message.timestamp.toLocaleTimeString()}
</span>
</div>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="sm"
onClick={() => handleCopy(message.content)}
className="h-6 w-6 p-0"
>
<Copy className="w-3 h-3" />
</Button>
{message.role === 'assistant' && (
<>
<Button
variant="ghost"
size="sm"
onClick={() => handleFeedback(message.id, 'positive')}
className={cn(
"h-6 w-6 p-0",
message.feedback === 'positive' && "text-green-600"
)}
>
<ThumbsUp className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleFeedback(message.id, 'negative')}
className={cn(
"h-6 w-6 p-0",
message.feedback === 'negative' && "text-red-600"
)}
>
<ThumbsDown className="w-3 h-3" />
</Button>
</>
)}
</div>
</div>
<div className="prose prose-sm max-w-none">
{formatCode(message.content).map((part, index) => (
<div key={index}>
{part.type === 'code' ? (
<div className="relative">
<Badge variant="outline" className="text-xs mb-2">
{part.language}
</Badge>
<pre className="bg-muted p-3 rounded-md overflow-x-auto text-sm">
<code>{part.content}</code>
</pre>
</div>
) : (
<div className="whitespace-pre-wrap text-sm leading-relaxed">
{part.content}
</div>
)}
</div>
))}
</div>
</CardContent>
</Card>
))}
{/* Current streaming message */}
{isLoading && currentMessage && (
<Card className="relative border-dashed">
<CardContent className="p-4">
<div className="flex items-center gap-2 mb-3">
<Badge variant="default" className="text-xs">
<RefreshCw className="w-3 h-3 mr-1 animate-spin" />
AI Assistant
</Badge>
<span className="text-xs text-muted-foreground">Thinking...</span>
</div>
<div className="prose prose-sm max-w-none">
{formatCode(currentMessage).map((part, index) => (
<div key={index}>
{part.type === 'code' ? (
<div className="relative">
<Badge variant="outline" className="text-xs mb-2">
{part.language}
</Badge>
<pre className="bg-muted p-3 rounded-md overflow-x-auto text-sm opacity-75">
<code>{part.content}</code>
</pre>
</div>
) : (
<div className="whitespace-pre-wrap text-sm leading-relaxed opacity-75">
{part.content}
</div>
)}
</div>
))}
</div>
</CardContent>
</Card>
)}
</div>
</div>
);
}