import { useActionQuery } from "@agent-native/core/client/hooks"; import { useT } from "@agent-native/core/client/i18n"; import { IconBook2, IconCircleCheck, IconDatabase, IconExternalLink, IconFileText, IconInfoCircle, IconLink, IconQuote, IconRefresh, IconSearch, } from "@tabler/icons-react"; import { useMemo } from "react"; import { Link, useSearchParams } from "react-router"; import { EmptyActionState, LoadingRows, PageHeader, StatusBadge, } from "@/components/brain/Surface"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, } from "@/components/ui/sheet"; import { type BrainProjectsResponse, type KnowledgeRow, type SearchEverythingResponse, type SearchEverythingResult, formatPercent, statusLabel, } from "@/lib/brain"; const typeOptions = ["all", "knowledge", "capture", "source"]; const providerOptions = [ "all", "slack", "granola", "github", "clips", "manual", "generic", ]; const statusOptions = [ "all", "published", "draft", "redacted", "queued", "distilling", "distilled", "ignored", "active", "paused", "archived", "error", ]; const limitOptions = ["10", "25", "50", "100"]; const groupOrder = ["knowledge", "capture", "source"]; export default function SearchRoute() { const t = useT(); const [params, setParams] = useSearchParams(); const query = params.get("q") ?? ""; const type = params.get("type") ?? "all"; const provider = params.get("provider") ?? "all"; const status = params.get("status") ?? "all"; const projectId = params.get("projectId") ?? "all"; const limit = params.get("limit") ?? "25"; const actionParams = useMemo( () => ({ query: query.trim() || undefined, type: type === "all" ? undefined : type, provider: provider === "all" ? undefined : provider, projectId: projectId === "all" ? undefined : projectId, status: status === "all" ? undefined : status, limit: Number.parseInt(limit, 10) || 25, }), [limit, projectId, provider, query, status, type], ); const searchQuery = useActionQuery( "search-everything" as any, actionParams as any, { enabled: Boolean(query.trim()), retry: false }, ); const projectsQuery = useActionQuery( "list-projects" as any, {} as any, ); const results = useMemo( () => filterResults(normalizeResults(searchQuery.data), provider, status), [provider, searchQuery.data, status], ); const hasFilters = Boolean(query.trim()) || type !== "all" || provider !== "all" || projectId !== "all" || status !== "all"; const groupedResults = useMemo(() => groupResults(results), [results]); const resultFacets = useMemo( () => buildResultFacets(results, { type: t("searchPage.type"), source: t("searchPage.source"), status: t("searchPage.status"), }), [results, t], ); const resultCount = results.length; function updateParam(key: string, value: string) { const next = new URLSearchParams(params); if (!value || value === "all" || (key === "limit" && value === "25")) { next.delete(key); } else { next.set(key, value); } setParams(next, { replace: true }); } function clearFilters() { setParams(new URLSearchParams(), { replace: true }); } return (
{searchQuery.isFetching ? t("searchPage.searching") : t("searchPage.results", { count: resultCount })} } />
updateParam("q", event.target.value)} placeholder={t("searchPage.searchPlaceholder")} className="h-11 ps-9 text-base" autoFocus />
{results.length ? ( updateParam(key, value)} /> ) : null}
{searchQuery.isLoading ? ( ) : searchQuery.isError ? ( ) : results.length ? (
{groupedResults.map(([group, groupResults]) => (

{statusLabel(group)}

{groupResults.length}
{groupResults.map((result) => ( ))}
))}
) : ( )}
); } function SearchResultRow({ result, query, }: { result: SearchEverythingResult; query: string; }) { const t = useT(); const body = result.snippet ?? result.summary ?? t("searchPage.noExcerpt"); const whyMatched = explainMatch(result, query); const sourceLabel = result.sourceTitle ?? result.source?.title ?? result.sourceProvider ?? result.provider ?? t("searchPage.companyKnowledge"); const internalHref = internalResultHref(result); const sourceUrl = result.url ?? result.sourceUrl ?? result.citation?.sourceUrl ?? null; const sourceProvider = result.sourceProvider ?? result.provider ?? result.source?.provider ?? null; return (
{result.status ? : null} {sourceProvider ? ( {displayLabel(sourceProvider)} ) : null} {result.matchLane ? ( {result.matchLane} ) : null}

{result.title || t("searchPage.untitledResult")}

{body}

{whyMatched ? (
{result.retrievalReason ?? whyMatched}
) : null}
{typeof result.confidence === "number" ? ( {t("searchPage.confidence")} {formatPercent(result.confidence)} ) : null} {typeof result.score === "number" ? ( {t("searchPage.score")} {formatScore(result.score)} ) : null}
{t("searchPage.citation")}: {sourceLabel} {result.updatedAt ? ( {t("searchPage.updated")} {result.updatedAt} ) : null}
{internalHref ? ( ) : null} {sourceUrl ? ( ) : null}
); } function SearchResultDetails({ result, query, }: { result: SearchEverythingResult; query: string; }) { const t = useT(); const body = result.snippet ?? result.summary ?? t("searchPage.noExcerpt"); const summary = result.summary ?? result.snippet ?? null; const sourceProvider = result.sourceProvider ?? result.provider ?? result.source?.provider ?? null; const sourceTitle = result.sourceTitle ?? result.source?.title ?? result.citation?.captureTitle ?? t("searchPage.companyKnowledge"); const sourceUrl = result.url ?? result.sourceUrl ?? result.citation?.sourceUrl ?? null; const internalHref = internalResultHref(result); const quote = result.citation?.quote ?? null; const whyMatched = explainMatch(result, query); return (
{result.status ? : null} {sourceProvider ? ( {displayLabel(sourceProvider)} ) : null}
{result.title || t("searchPage.untitledResult")} {body}

{t("searchPage.whyMatched")}

{whyMatched ?? t("searchPage.matchedIndex")}

{t("searchPage.summary")}

{summary ?? t("searchPage.noSummary")}

{t("searchPage.citationQuote")}

{quote ? (
{quote}
) : (

{t("searchPage.noCitationQuote")}

)}

{t("searchPage.details")}

{sourceUrl ? ( ) : null} {internalHref ? ( ) : result.source?.id ? ( ) : null}
); } function DetailItem({ label, value, }: { label: string; value?: string | null; }) { const t = useT(); return (
{label}
{value || t("searchPage.notAvailable")}
); } function ResultFacets({ facets, onSelect, }: { facets: ResultFacetGroup[]; onSelect: (key: string, value: string) => void; }) { const t = useT(); const visibleFacets = facets.filter((facet) => facet.items.length); if (!visibleFacets.length) return null; return (
{t("searchPage.inTheseResults")}
{visibleFacets.flatMap((facet) => facet.items.slice(0, 4).map((item) => ( )), )}
); } function ResultTypeBadge({ type }: { type: string }) { return ( {statusLabel(type)} ); } function ResultTypeIcon({ type }: { type: string }) { const normalized = type.toLowerCase(); if (normalized === "source") return ; if (normalized === "capture") return ; return ; } type ResultFacetGroup = { key: "type" | "provider" | "status"; label: string; items: Array<{ value: string; label: string; count: number }>; }; function buildResultFacets( results: SearchEverythingResult[], labels: { type: string; source: string; status: string }, ): ResultFacetGroup[] { return [ { key: "type", label: labels.type, items: countFacet(results, (result) => result.type || "knowledge"), }, { key: "provider", label: labels.source, items: countFacet( results, (result) => result.sourceProvider ?? result.provider ?? result.source?.provider, ), }, { key: "status", label: labels.status, items: countFacet(results, (result) => result.status), }, ]; } function countFacet( results: SearchEverythingResult[], getValue: (result: SearchEverythingResult) => string | null | undefined, ) { const counts = new Map(); for (const result of results) { const value = getValue(result)?.toLowerCase(); if (!value) continue; counts.set(value, (counts.get(value) ?? 0) + 1); } return Array.from(counts.entries()) .map(([value, count]) => ({ value, label: displayLabel(value), count, })) .sort( (left, right) => right.count - left.count || left.label.localeCompare(right.label), ); } function normalizeResults( data: SearchEverythingResponse | undefined, ): SearchEverythingResult[] { const direct = data?.results ?? data?.items ?? data?.rows; if (direct?.length) return direct.map(normalizeResult); if (data?.knowledge?.length) return data.knowledge.map(knowledgeToResult); return []; } function normalizeResult( result: SearchEverythingResult, ): SearchEverythingResult { return { id: result.id, type: result.type ?? "knowledge", title: result.title || "", snippet: result.snippet, summary: result.summary, provider: result.provider, source: result.source, sourceTitle: result.sourceTitle ?? result.source?.title, sourceProvider: result.sourceProvider ?? result.provider ?? result.source?.provider, sourceUrl: result.sourceUrl, citation: result.citation, status: result.status, url: result.url ?? result.sourceUrl ?? result.citation?.sourceUrl, confidence: result.confidence, updatedAt: result.updatedAt, score: result.score, }; } function knowledgeToResult(row: KnowledgeRow): SearchEverythingResult { return { id: row.id, type: "knowledge", title: row.title, snippet: row.summary ?? row.body, summary: row.summary, sourceTitle: row.sourceName ?? row.sourceId, sourceProvider: row.sourceType, status: row.status, confidence: row.confidence, updatedAt: row.updatedAt, }; } function groupResults(results: SearchEverythingResult[]) { const grouped = new Map(); for (const result of results) { const key = result.type || "knowledge"; grouped.set(key, [...(grouped.get(key) ?? []), result]); } return Array.from(grouped.entries()).sort(([left], [right]) => { const leftIndex = groupOrder.indexOf(left); const rightIndex = groupOrder.indexOf(right); if (leftIndex === -1 && rightIndex === -1) return left.localeCompare(right); if (leftIndex === -1) return 1; if (rightIndex === -1) return -1; return leftIndex - rightIndex; }); } function filterResults( results: SearchEverythingResult[], provider: string, status: string, ) { return results.filter((result) => { const resultProvider = result.sourceProvider ?? result.provider ?? result.source?.provider; const matchesProvider = provider === "all" || resultProvider?.toLowerCase() === provider; const matchesStatus = status === "all" || result.status?.toLowerCase() === status; return matchesProvider && matchesStatus; }); } function internalResultHref(result: SearchEverythingResult) { if (result.type === "knowledge") return `/knowledge?knowledgeId=${result.id}`; if (result.type === "source") return `/sources?sourceId=${result.id}`; return null; } function explainMatch(result: SearchEverythingResult, query: string) { const queryTerms = normalizeQueryTerms(query); const matchedFields = [ textMatchesTerms(result.title, queryTerms) ? "title" : null, textMatchesTerms(result.summary, queryTerms) ? "summary" : null, textMatchesTerms(result.snippet, queryTerms) ? "snippet" : null, textMatchesTerms(result.citation?.quote, queryTerms) ? "citation quote" : null, textMatchesTerms(result.sourceTitle ?? result.source?.title, queryTerms) ? "source" : null, textMatchesTerms( `${result.sourceProvider ?? result.provider ?? ""} ${result.status ?? ""}`, queryTerms, ) ? "metadata" : null, ].filter((field): field is string => Boolean(field)); const score = typeof result.score === "number" ? ` with score ${formatScore(result.score)}` : ""; const confidence = typeof result.confidence === "number" ? ` and ${formatPercent(result.confidence)} confidence` : ""; if (matchedFields.length) { return `Matched ${formatList(matchedFields.slice(0, 2))}${score}${confidence}.`; } const provider = result.sourceProvider ?? result.provider ?? result.source?.provider; if (provider || result.status) { return `Matched source metadata${score}.`; } if (score) return `Ranked by search score ${formatScore(result.score ?? 0)}.`; return null; } function normalizeQueryTerms(query: string) { return Array.from( new Set( query .toLowerCase() .split(/[^a-z0-9-]+/) .map((term) => term.trim()) .filter((term) => term.length > 2), ), ).slice(0, 8); } function textMatchesTerms(value: string | null | undefined, terms: string[]) { if (!value || !terms.length) return false; const text = value.toLowerCase(); return terms.some((term) => text.includes(term)); } function formatList(values: string[]) { if (values.length <= 1) return values[0] ?? ""; return `${values.slice(0, -1).join(", ")} and ${values[values.length - 1]}`; } function formatScore(score: number) { if (score <= 1) return score.toFixed(2); return Math.round(score).toString(); } function formatDateTime(value: string | null | undefined) { if (!value) return null; const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short", }).format(date); } function displayLabel(value: string) { const labels: Record = { docs: "Docs", github: "GitHub", drive: "Drive", granola: "Granola", manual: "Manual", generic: "Generic", notion: "Notion", slack: "Slack", clips: "Clips", }; return labels[value] ?? statusLabel(value); }