Download frontend/src/components/layout/Sidebar.jsx from Rixhot/ZARAI: direct link, hf CLI and curl.
- Browser
- Download file 6.18 kB
-
https://huggingface.co/spaces/Rixhot/ZARAI/resolve/main/frontend/src/components/layout/Sidebar.jsx
- Command line
-
hf download hf://spaces/Rixhot/ZARAI/frontend/src/components/layout/Sidebar.jsx
-
curl -L -o Sidebar.jsx https://huggingface.co/spaces/Rixhot/ZARAI/resolve/main/frontend/src/components/layout/Sidebar.jsx
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> | |
| ) | |
| } | |