"use client"; import { Command } from "cmdk"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { useQuery } from "@tanstack/react-query"; import { FileText, Plus, Sparkles, Home, Share2, Settings, Search, BarChart3, Trash2, Database, FlaskConical, Moon, Sun, Columns3, Bot, Inbox } from "lucide-react"; import { useUI } from "@/lib/client/store"; import { api } from "@/lib/client/api"; import type { TreeNode } from "./types"; import { cn } from "@/lib/utils"; export function CommandPalette({ tree }: { tree: TreeNode[] }) { const ui = useUI(); const r = useRouter(); const [q, setQ] = useState(""); const [mode, setMode] = useState<"quick" | "full">("quick"); useEffect(() => { if (!ui.paletteOpen) { setQ(""); setMode("quick"); } }, [ui.paletteOpen]); const { data: hits = [] } = useQuery({ queryKey: ["search", mode, q], queryFn: () => api(`/api/search?mode=${mode === "full" ? "hybrid" : "quick"}&q=${encodeURIComponent(q)}&limit=12`), enabled: ui.paletteOpen && (mode === "full" ? q.length > 2 : true), staleTime: 5000 }); const go = (href: string) => { ui.set({ paletteOpen: false }); r.push(href); }; const run = (fn: () => void) => { ui.set({ paletteOpen: false }); fn(); }; if (!ui.paletteOpen) return null; const Item = ({ onSelect, icon, children, hint, value }: { onSelect: () => void; icon: React.ReactNode; children: React.ReactNode; hint?: string; value: string }) => {icon}{children}{hint && {hint}}; return (
ui.set({ paletteOpen: false })}> e.stopPropagation()} onKeyDown={(e) => { if (e.key === "Escape") ui.set({ paletteOpen: false }); if (e.key === "Tab") { e.preventDefault(); setMode((m) => (m === "quick" ? "full" : "quick")); } }}>
{mode === "full" && q.length <= 2 ? "Type at least 3 characters" : "Nothing found"} {mode === "quick" ? (<> {q.trim() && } onSelect={() => go(`/search?q=${encodeURIComponent(q)}`)}>Search content for “{q}”} {q.trim() && } onSelect={() => run(async () => { const p = await api("/api/pages", { method: "POST", json: { title: q } }); r.push(`/p/${p.id}`); })}>New page “{q}”} onSelect={() => run(() => ui.openAI({ prefill: q }))}>Ask AI “{q}”} {(q ? (hits as any[]) : tree.slice(0, 8)).map((p: any) => : p.type === "research" ? : )} hint={p.section} onSelect={() => go(`/p/${p.id}`)}>{p.title || "Untitled"})} } onSelect={() => go("/")}>Home} onSelect={() => go("/inbox")}>Tasks} onSelect={() => go("/graph")}>Knowledge graph} onSelect={() => go("/ai/models")}>AI models dashboard} onSelect={() => go("/ai/compare")}>Compare models} onSelect={() => go("/ai/agents")}>Agents} onSelect={() => go("/trash")}>Trash} onSelect={() => go("/settings")}>Settings } hint="⌘⇧N" onSelect={() => run(async () => { const p = await api("/api/pages", { method: "POST", json: { title: "" } }); r.push(`/p/${p.id}`); })}>New page} onSelect={() => run(async () => { const p = await api("/api/pages", { method: "POST", json: { title: "New database", type: "database", section: "databases" } }); r.push(`/p/${p.id}`); })}>New database} hint="⌘/" onSelect={() => run(() => ui.openAI())}>Open AI assistant} onSelect={() => run(() => ui.setTheme("dark"))}>Dark mode} onSelect={() => run(() => ui.setTheme("light"))}>Light mode ) : ( (hits as any[]).map((h: any) => go(`/p/${h.pageId}${h.blockId ? `#block-${h.blockId}` : ""}`)} className="cursor-pointer rounded-md px-2 py-2 data-[selected=true]:bg-bg-sunk">
{h.icon ?? "📄"}{h.title}{h.heading && › {h.heading}}{h.semantic ? `sem ${(h.semantic * 100).toFixed(0)}%` : "kw"}
{h.content}
) )}
); }