"use client"; import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Boxes, Check, ChevronRight, Cloud, Cpu, Database, FolderOpen, Network, Plus, Search, Server, Star, Workflow, type LucideIcon, } from "lucide-react"; import { kbDocCount, kbHasLiveProgress, kbNeedsReindex, kbProvider, resolveKbStatus, type KnowledgeBase, } from "@/lib/knowledge-helpers"; import type { RagProviderSummary } from "@/lib/knowledge-api"; interface KnowledgeHomeProps { kbs: KnowledgeBase[]; providers: RagProviderSummary[]; onOpenKb: (name: string) => void; onOpenEngine: (id: string) => void; onCreate: () => void; /** Open the create flow pre-set to link an Obsidian vault. */ onConnectObsidian: () => void; } const ENGINE_ICONS: Record = { llamaindex: Boxes, pageindex: Cloud, graphrag: Network, lightrag: Workflow, "lightrag-server": Server, }; type EngineStatus = "ready" | "needs_key" | "unavailable"; function engineStatus(p: RagProviderSummary): EngineStatus { if (p.requires_api_key && p.configured === false) return "needs_key"; if (p.configured === false) return "unavailable"; return "ready"; } function EngineStatusBadge({ status }: { status: EngineStatus }) { const { t } = useTranslation(); if (status === "ready") { return ( {t("Ready")} ); } if (status === "needs_key") { return ( {t("Needs key")} ); } return ( {t("Not installed")} ); } function StatusDot({ kb }: { kb: KnowledgeBase }) { const status = resolveKbStatus(kb); const needsReindex = kbNeedsReindex(kb); const isLive = kbHasLiveProgress(kb); const tone = needsReindex ? "bg-amber-500" : status === "error" ? "bg-red-500" : isLive ? "bg-sky-500 animate-pulse" : status === "ready" ? "bg-emerald-500" : "bg-[var(--muted-foreground)]"; return ; } export default function KnowledgeHome({ kbs, providers, onOpenKb, onOpenEngine, onCreate, onConnectObsidian, }: KnowledgeHomeProps) { const { t } = useTranslation(); const [query, setQuery] = useState(""); const providerName = (id: string) => providers.find((p) => p.id === id)?.name ?? id.charAt(0).toUpperCase() + id.slice(1); const obsidianCount = useMemo( () => kbs.filter((kb) => kb.metadata?.type === "obsidian").length, [kbs], ); const kbCountByProvider = useMemo(() => { const counts: Record = {}; for (const kb of kbs) counts[kbProvider(kb)] = (counts[kbProvider(kb)] ?? 0) + 1; return counts; }, [kbs]); const filteredKbs = useMemo(() => { const q = query.trim().toLowerCase(); if (!q) return kbs; return kbs.filter((kb) => kb.name.toLowerCase().includes(q)); }, [kbs, query]); return (
{/* Header */}

{t("Knowledge Center")}

{t("Manage your knowledge bases and retrieval engines.")}

{/* Retrieval engines */}

{t("Retrieval engines")}

{providers.map((p) => { const status = engineStatus(p); const Icon = ENGINE_ICONS[p.id] ?? Boxes; const count = kbCountByProvider[p.id] ?? 0; return ( ); })} {/* Obsidian — a connected source, not a config-backed engine: a pointer to a live vault the tutor reads & writes in place. Shown here for discoverability; clicking opens the unified create flow pre-set to link a vault. */}
{/* Knowledge bases */}

{t("Knowledge bases")} {kbs.length}

{kbs.length > 6 && (
setQuery(event.target.value)} placeholder={t("Search knowledge bases…")} className="w-full rounded-lg border border-[var(--border)] bg-[var(--background)] py-1.5 pl-8 pr-3 text-[12px] text-[var(--foreground)] outline-none transition-colors placeholder:text-[var(--muted-foreground)] focus:border-[var(--foreground)]/25" />
)}
{kbs.length === 0 ? (
{t("No knowledge bases yet")}

{t( "Create one to upload documents and retrieve grounded context in chat.", )}

) : filteredKbs.length === 0 ? (
{t("No matches")}
) : (
{filteredKbs.map((kb) => { const docs = kbDocCount(kb); return ( ); })}
)}
); }