"use client"; import React, { useState, useEffect, useCallback, useRef } from "react"; import { fetchGroups, fetchKnowledgeBrowse, Group, Signal, KnowledgeBrowseResponse, KnowledgeDayEntry, KnowledgeTopicSummary, parseMetaList, formatRelativeTime, } from "../lib/api"; // ─── Helpers ────────────────────────────────────────────────────────────────── const LENS_COLOR: Record = { dev: "#00daf3", product: "#A78BFA", client: "#fbbf24", community: "#34d399", meet: "#f87171", jira: "#fb923c", }; const SEVERITY_STYLE: Record = { critical: { color: "#ff6f78", bg: "rgba(255,111,120,0.12)" }, high: { color: "#ffb300", bg: "rgba(255,179,0,0.12)" }, medium: { color: "#A78BFA", bg: "rgba(167,139,250,0.12)" }, low: { color: "#6B7280", bg: "rgba(107,114,128,0.10)" }, }; const TYPE_LABEL: Record = { architecture_decision: "Architecture", tech_debt: "Tech Debt", knowledge_silo_evidence: "Knowledge Silo", recurring_bug: "Bug", stack_decision: "Stack", deployment_risk: "Deploy Risk", workaround: "Workaround", delivery_commitment: "Commitment", feature_request: "Feature", user_pain_point: "Pain Point", roadmap_drift: "Roadmap", priority_conflict: "Conflict", metric_mention: "Metric", user_quote: "Quote", competitor_intel: "Competitor", promise: "Promise", scope_creep: "Scope Creep", sentiment_signal: "Sentiment", unanswered_request: "Unanswered", satisfaction: "Satisfaction", escalation_risk: "Escalation", client_decision: "Client Decision", meet_decision: "Decision", meet_action_item: "Action Item", meet_blocker: "Blocker", meet_risk: "Risk", meet_summary: "Summary", }; function typeLabel(t: string) { return TYPE_LABEL[t] ?? t.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); } function fmtDate(iso: string): string { if (!iso || iso === "unknown") return "Unknown"; const d = new Date(iso + (iso.includes("T") ? "" : "T00:00:00")); const today = new Date(); const yesterday = new Date(today); yesterday.setDate(today.getDate() - 1); const opts: Intl.DateTimeFormatOptions = { month: "short", day: "numeric", year: "numeric" }; if (d.toDateString() === today.toDateString()) return "Today · " + d.toLocaleDateString("en-US", { month: "short", day: "numeric" }); if (d.toDateString() === yesterday.toDateString()) return "Yesterday · " + d.toLocaleDateString("en-US", { month: "short", day: "numeric" }); return d.toLocaleDateString("en-US", opts); } function fmtTime(iso: string): string { if (!iso) return ""; try { return new Date(iso).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: true }); } catch { return ""; } } // ─── Signal Card ───────────────────────────────────────────────────────────── function SignalCard({ signal, expanded, onToggle }: { signal: Signal; expanded: boolean; onToggle: () => void; }) { const meta = signal.metadata; const lens = meta.lens || "dev"; const lensColor = LENS_COLOR[lens] ?? "#A78BFA"; const sev = SEVERITY_STYLE[meta.severity] ?? SEVERITY_STYLE.low; const entities = parseMetaList(meta.entities).slice(0, 3); const keywords = parseMetaList(meta.keywords).slice(0, 3); const summary = meta.summary || signal.document || ""; return ( ); } // ─── Day Section ───────────────────────────────────────────────────────────── function DaySection({ entry, activeTopic }: { entry: KnowledgeDayEntry; activeTopic: string | null }) { const [expandedId, setExpandedId] = useState(null); const visibleSignals = activeTopic ? entry.signals.filter((s) => { try { const kws: string[] = JSON.parse(s.metadata.keywords || "[]"); return kws.map((k) => k.toLowerCase().trim()).includes(activeTopic.toLowerCase()) || s.metadata.type.replace(/_/g, " ").toLowerCase() === activeTopic.toLowerCase(); } catch { return false; } }) : entry.signals; if (visibleSignals.length === 0) return null; return (
{/* Day header */}
{fmtDate(entry.date)}
{visibleSignals.length} signals
{/* Topic chips for this day */} {entry.topics.length > 0 && (
{entry.topics.slice(0, 5).map((t, i) => ( {t} ))}
)} {/* Signal cards */}
{visibleSignals.map((sig) => ( setExpandedId(expandedId === sig.id ? null : sig.id)} /> ))}
); } // ─── Topic Chips ───────────────────────────────────────────────────────────── function TopicChips({ topics, activeTopic, onSelect, }: { topics: KnowledgeTopicSummary[]; activeTopic: string | null; onSelect: (t: string | null) => void; }) { const scrollRef = useRef(null); return (
{topics.map((t) => ( ))}
); } // ─── Main Component ─────────────────────────────────────────────────────────── export default function KnowledgeBrowser() { const [groups, setGroups] = useState([]); const [selectedGroup, setSelectedGroup] = useState(""); const [browseData, setBrowseData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [activeTopic, setActiveTopic] = useState(null); const [dateFrom, setDateFrom] = useState(""); const [dateTo, setDateTo] = useState(""); const timelineRef = useRef(null); // Load groups useEffect(() => { fetchGroups().then((grps) => { setGroups(grps); if (grps.length > 0) setSelectedGroup(grps[0].group_id); }).catch(() => {}); }, []); // Load browse data when group or date filters change const loadBrowse = useCallback(async (groupId: string, from: string, to: string) => { if (!groupId) return; setLoading(true); setError(null); setActiveTopic(null); try { const data = await fetchKnowledgeBrowse(groupId, { dateFrom: from || undefined, dateTo: to || undefined, }); setBrowseData(data); } catch { setError("Backend unavailable — start the ThirdEye server to browse knowledge."); setBrowseData(null); } finally { setLoading(false); } }, []); useEffect(() => { if (selectedGroup) loadBrowse(selectedGroup, dateFrom, dateTo); }, [selectedGroup, loadBrowse]); const handleApplyDates = () => { if (selectedGroup) loadBrowse(selectedGroup, dateFrom, dateTo); }; const handleClearDates = () => { setDateFrom(""); setDateTo(""); if (selectedGroup) loadBrowse(selectedGroup, "", ""); }; const selectedGroupData = groups.find((g) => g.group_id === selectedGroup); // Count visible signals for active topic across all days const visibleCount = browseData ? activeTopic ? browseData.timeline.reduce((acc, day) => { const cnt = day.signals.filter((s) => { try { const kws: string[] = JSON.parse(s.metadata.keywords || "[]"); return kws.map((k) => k.toLowerCase().trim()).includes(activeTopic.toLowerCase()) || s.metadata.type.replace(/_/g, " ").toLowerCase() === activeTopic.toLowerCase(); } catch { return false; } }).length; return acc + cnt; }, 0) : browseData.total_signals : 0; return (
{/* ── Group Selector ──────────────────────────────────────────────── */}

