/* * Sidebar.tsx * Zyquo Cloud Web * * Author: Simon-Pierre Boucher * Mail: contact@spboucher.ai * * Conversation sidebar: wordmark, search (⌘F), New Chat, grouped rows * (Pinned / Today / Yesterday / Previous 7 Days / Older), footer with * Settings + usage summary. Collapses to a drawer on phones. */ import { useMemo, useRef } from 'react' import { useStore } from '../state/store' import { matchesSearch } from '../storage/conversations' import type { Conversation } from '../types' import ZyquoGlyph from './ZyquoGlyph' import { IconGear, IconPin, IconPlus, IconSearch, IconTrash } from './icons' const GROUP_ORDER = ['Pinned', 'Today', 'Yesterday', 'Previous 7 Days', 'Older'] as const function groupFor(conversation: Conversation, now: Date): (typeof GROUP_ORDER)[number] { if (conversation.pinned) return 'Pinned' const updated = new Date(conversation.updatedAt) const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime() if (updated.getTime() >= startOfToday) return 'Today' if (updated.getTime() >= startOfToday - 86_400_000) return 'Yesterday' if (updated.getTime() >= startOfToday - 7 * 86_400_000) return 'Previous 7 Days' return 'Older' } function relativeTime(timestamp: number): string { const delta = Date.now() - timestamp const minutes = Math.floor(delta / 60_000) if (minutes < 1) return 'now' if (minutes < 60) return `${minutes}m` const hours = Math.floor(minutes / 60) if (hours < 24) return `${hours}h` const days = Math.floor(hours / 24) if (days < 7) return `${days}d` return new Date(timestamp).toLocaleDateString() } export default function Sidebar() { const conversations = useStore((s) => s.conversations) const selectedID = useStore((s) => s.selectedID) const searchText = useStore((s) => s.searchText) const setSearchText = useStore((s) => s.setSearchText) const select = useStore((s) => s.select) const newConversation = useStore((s) => s.newConversation) const removeConversation = useStore((s) => s.removeConversation) const updateConversation = useStore((s) => s.updateConversation) const openSettings = useStore((s) => s.openSettings) const sidebarOpen = useStore((s) => s.sidebarOpen) const searchRef = useRef(null) const groups = useMemo(() => { const now = new Date() const filtered = conversations.filter((c) => matchesSearch(c, searchText)) const byGroup = new Map() for (const conversation of filtered) { const group = groupFor(conversation, now) const list = byGroup.get(group) ?? [] list.push(conversation) byGroup.set(group, list) } return GROUP_ORDER.filter((g) => byGroup.has(g)).map((g) => ({ name: g, items: byGroup.get(g) ?? [], })) }, [conversations, searchText]) const usage = useMemo(() => { let tokens = 0 let cost = 0 for (const conversation of conversations) { for (const message of conversation.messages) { tokens += (message.usage?.inputTokens ?? 0) + (message.usage?.outputTokens ?? 0) cost += message.estimatedCost ?? 0 } } return { tokens, cost } }, [conversations]) return ( ) }