Spaces:
Runtime error
Runtime error
| "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> | |
| ); | |
| } |