Knowledge Source

{groups.length === 0 ? (

Loading groups…

) : ( )} {selectedGroupData && (

lens: {selectedGroupData.lens || "unknown"}

)}
{/* ── Date Range Filter ───────────────────────────────────────────── */}

Date Range

setDateFrom(e.target.value)} className="flex-1 bg-[#0C0814]/70 border border-[#A78BFA]/15 rounded-lg text-[10px] font-mono text-[#C4B5F4] px-2 py-1.5 focus:outline-none focus:border-[#A78BFA]/40" style={{ colorScheme: "dark" }} /> setDateTo(e.target.value)} className="flex-1 bg-[#0C0814]/70 border border-[#A78BFA]/15 rounded-lg text-[10px] font-mono text-[#C4B5F4] px-2 py-1.5 focus:outline-none focus:border-[#A78BFA]/40" style={{ colorScheme: "dark" }} /> {(dateFrom || dateTo) && ( )}
{/* ── Browser Panel ────────────────────────────────────────────────── */}
{/* Header */}
Knowledge Browser {loading && ( autorenew )}
{browseData && !loading && (

{visibleCount} {activeTopic ? ` signals · topic: "${activeTopic}"` : ` signals · ${browseData.topics.length} topics`}

)}
{/* Topic Chips */} {browseData && browseData.topics.length > 0 && (

Topics

)} {/* Timeline */}
{/* Loading state */} {loading && (
autorenew

Loading knowledge…

)} {/* Error state */} {!loading && error && (
cloud_off

{error}

)} {/* Empty state */} {!loading && !error && browseData && browseData.timeline.length === 0 && (
library_books

No signals found

{dateFrom || dateTo ? "Try adjusting the date range" : "Send messages to populate this group"}

)} {/* Timeline days */} {!loading && !error && browseData && browseData.timeline.map((entry) => ( ))} {/* No signals for active topic */} {!loading && !error && browseData && activeTopic && visibleCount === 0 && (
search_off

No signals for topic "{activeTopic}"

)}
{/* Footer stats */} {browseData && !loading && (

{browseData.total_signals}

Signals

{browseData.topics.length}

Topics

{browseData.timeline.length}

Days

)}
); }