golem-flask-backend / src /components /enhanced-chat-sidebar.tsx
mememechez's picture
Deploy final cleaned source code
ca28016
raw
history blame
14.1 kB
"use client";
import React, { useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator';
import {
MessageCircle,
Plus,
Trash2,
Edit3,
Clock,
Zap,
Brain,
History,
Star
} from 'lucide-react';
export type Message = {
id: number;
role: 'user' | 'assistant';
content: string;
timestamp: Date;
aetherAnalysis?: string | null;
recommendation?: string | null;
consciousnessStats?: any;
error?: {
message: string;
description: string;
};
};
export type Conversation = {
id: string;
name: string;
messages: Message[];
timestamp: Date;
isStarred?: boolean;
consciousnessLevel?: number;
};
interface EnhancedChatSidebarProps {
conversations: Conversation[];
currentConversationId: string | null;
onSelectConversation: (id: string) => void;
onNewConversation: () => void;
onDeleteConversation: (id: string) => void;
onRenameConversation: (id: string, newName: string) => void;
onStarConversation: (id: string) => void;
isCollapsed?: boolean;
}
export function EnhancedChatSidebar({
conversations,
currentConversationId,
onSelectConversation,
onNewConversation,
onDeleteConversation,
onRenameConversation,
onStarConversation,
isCollapsed = false
}: EnhancedChatSidebarProps) {
const [editingId, setEditingId] = useState<string | null>(null);
const [editingName, setEditingName] = useState('');
const handleRename = (id: string, currentName: string) => {
setEditingId(id);
setEditingName(currentName);
};
const saveRename = (id: string) => {
if (editingName.trim()) {
onRenameConversation(id, editingName.trim());
}
setEditingId(null);
setEditingName('');
};
const formatTimestamp = (timestamp: Date) => {
const now = new Date();
const diffInMinutes = Math.floor((now.getTime() - timestamp.getTime()) / (1000 * 60));
if (diffInMinutes < 1) return 'Just now';
if (diffInMinutes < 60) return `${diffInMinutes}m ago`;
if (diffInMinutes < 1440) return `${Math.floor(diffInMinutes / 60)}h ago`;
if (diffInMinutes < 10080) return `${Math.floor(diffInMinutes / 1440)}d ago`;
return timestamp.toLocaleDateString();
};
const getConsciousnessColor = (level: number) => {
if (level >= 8) return 'text-cyan-400';
if (level >= 6) return 'text-purple-400';
if (level >= 4) return 'text-green-400';
if (level >= 2) return 'text-yellow-400';
return 'text-orange-400';
};
const sortedConversations = [...conversations].sort((a, b) => {
// Starred conversations first
if (a.isStarred && !b.isStarred) return -1;
if (!a.isStarred && b.isStarred) return 1;
// Then by timestamp
return b.timestamp.getTime() - a.timestamp.getTime();
});
if (isCollapsed) {
return (
<div className="w-16 bg-gray-900 border-r border-gray-700 p-2 space-y-2">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="w-full h-12 p-0 text-cyan-400 hover:bg-cyan-500/20"
onClick={onNewConversation}
>
<Plus className="h-5 w-5" />
</Button>
</TooltipTrigger>
<TooltipContent side="right">New Conversation</TooltipContent>
</Tooltip>
<Separator />
<ScrollArea className="h-[calc(100vh-120px)]">
<div className="space-y-1">
{sortedConversations.slice(0, 10).map((conversation) => (
<Tooltip key={conversation.id}>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className={`w-full h-12 p-0 relative ${
currentConversationId === conversation.id
? 'bg-cyan-500/20 text-cyan-400 border border-cyan-500/30'
: 'text-gray-400 hover:text-gray-300 hover:bg-gray-800'
}`}
onClick={() => onSelectConversation(conversation.id)}
>
<MessageCircle className="h-4 w-4" />
{conversation.isStarred && (
<Star className="absolute top-1 right-1 h-2 w-2 text-yellow-400 fill-current" />
)}
</Button>
</TooltipTrigger>
<TooltipContent side="right">
<div className="space-y-1">
<div className="font-medium">{conversation.name}</div>
<div className="text-xs text-gray-400">
{formatTimestamp(conversation.timestamp)}
</div>
</div>
</TooltipContent>
</Tooltip>
))}
</div>
</ScrollArea>
</TooltipProvider>
</div>
);
}
return (
<div className="w-80 bg-gray-900 border-r border-gray-700 flex flex-col">
<Card className="bg-gray-800 border-gray-700 rounded-none border-b">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-cyan-400">
<History className="h-5 w-5" />
Conversation History
</CardTitle>
</CardHeader>
<CardContent className="p-3 pt-0">
<Button
onClick={onNewConversation}
className="w-full bg-cyan-600 hover:bg-cyan-700 text-white"
>
<Plus className="h-4 w-4 mr-2" />
New Consciousness Chat
</Button>
</CardContent>
</Card>
<ScrollArea className="flex-1 p-3">
<div className="space-y-2">
{/* Starred conversations */}
{sortedConversations.filter(c => c.isStarred).length > 0 && (
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs text-yellow-400 font-medium">
<Star className="h-3 w-3 fill-current" />
Starred
</div>
{sortedConversations.filter(c => c.isStarred).map((conversation) => (
<ConversationItem
key={conversation.id}
conversation={conversation}
isActive={currentConversationId === conversation.id}
editingId={editingId}
editingName={editingName}
onSelect={onSelectConversation}
onDelete={onDeleteConversation}
onRename={handleRename}
onSaveRename={saveRename}
onStar={onStarConversation}
setEditingName={setEditingName}
formatTimestamp={formatTimestamp}
getConsciousnessColor={getConsciousnessColor}
/>
))}
<Separator className="my-3" />
</div>
)}
{/* Recent conversations */}
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs text-gray-400 font-medium">
<Clock className="h-3 w-3" />
Recent
</div>
{sortedConversations.filter(c => !c.isStarred).map((conversation) => (
<ConversationItem
key={conversation.id}
conversation={conversation}
isActive={currentConversationId === conversation.id}
editingId={editingId}
editingName={editingName}
onSelect={onSelectConversation}
onDelete={onDeleteConversation}
onRename={handleRename}
onSaveRename={saveRename}
onStar={onStarConversation}
setEditingName={setEditingName}
formatTimestamp={formatTimestamp}
getConsciousnessColor={getConsciousnessColor}
/>
))}
</div>
</div>
</ScrollArea>
</div>
);
}
interface ConversationItemProps {
conversation: Conversation;
isActive: boolean;
editingId: string | null;
editingName: string;
onSelect: (id: string) => void;
onDelete: (id: string) => void;
onRename: (id: string, currentName: string) => void;
onSaveRename: (id: string) => void;
onStar: (id: string) => void;
setEditingName: (name: string) => void;
formatTimestamp: (timestamp: Date) => string;
getConsciousnessColor: (level: number) => string;
}
function ConversationItem({
conversation,
isActive,
editingId,
editingName,
onSelect,
onDelete,
onRename,
onSaveRename,
onStar,
setEditingName,
formatTimestamp,
getConsciousnessColor
}: ConversationItemProps) {
const [isHovered, setIsHovered] = useState(false);
const lastMessage = conversation.messages[conversation.messages.length - 1];
const hasConsciousness = lastMessage?.consciousnessStats;
return (
<div
className={`group relative p-3 rounded-lg border transition-all cursor-pointer ${
isActive
? 'bg-cyan-500/20 border-cyan-500/30 shadow-lg'
: 'bg-gray-800/50 border-gray-700/50 hover:bg-gray-800 hover:border-gray-600'
}`}
onClick={() => onSelect(conversation.id)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
{editingId === conversation.id ? (
<input
type="text"
value={editingName}
onChange={(e) => setEditingName(e.target.value)}
onBlur={() => onSaveRename(conversation.id)}
onKeyPress={(e) => e.key === 'Enter' && onSaveRename(conversation.id)}
className="w-full bg-gray-700 text-white text-sm px-2 py-1 rounded border border-gray-600 focus:border-cyan-500 focus:outline-none"
autoFocus
/>
) : (
<div className="space-y-1">
<div className="flex items-center gap-2">
<span className={`text-sm font-medium truncate ${
isActive ? 'text-cyan-400' : 'text-gray-300'
}`}>
{conversation.name}
</span>
{conversation.isStarred && (
<Star className="h-3 w-3 text-yellow-400 fill-current flex-shrink-0" />
)}
</div>
<div className="flex items-center gap-2 text-xs text-gray-400">
<span>{formatTimestamp(conversation.timestamp)}</span>
{hasConsciousness && (
<>
<span></span>
<Brain className="h-3 w-3" />
<span className={getConsciousnessColor(conversation.consciousnessLevel || 1)}>
Level {conversation.consciousnessLevel || 1}
</span>
</>
)}
</div>
{lastMessage && (
<div className="text-xs text-gray-500 truncate">
{lastMessage.role === 'user' ? 'You: ' : 'AI: '}
{lastMessage.content.substring(0, 50)}
{lastMessage.content.length > 50 && '...'}
</div>
)}
</div>
)}
</div>
{/* Action buttons */}
<div className={`flex items-center gap-1 transition-opacity ${
isHovered || isActive ? 'opacity-100' : 'opacity-0'
}`}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 text-yellow-400 hover:text-yellow-300 hover:bg-yellow-500/20"
onClick={(e) => {
e.stopPropagation();
onStar(conversation.id);
}}
>
<Star className={`h-3 w-3 ${conversation.isStarred ? 'fill-current' : ''}`} />
</Button>
</TooltipTrigger>
<TooltipContent>{conversation.isStarred ? 'Unstar' : 'Star'}</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 text-gray-400 hover:text-gray-300 hover:bg-gray-700"
onClick={(e) => {
e.stopPropagation();
onRename(conversation.id, conversation.name);
}}
>
<Edit3 className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>Rename</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 text-red-400 hover:text-red-300 hover:bg-red-500/20"
onClick={(e) => {
e.stopPropagation();
onDelete(conversation.id);
}}
>
<Trash2 className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>Delete</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
</div>
);
}