"use client"; import { useState, Suspense, useEffect } from "react"; import Link from "next/link"; import { useSearchParams, useRouter } from "next/navigation"; import { useSections } from "@/hooks/useSections"; import { useSearch, type SearchMode } from "@/hooks/useSearch"; import SearchBar from "@/components/SearchBar"; import { EmptyState, ErrorState } from "@/components/EmptyState"; import { Search, SearchX } from "lucide-react"; function SearchContent() { const searchParams = useSearchParams(); const router = useRouter(); const initialQ = searchParams.get("q") ?? ""; const initialMode = (searchParams.get("mode") as SearchMode) ?? "hybrid"; const [query, setQuery] = useState(initialQ); const [mode, setMode] = useState( ["hybrid", "keyword", "semantic"].includes(initialMode) ? initialMode : "hybrid", ); const [selectedSection, setSelectedSection] = useState("all"); const { data: liveSections } = useSections(); const sections = liveSections ?? []; // SWR search — debounced inside hook (180ms) const { hits, error, isValidating, isLoading } = useSearch(query, mode, 30); const loading = isLoading || isValidating; // Keep URL in sync (debounced via hook timing is fine — push without blocking render) useEffect(() => { const q = query.trim(); const target = q ? `/search?q=${encodeURIComponent(q)}&mode=${mode}` : `/search?mode=${mode}`; const current = `${searchParams.toString() ? `/search?${searchParams.toString()}` : "/search"}`; if (target !== current && q) { router.replace(target); } }, [query, mode]); // eslint-disable-line react-hooks/exhaustive-deps const filteredHits = selectedSection === "all" ? hits : hits.filter((r) => r.section === selectedSection); function handleModeChange(newMode: SearchMode) { setMode(newMode); const q = query.trim(); const qs = q ? `?q=${encodeURIComponent(q)}&mode=${newMode}` : `?mode=${newMode}`; router.replace(`/search${qs}`); } const suggestions = [ "stack alignment", "ret2win exploit", "bullmq worker", "streamable http", "full-text search", "caddy reverse proxy", "pgvector cosine", ]; return (

Multi-Modal Search Console

Query across knowledge documents and CTF writeups using PostgreSQL Full-Text Search (tsvector), Cosine Vector Embeddings, or Reciprocal Rank Fusion (RRF).

setQuery("")} />
Algorithm: {[ { id: "hybrid", label: "Hybrid (RRF)", desc: "Reciprocal Rank Fusion (FTS + Vector)" }, { id: "keyword", label: "Keyword (FTS)", desc: "PostgreSQL GIN tsvector" }, { id: "semantic", label: "Semantic Vector", desc: "Cosine Similarity Embeddings" }, ].map((m) => ( ))}
{sections.map((s) => ( ))}
{error && setQuery((q) => q + " ")} />} {loading && (
SEARCHING_VECTOR_SPACE...
)} {!loading && query.trim() && filteredHits.length === 0 && !error && ( )} {!loading && !query.trim() && (

Popular Queries:

{suggestions.map((term) => ( ))}
)} {!loading && filteredHits.length > 0 && (
Found {filteredHits.length} result{filteredHits.length !== 1 ? "s" : ""} Ranked by {mode} score
{filteredHits.map((r) => (
{r.section} /{r.slug}
{r.score.toFixed(3)}

{r.title}

{r.snippet && (

)} ))}

)}
); } export default function SearchPage() { return (

Search

Loading search module...

}> ); }