"use client"; import * as React from "react"; import { usePathname, useRouter } from "next/navigation"; import { Archive, Bot, FolderKanban, Link2, MessageSquare, Pin, Sparkles, User, WandSparkles } from "lucide-react"; import type { SearchConversationHit, SearchMessageHit, SearchModelHit, SearchPromptHit, SearchProjectHit, SearchPresetHit, SearchGroup, SearchResponse } from "@/lib/client/types"; import { useApp } from "@/components/app/store"; import { ProviderIcon } from "@/components/brand/provider-icon"; import { providerName } from "@/lib/client/providers"; import { cn, formatRelative } from "@/lib/utils"; import { Highlight } from "./highlight"; export const GROUP_LABELS: Record = { conversations: "Conversations", messages: "Messages", models: "Models", prompts: "Prompts", projects: "Projects", presets: "Presets", }; export const GROUP_ORDER: SearchGroup[] = ["conversations", "messages", "projects", "models", "prompts", "presets"]; export function groupItems(data: SearchResponse, group: SearchGroup): unknown[] { return data[group] as unknown[]; } /* ------------------------------------------------------------------------------------------------ * Navigation — one place that knows where every hit goes. * messages → /app/chat/# (chat-view scrolls to the anchor) * ---------------------------------------------------------------------------------------------- */ export function useSearchNavigation(opts: { onNavigate?: (href: string | null) => void } = {}) { const router = useRouter(); const pathname = usePathname(); const { setSelectedModelKey } = useApp(); const done = opts.onNavigate; const go = React.useCallback( (href: string) => { done?.(href); router.push(href); }, [router, done], ); return React.useMemo( () => ({ openConversation: (c: SearchConversationHit) => go(`/app/chat/${c.id}`), openMessage: (m: SearchMessageHit) => { const href = `/app/chat/${m.conversationId}#${m.id}`; go(href); // Same conversation already open: the route does not remount, so scroll ourselves. if (pathname === `/app/chat/${m.conversationId}`) { requestAnimationFrame(() => document.getElementById(m.id)?.scrollIntoView({ block: "center", behavior: "smooth" })); } }, openModel: (m: SearchModelHit) => { setSelectedModelKey(m.key); window.dispatchEvent(new CustomEvent("polyllm:switch-model", { detail: { modelKey: m.key } })); if (!/^\/app\/chat/.test(pathname)) go("/app/chat"); else done?.(null); }, // Library prompts insert into the composer (workstream D contract); legacy presets open their editor. openPrompt: (p: SearchPromptHit) => go(p.kind === "library" ? `/app/chat?promptId=${p.id}` : `/app/prompts?edit=${p.id}`), openProject: (p: SearchProjectHit) => go(`/app/projects/${p.id}`), openPreset: (p: SearchPresetHit) => go(`/app/chat?preset=${p.id}`), }), [go, pathname, setSelectedModelKey, done], ); } /* ------------------------------------------------------------------------------------------------ * Row contents (presentational — wrapped by