"use client"; import React, { useState, useEffect, useRef, useMemo } from "react"; import { Search, MessageSquare, Folder, Brain, Database, Sliders, Plus, Network, Volume2, VolumeX, ArrowRight, CornerDownLeft, Zap, Pin, Users } from "lucide-react"; import type { Conversation, Project } from "@/types"; import { playClickSound, toggleSoundMute, isSoundMuted } from "@/lib/soundEffects"; interface QuickSwitcherProps { isOpen: boolean; onClose: () => void; conversations: Conversation[]; projects: Project[]; activeConvId: string; onSelectConversation: (id: string) => void; onSelectProject: (id: string | null) => void; onNewSession: () => void; onOpenKnowledge: () => void; onOpenSettings: () => void; onInsertCommand: (command: string) => void; enableReasoning: boolean; onToggleReasoning: () => void; } interface SearchItem { id: string; title: string; subtitle?: string; category: "session" | "project" | "command" | "action"; icon: React.ReactNode; badge?: string; action: () => void; } export const QuickSwitcher: React.FC = ({ isOpen, onClose, conversations, projects, activeConvId, onSelectConversation, onSelectProject, onNewSession, onOpenKnowledge, onOpenSettings, onInsertCommand, enableReasoning, onToggleReasoning, }) => { const [query, setQuery] = useState(""); const [selectedIndex, setSelectedIndex] = useState(0); const inputRef = useRef(null); const listRef = useRef(null); // Focus input when opened useEffect(() => { if (isOpen) { setQuery(""); setSelectedIndex(0); setTimeout(() => inputRef.current?.focus(), 50); } }, [isOpen]); const items = useMemo(() => { const list: SearchItem[] = []; // Core actions list.push({ id: "action_new_session", title: "New Session", subtitle: "Start a fresh chat with Purcar", category: "action", icon: , action: () => { onNewSession(); onClose(); }, }); list.push({ id: "action_toggle_reasoning", title: enableReasoning ? "Disable Reasoning Mode" : "Enable Reasoning Mode", subtitle: enableReasoning ? "Switch to direct high-speed responses" : "Enable cognitive chain", category: "action", icon: , badge: enableReasoning ? "ON" : "OFF", action: () => { onToggleReasoning(); onClose(); }, }); list.push({ id: "action_toggle_sound", title: isSoundMuted() ? "Unmute Audio Haptics" : "Mute Audio Haptics", subtitle: "Tactile Discord-inspired audio cues", category: "action", icon: isSoundMuted() ? : , action: () => { toggleSoundMute(); onClose(); }, }); list.push({ id: "action_knowledge", title: "Knowledge Base (RAG)", subtitle: "Manage private documents and embeddings", category: "action", icon: , action: () => { onOpenKnowledge(); onClose(); }, }); list.push({ id: "action_settings", title: "Engine Hyperparameters", subtitle: "Temperature, Top-K, Repetition Penalty, Max Tokens", category: "action", icon: , action: () => { onOpenSettings(); onClose(); }, }); // Commands [ { name: "@web_search", desc: "Live web search & source citations", icon: }, { name: "@rag", desc: "Retrieve answers from knowledge base", icon: }, { name: "@reasoning", desc: "Force cognitive step-by-step thinking", icon: }, { name: "@raw", desc: "Bypass reasoning, RAG & web search", icon: }, ].forEach((cmd) => { list.push({ id: `cmd_${cmd.name}`, title: cmd.name, subtitle: cmd.desc, category: "command", icon: cmd.icon, badge: "command", action: () => { onInsertCommand(`${cmd.name} `); onClose(); }, }); }); // Projects projects.forEach((proj) => { list.push({ id: `proj_${proj.id}`, title: proj.name, subtitle: "Filter workspace by this project", category: "project", icon: , badge: "project", action: () => { onSelectProject(proj.id); onClose(); }, }); }); // Conversations conversations.forEach((conv) => { list.push({ id: `conv_${conv.id}`, title: conv.title || "Untitled Session", subtitle: `${conv.messages?.length || 0} messages · ${new Date(conv.updatedAt || conv.createdAt).toLocaleDateString()}`, category: "session", icon: conv.isGroup ? : , badge: conv.id === activeConvId ? "active" : conv.isPinned ? "pinned" : undefined, action: () => { onSelectConversation(conv.id); onClose(); }, }); }); return list; }, [ conversations, projects, activeConvId, enableReasoning, onNewSession, onToggleReasoning, onOpenKnowledge, onOpenSettings, onInsertCommand, onSelectProject, onSelectConversation, onClose, ]); // Filter items by query const filtered = useMemo(() => { const q = query.trim().toLowerCase(); if (!q) return items; return items.filter( (item) => item.title.toLowerCase().includes(q) || (item.subtitle && item.subtitle.toLowerCase().includes(q)) || item.category.includes(q) ); }, [items, query]); // Handle keyboard navigation const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "ArrowDown") { e.preventDefault(); setSelectedIndex((prev) => (prev + 1) % (filtered.length || 1)); } else if (e.key === "ArrowUp") { e.preventDefault(); setSelectedIndex((prev) => (prev - 1 + filtered.length) % (filtered.length || 1)); } else if (e.key === "Enter") { e.preventDefault(); if (filtered[selectedIndex]) { playClickSound(); filtered[selectedIndex].action(); } } else if (e.key === "Escape") { e.preventDefault(); onClose(); } }; // Keep selected element visible useEffect(() => { if (listRef.current) { const selectedEl = listRef.current.children[selectedIndex] as HTMLElement; if (selectedEl) { selectedEl.scrollIntoView({ block: "nearest" }); } } }, [selectedIndex]); if (!isOpen) return null; return (
e.stopPropagation()} onKeyDown={handleKeyDown} > {/* Search Input Bar */}
{ setQuery(e.target.value); setSelectedIndex(0); }} placeholder="Search sessions, commands (@), projects, or actions..." className="flex-1 bg-transparent text-[#f5f0eb] placeholder-zinc-500 text-xs outline-none border-none" /> ESC
{/* Results List */}
{filtered.length === 0 ? (
No matching sessions, commands, or actions found for "{query}"
) : ( filtered.map((item, index) => { const isSelected = index === selectedIndex; return (
{ playClickSound(); item.action(); }} onMouseEnter={() => setSelectedIndex(index)} className={`flex items-center justify-between px-3 py-2 rounded-md cursor-pointer transition-colors ${ isSelected ? "bg-white/[0.08] text-[#f5f0eb] border border-white/10" : "text-zinc-300 hover:bg-white/[0.04] border border-transparent" }`} >
{item.icon}
{item.title} {item.badge && ( {item.badge} )}
{item.subtitle && (
{item.subtitle}
)}
{isSelected && ( Select )}
); }) )}
{/* Footer info */}
↑ ↓ navigate ↵ select
Discord Quick Switcher · Ctrl+K
); };