ZARAI / frontend /src /components /layout /Sidebar.jsx
Srajal2579
Deploy JARVIS
3e646d9
Raw History Blame Contribute Delete
6.18 kB
/**
* JARVIS — Sidebar
* Session history, new chat button, model status indicators
*/
import React, { useState } from 'react'
import { motion } from 'framer-motion'
import {
Plus, MessageSquare, Trash2, ChevronRight,
Bot, Zap, Eye, Mic, Brain, Settings,
} from 'lucide-react'
import { formatDistanceToNow } from 'date-fns'
import clsx from 'clsx'
import useAppStore from '../../stores/appStore'
import toast from 'react-hot-toast'
export default function Sidebar() {
const {
sessions, activeSessionId, modelStatus,
newSession, switchSession, deleteSession,
} = useAppStore()
const [hoveredId, setHoveredId] = useState(null)
const handleNew = () => {
newSession()
toast('New chat started', { icon: '✨' })
}
const handleDelete = async (e, id) => {
e.stopPropagation()
if (sessions.length === 1) return toast.error('Cannot delete the only session')
await deleteSession(id)
toast('Session deleted')
}
const models = [
{ key: 'llm', label: 'LLM', icon: Brain },
{ key: 'blip', label: 'Vision', icon: Eye },
{ key: 'whisper', label: 'Voice', icon: Mic },
]
return (
<aside className="w-64 h-full bg-jarvis-surface border-r border-jarvis-border flex flex-col">
{/* Header */}
<div className="p-4 border-b border-jarvis-border">
<div className="flex items-center gap-2 mb-4">
<div className="w-8 h-8 rounded-lg bg-jarvis-accent/10 border border-jarvis-accent/30
flex items-center justify-center animate-glow">
<Bot size={16} className="text-jarvis-accent" />
</div>
<div>
<h1 className="font-display text-sm font-bold text-jarvis-accent tracking-widest">
JARVIS
</h1>
<p className="text-[10px] text-jarvis-dim font-mono">v1.0 · MULTI-MODAL</p>
</div>
</div>
<button
onClick={handleNew}
className="w-full flex items-center gap-2 px-3 py-2 rounded-lg
bg-jarvis-accent/10 border border-jarvis-accent/30
text-jarvis-accent text-sm font-medium
hover:bg-jarvis-accent/20 transition-all duration-200
active:scale-95"
>
<Plus size={14} />
New Chat
</button>
</div>
{/* Sessions */}
<div className="flex-1 overflow-y-auto py-2">
<p className="px-4 py-1 text-[10px] uppercase tracking-widest text-jarvis-dim font-mono">
Conversations
</p>
{sessions.length === 0 && (
<p className="px-4 py-3 text-jarvis-dim text-xs">No sessions yet.</p>
)}
{sessions.map((session) => (
<motion.div
key={session.id}
layout
initial={{ opacity: 0, x: -8 }}
animate={{ opacity: 1, x: 0 }}
className={clsx(
'group relative mx-2 mb-0.5 rounded-lg cursor-pointer',
'transition-colors duration-150',
activeSessionId === session.id
? 'bg-jarvis-accent/10 border border-jarvis-accent/20'
: 'hover:bg-jarvis-panel border border-transparent',
)}
onClick={() => switchSession(session.id)}
onMouseEnter={() => setHoveredId(session.id)}
onMouseLeave={() => setHoveredId(null)}
>
<div className="flex items-center gap-2 px-3 py-2.5">
<MessageSquare
size={13}
className={clsx(
'flex-shrink-0',
activeSessionId === session.id ? 'text-jarvis-accent' : 'text-jarvis-dim',
)}
/>
<div className="flex-1 min-w-0">
<p className={clsx(
'text-xs font-medium truncate',
activeSessionId === session.id ? 'text-jarvis-text' : 'text-jarvis-muted',
)}>
{session.title || 'New Chat'}
</p>
<p className="text-[10px] text-jarvis-dim truncate">
{session.updated_at
? formatDistanceToNow(new Date(session.updated_at), { addSuffix: true })
: '—'}
</p>
</div>
{/* Delete button */}
{hoveredId === session.id && (
<button
onClick={(e) => handleDelete(e, session.id)}
className="flex-shrink-0 p-1 rounded hover:text-jarvis-error
text-jarvis-dim transition-colors"
>
<Trash2 size={11} />
</button>
)}
</div>
</motion.div>
))}
</div>
{/* Model status */}
<div className="p-3 border-t border-jarvis-border">
<p className="text-[10px] uppercase tracking-widest text-jarvis-dim font-mono mb-2">
AI Modules
</p>
<div className="space-y-1">
{models.map(({ key, label, icon: Icon }) => {
const loaded = modelStatus?.models?.[key]
return (
<div key={key} className="flex items-center gap-2">
<div className={clsx(
'w-1.5 h-1.5 rounded-full',
loaded === true ? 'bg-jarvis-success animate-pulse-slow' :
loaded === false ? 'bg-jarvis-error' :
'bg-jarvis-dim',
)} />
<Icon size={11} className="text-jarvis-dim" />
<span className="text-[11px] text-jarvis-muted font-mono">{label}</span>
<span className={clsx(
'ml-auto text-[9px] font-mono',
loaded === true ? 'text-jarvis-success' :
loaded === false ? 'text-jarvis-error' :
'text-jarvis-dim',
)}>
{loaded === true ? 'ONLINE' : loaded === false ? 'OFFLINE' : '—'}
</span>
</div>
)
})}
</div>
</div>
</aside>
)
}