"use client"; import { useState, useEffect, useRef } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { Search, Plus, ArrowRight, BookOpen, Flag, Sparkles, X, } from "lucide-react"; interface Hit { slug: string; title: string; section: string; snippet?: string; score: number; } interface QuickAction { id: string; title: string; subtitle: string; icon: any; href?: string; action?: () => void; } export default function CommandMenu() { const [open, setOpen] = useState(false); const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const [selectedIndex, setSelectedIndex] = useState(0); const router = useRouter(); const inputRef = useRef(null); const quickActions: QuickAction[] = [ { id: "create-doc", title: "Create New Document", subtitle: "Write technical doc, writeup, or note", icon: Plus, href: "/create", }, { id: "hybrid-search", title: "Multi-Modal Hybrid Search", subtitle: "Full-text + Semantic vector search console", icon: Search, href: "/search", }, { id: "browse-docs", title: "Documentation Hub", subtitle: "System setup, API references, protocol specs", icon: BookOpen, href: "/docs", }, { id: "browse-writeups", title: "CTF & Writeups Directory", subtitle: "Pwn, Reverse, Web, Crypto writeups and solutions", icon: Flag, href: "/writeups", }, { id: "browse-research", title: "Research & Benchmarks", subtitle: "Architecture notes, benchmarks, experiments", icon: Sparkles, href: "/research", }, ]; // Global shortcut ⌘K or / useEffect(() => { function handleKeyDown(e: KeyboardEvent) { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); setOpen((prev) => !prev); } else if (e.key === "/" && !["INPUT", "TEXTAREA"].includes((e.target as HTMLElement)?.tagName)) { e.preventDefault(); setOpen(true); } else if (e.key === "Escape" && open) { setOpen(false); } } window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [open]); useEffect(() => { if (open) { setTimeout(() => inputRef.current?.focus(), 50); setQuery(""); setResults([]); setSelectedIndex(0); } }, [open]); // Debounced search useEffect(() => { if (!query.trim()) { setResults([]); return; } const timer = setTimeout(async () => { setLoading(true); try { const res = await fetch(`/api/search?q=${encodeURIComponent(query)}&limit=10`); const data = await res.json(); setResults(data.results || []); setSelectedIndex(0); } catch { setResults([]); } finally { setLoading(false); } }, 120); return () => clearTimeout(timer); }, [query]); function handleNavigate(href: string) { setOpen(false); router.push(href); } const totalItems = query.trim() ? results.length : quickActions.length; function handleKeyNavigation(e: React.KeyboardEvent) { if (e.key === "ArrowDown") { e.preventDefault(); setSelectedIndex((prev) => (totalItems > 0 ? (prev + 1) % totalItems : 0)); } else if (e.key === "ArrowUp") { e.preventDefault(); setSelectedIndex((prev) => (totalItems > 0 ? (prev - 1 + totalItems) % totalItems : 0)); } else if (e.key === "Enter") { e.preventDefault(); if (query.trim() && results[selectedIndex]) { handleNavigate(`/${results[selectedIndex].slug}`); } else if (!query.trim() && quickActions[selectedIndex]?.href) { handleNavigate(quickActions[selectedIndex].href!); } } } if (!open) { return ( ); } return (
{/* Backdrop */}
setOpen(false)} /> {/* Modal */}
{/* Search input bar */}
setQuery(e.target.value)} onKeyDown={handleKeyNavigation} placeholder="Search documents, writeups, concepts, or tools..." className="w-full bg-transparent text-[var(--text-primary)] placeholder-[var(--text-dim)] text-sm focus:outline-none" /> {loading && (
)}
{/* Results / Quick Actions List */}
{query.trim() === "" ? (
Quick Shortcuts
{quickActions.map((action, idx) => { const Icon = action.icon; const isSelected = idx === selectedIndex; return (
action.href && handleNavigate(action.href)} onMouseEnter={() => setSelectedIndex(idx)} className={`flex items-center justify-between gap-3 px-3 py-2 rounded-lg cursor-pointer transition-colors ${ isSelected ? "bg-[var(--bg-elevated)] text-[var(--text-primary)]" : "hover:bg-[var(--bg-elevated)] text-[var(--text-secondary)]" }`} >
{action.title}
{action.subtitle}
); })}
) : results.length === 0 && !loading ? (

No knowledge base items matched "{query}"

) : (
    {results.map((hit, index) => { const isSelected = index === selectedIndex; return (
  • handleNavigate(`/${hit.slug}`)} onMouseEnter={() => setSelectedIndex(index)} className={`flex items-start justify-between gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-colors ${ isSelected ? "bg-[var(--bg-elevated)] text-[var(--text-primary)] border border-[var(--border-color)]" : "hover:bg-[var(--bg-elevated)] text-[var(--text-secondary)] border border-transparent" }`} >
    {hit.section} {hit.title}
    {hit.snippet && (

    )}

    /{hit.slug}
  • ); })}
)}
{/* Footer */}
[ ↑↓ NAVIGATE ] [ ↵ SELECT ]
setOpen(false)} className="hover:text-[var(--text-primary)] font-medium flex items-center gap-1" > Full Search Console
); }