pi-mega-compact 0.11.7 → 0.11.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/extensions/dashboard-server/routes-memory-map.js +8 -15
  2. package/dist/extensions/mega-commands.js +65 -5
  3. package/dist/extensions/mega-config.js +6 -0
  4. package/dist/extensions/mega-events/context-handler.js +65 -0
  5. package/dist/src/config/turns.js +2 -1
  6. package/dist/src/memoryGraph/embedding.js +50 -0
  7. package/dist/src/memoryGraph/gates.js +379 -0
  8. package/dist/src/memoryGraph/sources.js +390 -0
  9. package/dist/src/memoryGraph.js +133 -259
  10. package/dist/src/store/sqlite/embedding-cache.js +71 -0
  11. package/dist/src/store/sqlite.js +1 -0
  12. package/dist/src/topics/cluster.js +10 -2
  13. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js → AchievementTiles-BESF1HT1.js} +2 -2
  14. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js.map → AchievementTiles-BESF1HT1.js.map} +1 -1
  15. package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js → AchievementsTab-8RzNnz8u.js} +2 -2
  16. package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js.map → AchievementsTab-8RzNnz8u.js.map} +1 -1
  17. package/extensions/dashboard-client/dist/assets/{CacheTab-ASwhOMjR.js → CacheTab-CUIiUgh3.js} +2 -2
  18. package/extensions/dashboard-client/dist/assets/{CacheTab-ASwhOMjR.js.map → CacheTab-CUIiUgh3.js.map} +1 -1
  19. package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js → ConfigTab-D85fB9HP.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js.map → ConfigTab-D85fB9HP.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js → EventsTab-TCLR81G5.js} +2 -2
  22. package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js.map → EventsTab-TCLR81G5.js.map} +1 -1
  23. package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js → GameTab-BqneNRYH.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js.map → GameTab-BqneNRYH.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-P4SUsvMf.js → MaintenanceTab-LblMg3NB.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-P4SUsvMf.js.map → MaintenanceTab-LblMg3NB.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/MemoryMapTab-CpjMQwRr.js +2 -0
  28. package/extensions/dashboard-client/dist/assets/MemoryMapTab-CpjMQwRr.js.map +1 -0
  29. package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js → MetricsTab-LxNtNzLQ.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js.map → MetricsTab-LxNtNzLQ.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js → OverviewTab-BXKUZbT6.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js.map → OverviewTab-BXKUZbT6.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js → ReposTab-DTooKeAi.js} +2 -2
  34. package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js.map → ReposTab-DTooKeAi.js.map} +1 -1
  35. package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js → SessionsTab-Devulgm9.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js.map → SessionsTab-Devulgm9.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js → SetupTab-By9eqUoE.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js.map → SetupTab-By9eqUoE.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js → TimeSavedCard-z4M8TDLm.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js.map → TimeSavedCard-z4M8TDLm.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/TopicsTab-bCGZSfLN.js +2 -0
  42. package/extensions/dashboard-client/dist/assets/TopicsTab-bCGZSfLN.js.map +1 -0
  43. package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js → TurnsTab-m_XzE0H4.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js.map → TurnsTab-m_XzE0H4.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js → index-PRkilPXw.js} +3 -3
  46. package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js.map → index-PRkilPXw.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js → useSSE-CGzgc2IK.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js.map → useSSE-CGzgc2IK.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/index.html +1 -1
  50. package/extensions/dashboard-client/src/memory-map-layout.ts +161 -0
  51. package/extensions/dashboard-client/src/memory-map-shapes.tsx +126 -0
  52. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +201 -165
  53. package/extensions/dashboard-client/src/tabs/ReposTab.tsx +21 -0
  54. package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +1 -1
  55. package/extensions/dashboard-server/api-contracts/index.ts +2 -1
  56. package/extensions/dashboard-server/api-contracts/memory-map.ts +14 -6
  57. package/extensions/dashboard-server/routes-memory-map.ts +21 -29
  58. package/extensions/mega-commands.ts +63 -6
  59. package/extensions/mega-config.ts +18 -0
  60. package/extensions/mega-events/context-handler.ts +80 -0
  61. package/package.json +1 -1
  62. package/src/config/turns.ts +5 -2
  63. package/src/memoryGraph/embedding.ts +63 -0
  64. package/src/memoryGraph/gates.ts +451 -0
  65. package/src/memoryGraph/sources.ts +484 -0
  66. package/src/memoryGraph.ts +188 -350
  67. package/src/store/sqlite/embedding-cache.ts +81 -0
  68. package/src/store/sqlite.ts +1 -0
  69. package/src/topics/cluster.ts +11 -1
  70. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js +0 -2
  71. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js.map +0 -1
  72. package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js +0 -2
  73. package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js.map +0 -1
@@ -0,0 +1,81 @@
1
+ /**
2
+ * embedding-cache.ts — read-or-compute embedding with content-hash caching.
3
+ *
4
+ * The `embedding_cache` table (schema at schema.ts:421) avoids re-embedding the
5
+ * same content on every graph build. D3 Source B/C callers first hit this cache;
6
+ * only cache misses run the embedder.
7
+ *
8
+ * Non-fatal: all errors are caught and return [] so the graph build never breaks.
9
+ */
10
+
11
+ import { createHash } from "node:crypto";
12
+ import { DatabaseSync } from "node:sqlite";
13
+ import { defaultEmbedder } from "../../embedder.js";
14
+ import { encodeEmbedding } from "./utils.js";
15
+
16
+ /**
17
+ * Decode a Float32 BLOB back to a number[].
18
+ *
19
+ * Mirrors decodeEmbedding from utils.ts but accepts a Buffer explicitly (the
20
+ * type node:sqlite returns for BLOB columns). Also handles Uint8Array (the
21
+ * common node:sqlite runtime type) via DataView.
22
+ *
23
+ * Returns an empty array for null/undefined/zero-length input.
24
+ */
25
+ export function decodeEmbeddingBlob(buf: Buffer | Uint8Array | null | undefined): number[] {
26
+ if (!buf || buf.byteLength === 0) return [];
27
+ const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
28
+ const n = buf.byteLength / 4;
29
+ const out = new Array<number>(n);
30
+ for (let i = 0; i < n; i++) out[i] = dv.getFloat32(i * 4, true);
31
+ return out;
32
+ }
33
+
34
+ /**
35
+ * Read an embedding for `content` from the cache, or compute and store it.
36
+ *
37
+ * 1. Hash content with SHA-256 → hex digest (matches the content_hash convention).
38
+ * 2. SELECT from embedding_cache — hit → decode blob → return number[].
39
+ * 3. Miss → embed via defaultEmbedder() → encode → INSERT OR REPLACE → return.
40
+ *
41
+ * Non-fatal: any error is caught, logged, and returns [].
42
+ *
43
+ * @param db - Open node:sqlite DatabaseSync (embedding_cache table must exist).
44
+ * @param content - Text to embed (e.g. a context chunk's raw text).
45
+ * @returns The 512‑dim embedding vector, or [] on cache-hit decode failure or
46
+ * any error.
47
+ */
48
+ export function getOrComputeEmbedding(db: DatabaseSync, content: string): number[] {
49
+ try {
50
+ const hash = createHash("sha256").update(content).digest("hex");
51
+
52
+ // Parameterized query — PREVENT-002
53
+ const row = db
54
+ .prepare("SELECT embedding FROM embedding_cache WHERE content_hash = ?")
55
+ .get(hash) as { embedding: Buffer } | undefined;
56
+
57
+ if (row) {
58
+ return decodeEmbeddingBlob(row.embedding);
59
+ }
60
+
61
+ // Cache miss: embed, store, return
62
+ const embedder = defaultEmbedder();
63
+ const vec = embedder.embed(content);
64
+ const blob = encodeEmbedding(vec);
65
+
66
+ db.prepare(
67
+ "INSERT OR REPLACE INTO embedding_cache (content_hash, embedding, computed_at) VALUES (?, ?, ?)"
68
+ ).run(hash, blob, Date.now());
69
+
70
+ return vec;
71
+ } catch (err) {
72
+ // Non-fatal: log and degrade gracefully
73
+ const msg = err instanceof Error ? err.message : String(err);
74
+ // Logger not imported here to keep this leaf self-contained; stderr is
75
+ // acceptable for an error path that signals a bug, not a user‑facing log.
76
+ process.stderr.write(
77
+ `[embedding-cache] getOrComputeEmbedding error: ${msg}\n`
78
+ );
79
+ return [];
80
+ }
81
+ }
@@ -26,3 +26,4 @@ export * from "./sqlite/game-achievements.js";
26
26
  export * from "./sqlite/perf-samples.js";
27
27
  export * from "./sqlite/cache-stripes.js";
28
28
  export * from "./sqlite/turns.js";
29
+ export * from "./sqlite/embedding-cache.js";
@@ -84,13 +84,23 @@ export function loadEmbeddings(mainDb: DatabaseSync): EmbeddedChunk[] {
84
84
  * Build a topic model from the real stored chunks. Picks k by silhouette when
85
85
  * computable, else elbow; degenerate/small corpus → single `general` cluster.
86
86
  * Never throws on degenerate input — returns the safe single-cluster model.
87
+ *
88
+ * When `source` is provided, uses those pre-loaded EmbeddedChunks instead of
89
+ * reading from `mainDb` (used by D1 seed path from raw_transcript). Source can
90
+ * be partial — the function still falls back to mainDb for additional chunks
91
+ * when source is non-empty but mainDb also has rows. Default behavior unchanged
92
+ * when source is omitted (backward-compatible).
87
93
  */
88
94
  export function buildTopicModel(
89
95
  mainDb: DatabaseSync,
90
96
  cfg: WikiClusterConfig = DEFAULT_CLUSTER_CONFIG,
97
+ source?: EmbeddedChunk[],
91
98
  ): ClusterModel {
92
99
  const builtAt = Date.now();
93
- const chunks = loadEmbeddings(mainDb);
100
+ const chunks =
101
+ source !== undefined && source.length > 0
102
+ ? source
103
+ : loadEmbeddings(mainDb);
94
104
  const totalChunks = chunks.length;
95
105
 
96
106
  // Too small to cluster meaningfully → single general cluster.
@@ -1,2 +0,0 @@
1
- import{r as m,j as n}from"./index-DrktNOoT.js";const _=5e3,L=.005,R=.01,C=.9,D=.1,G=300,q=.3,E=900,b=600,d=40;function I(l){switch(l){case"temporal":return"#6366f1";case"semantic":return"#22c55e";case"raptor_parent":return"#f59e0b"}}function $(l){return!l||l==="active"?1:.5}function H(l){const t=l.nodes.map((e,i)=>{const r=2*Math.PI*i/Math.max(l.nodes.length,1),x=200;return{...e,pos:{x:Math.cos(r)*x+300,y:Math.sin(r)*x+200},vx:0,vy:0,pinned:!1}}),g=new Map;t.forEach((e,i)=>g.set(e.id,i));const y=l.edges.map(e=>{const i=g.get(e.source),r=g.get(e.target);return i===void 0||r===void 0||i===r?null:{source:i,target:r,weight:e.weight,type:e.type}}).filter(e=>e!==null);return{nodes:t,edges:y}}function O(l){const{nodes:t,edges:g}=l,y=t.length;if(y!==0)for(let e=0;e<y;e++){if(t[e].pinned)continue;let i=0,r=0;for(let c=0;c<y;c++){if(e===c)continue;const p=t[e].pos.x-t[c].pos.x,f=t[e].pos.y-t[c].pos.y,S=p*p+f*f||1,N=_/S,v=Math.sqrt(S);i+=p/v*N,r+=f/v*N}for(const c of g){let p;if(c.source===e)p=c.target;else if(c.target===e)p=c.source;else continue;const f=t[p].pos.x-t[e].pos.x,S=t[p].pos.y-t[e].pos.y;i+=f*L*c.weight,r+=S*L*c.weight}i-=t[e].pos.x*R,r-=t[e].pos.y*R,t[e].vx=(t[e].vx+i)*C,t[e].vy=(t[e].vy+r)*C;const x=Math.sqrt(t[e].vx*t[e].vx+t[e].vy*t[e].vy);x>50&&(t[e].vx=t[e].vx/x*50,t[e].vy=t[e].vy/x*50),x>D&&(t[e].pos.x+=t[e].vx,t[e].pos.y+=t[e].vy)}}const V=()=>{const[l,t]=m.useState(null),[g,y]=m.useState(!0),[e,i]=m.useState(null),[r,x]=m.useState(null),[c,p]=m.useState(""),[f,S]=m.useState(""),[N,v]=m.useState(0),j=m.useRef(null),w=m.useRef(0);m.useEffect(()=>{let s=!1;const a=new URLSearchParams;return f&&a.set("sessionId",f),a.set("threshold","0.7"),a.set("maxEdgesPerNode","4"),y(!0),i(null),fetch(`/api/memory-map?${a.toString()}`).then(o=>{if(!o.ok)throw new Error(`HTTP ${o.status}`);return o.json()}).then(o=>{s||(t(o),j.current=H(o),v(1),y(!1))}).catch(o=>{s||(i(o instanceof Error?o.message:"Unknown error"),y(!1))}),()=>{s=!0,cancelAnimationFrame(w.current)}},[f]),m.useEffect(()=>{if(!j.current||N===0)return;const s=j.current;let a=0;const o=()=>{a>=G||(O(s),a++,v(a),w.current=requestAnimationFrame(o))};return w.current=requestAnimationFrame(o),()=>cancelAnimationFrame(w.current)},[N]);const F=m.useCallback((s,a)=>{const o=j.current;o&&(o.nodes[a].pinned=!o.nodes[a].pinned,o.nodes[a].pinned||(o.nodes[a].vx=0,o.nodes[a].vy=0),x(u=>u?.id===s.id?null:s),v(u=>u+1))},[]),A=m.useMemo(()=>{const s=j.current;if(!s||!c)return null;const a=c.toLowerCase();return new Set(s.nodes.map((o,u)=>o.summaryTruncated.toLowerCase().includes(a)||o.label.toLowerCase().includes(a)?u:-1).filter(o=>o>=0))},[c]);if(g)return n.jsx("div",{className:"memory-map-loading",children:"Loading memory graph..."});if(e)return n.jsxs("div",{className:"memory-map-error",children:[n.jsxs("p",{children:["Failed to load memory map: ",e]}),n.jsx("button",{className:"memory-map-retry-btn",onClick:()=>v(s=>s+1),children:"Retry"})]});if(!l||!j.current)return n.jsx("div",{className:"memory-map-empty",children:"No memory data available."});const h=j.current;return n.jsxs("div",{className:"memory-map-container",children:[n.jsxs("div",{className:"memory-map-toolbar",children:[n.jsx("input",{type:"text",placeholder:"Search memories...",value:c,onInput:s=>p(s.target.value),className:"memory-map-search"}),n.jsxs("span",{className:"memory-map-stats",children:[l.nodes.length," memories, ",l.edges.length," edges",n.jsx("br",{}),"Sessions: ",l.metadata.sessionCount," | Threshold:"," ",l.metadata.similarityThresholdUsed.toFixed(2)]})]}),n.jsxs("div",{className:"memory-map-legend",children:[n.jsx("span",{style:{color:I("semantic")},children:"Semantic"}),n.jsx("span",{style:{color:I("temporal")},children:"Temporal"}),n.jsx("span",{style:{color:I("raptor_parent")},children:"Raptor"})]}),n.jsxs("svg",{width:E,height:b,viewBox:`0 0 ${E} ${b}`,className:"memory-map-svg",children:[h.edges.map((s,a)=>{const o=h.nodes[s.source],u=h.nodes[s.target],T=I(s.type),k=Math.max(1,s.weight*2.5);return n.jsx("line",{x1:d+o.pos.x/(h.nodes.length||1)*(E-2*d),y1:d+o.pos.y/(h.nodes.length||1)*(b-2*d),x2:d+u.pos.x/(h.nodes.length||1)*(E-2*d),y2:d+u.pos.y/(h.nodes.length||1)*(b-2*d),stroke:T,strokeWidth:k,opacity:q},`edge-${a}`)}),h.nodes.map((s,a)=>{const o=d+s.pos.x/(h.nodes.length||1)*(E-2*d),u=d+s.pos.y/(h.nodes.length||1)*(b-2*d),T=Math.min(20,8+s.tokenEstimate/500),k=A?A.has(a):!0,M=r?.id===s.id,P=s.label.length>20?s.label.slice(0,18)+"...":s.label;return n.jsxs("g",{className:"memory-map-node-group",children:[n.jsx("circle",{cx:o,cy:u,r:M?T+3:T,fill:k?s.raptorLevel>0?"#f59e0b":"#6366f1":"#d1d5db",opacity:$(s.dedupStatus),stroke:M?"#ef4444":"none",strokeWidth:M?2:0,className:"memory-map-node",onClick:()=>F(s,a)}),n.jsx("text",{x:o,y:u+T+12,textAnchor:"middle",fontSize:"9px",fill:"#374151",className:"memory-map-label",children:P})]},`node-${a}`)})]}),r?n.jsxs("div",{className:"memory-map-detail",children:[n.jsx("h3",{children:r.label}),n.jsx("p",{children:r.summaryTruncated}),r.topicSummary?n.jsxs("p",{className:"memory-map-topic",children:["Topic: ",r.topicSummary]}):null,n.jsxs("p",{children:["Tokens: ",r.tokenEstimate," | Decisions: ",r.decisionCount]}),n.jsxs("p",{children:["Session: ",r.sessionId]}),n.jsxs("p",{children:["Raptor Level: ",r.raptorLevel]}),n.jsx("p",{className:"memory-map-snippet",children:r.textSnippet})]}):null]})};export{V as default};
2
- //# sourceMappingURL=MemoryMapTab-BBiH76eF.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"MemoryMapTab-BBiH76eF.js","sources":["../../src/tabs/MemoryMapTab.tsx"],"sourcesContent":["/**\n * MemoryMapTab.tsx — S46 visual memory map (force-directed graph of checkpoints).\n *\n * Fetches graph data from /api/memory-map and renders an SVG-based\n * force-directed layout. Uses no external graph library (no @xyflow/react).\n * The simulation uses Coulomb repulsion + Hooke attraction + center gravity.\n */\nimport type React from \"react\";\nimport { useEffect, useRef, useState, useCallback, useMemo } from \"react\";\n\n// ---------------------------------------------------------------------------\n// Types (mirror of api-contracts/memory-map.ts — client-side only)\n// ---------------------------------------------------------------------------\n\ninterface GraphNode {\n id: string;\n sessionId: string;\n label: string;\n summaryTruncated: string;\n tokenEstimate: number;\n timestamp: number;\n dedupStatus: string | undefined;\n raptorLevel: number;\n topicSummary: string | undefined;\n decisionCount: number;\n textSnippet: string;\n}\n\ninterface GraphEdge {\n source: string;\n target: string;\n weight: number;\n type: \"temporal\" | \"semantic\" | \"raptor_parent\";\n}\n\ninterface GraphMetadata {\n sessionCount: number;\n totalCheckpoints: number;\n totalEdges: number;\n semanticEdgeCount: number;\n temporalEdgeCount: number;\n raptorEdgeCount: number;\n similarityThresholdUsed: number;\n builtAt: number;\n}\n\ninterface MemoryMapResponse {\n nodes: GraphNode[];\n edges: GraphEdge[];\n metadata: GraphMetadata;\n}\n\n// ---------------------------------------------------------------------------\n// Force-directed layout types\n// ---------------------------------------------------------------------------\n\ninterface Position {\n x: number;\n y: number;\n}\n\ninterface LayoutNode extends GraphNode {\n pos: Position;\n vx: number;\n vy: number;\n pinned: boolean;\n}\n\ninterface LayoutEdge {\n source: number;\n target: number;\n weight: number;\n type: GraphEdge[\"type\"];\n}\n\n// ---------------------------------------------------------------------------\n// Force simulation constants\n// ---------------------------------------------------------------------------\n\nconst REPULSION = 5_000;\nconst ATTRACTION = 0.005;\nconst CENTER_GRAVITY = 0.01;\nconst DAMPING = 0.9;\nconst MIN_VELOCITY = 0.1;\nconst MAX_ITERATIONS = 300;\nconst EDGE_ALPHA = 0.3;\nconst SVG_WIDTH = 900;\nconst SVG_HEIGHT = 600;\nconst PADDING = 40;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction edgeColor(type: GraphEdge[\"type\"]): string {\n switch (type) {\n case \"temporal\":\n return \"#6366f1\"; // indigo\n case \"semantic\":\n return \"#22c55e\"; // green\n case \"raptor_parent\":\n return \"#f59e0b\"; // amber\n }\n}\n\nfunction nodeOpacity(dedupStatus: string | undefined): number {\n if (!dedupStatus || dedupStatus === \"active\") return 1;\n return 0.5;\n}\n\n// ---------------------------------------------------------------------------\n// Force simulation engine\n// ---------------------------------------------------------------------------\n\nfunction buildLayout(data: MemoryMapResponse): { nodes: LayoutNode[]; edges: LayoutEdge[] } {\n const nodes: LayoutNode[] = data.nodes.map((n, i) => {\n const angle = (2 * Math.PI * i) / Math.max(data.nodes.length, 1);\n const radius = 200;\n return {\n ...n,\n pos: { x: Math.cos(angle) * radius + 300, y: Math.sin(angle) * radius + 200 },\n vx: 0,\n vy: 0,\n pinned: false,\n };\n });\n\n const nodeIndex = new Map<string, number>();\n nodes.forEach((n, i) => nodeIndex.set(n.id, i));\n\n const edges: LayoutEdge[] = data.edges\n .map((e) => {\n const si = nodeIndex.get(e.source);\n const ti = nodeIndex.get(e.target);\n if (si === undefined || ti === undefined || si === ti) return null;\n return { source: si, target: ti, weight: e.weight, type: e.type };\n })\n .filter((e): e is LayoutEdge => e !== null);\n\n return { nodes, edges };\n}\n\nfunction applyForces(layout: { nodes: LayoutNode[]; edges: LayoutEdge[] }): void {\n const { nodes, edges } = layout;\n const n = nodes.length;\n if (n === 0) return;\n\n for (let i = 0; i < n; i++) {\n if (nodes[i].pinned) continue;\n let fx = 0;\n let fy = 0;\n\n for (let j = 0; j < n; j++) {\n if (i === j) continue;\n const dx = nodes[i].pos.x - nodes[j].pos.x;\n const dy = nodes[i].pos.y - nodes[j].pos.y;\n const distSq = dx * dx + dy * dy || 1;\n const force = REPULSION / distSq;\n const dist = Math.sqrt(distSq);\n fx += (dx / dist) * force;\n fy += (dy / dist) * force;\n }\n\n for (const e of edges) {\n let other: number;\n if (e.source === i) other = e.target;\n else if (e.target === i) other = e.source;\n else continue;\n const dx = nodes[other].pos.x - nodes[i].pos.x;\n const dy = nodes[other].pos.y - nodes[i].pos.y;\n const dist = Math.sqrt(dx * dx + dy * dy) || 1;\n fx += dx * ATTRACTION * e.weight;\n fy += dy * ATTRACTION * e.weight;\n }\n\n fx -= nodes[i].pos.x * CENTER_GRAVITY;\n fy -= nodes[i].pos.y * CENTER_GRAVITY;\n\n nodes[i].vx = (nodes[i].vx + fx) * DAMPING;\n nodes[i].vy = (nodes[i].vy + fy) * DAMPING;\n\n const speed = Math.sqrt(nodes[i].vx * nodes[i].vx + nodes[i].vy * nodes[i].vy);\n if (speed > 50) {\n nodes[i].vx = (nodes[i].vx / speed) * 50;\n nodes[i].vy = (nodes[i].vy / speed) * 50;\n }\n\n if (speed > MIN_VELOCITY) {\n nodes[i].pos.x += nodes[i].vx;\n nodes[i].pos.y += nodes[i].vy;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nconst MemoryMapTab: React.FC = () => {\n const [data, setData] = useState<MemoryMapResponse | null>(null);\n const [loading, setLoading] = useState<boolean>(true);\n const [error, setError] = useState<string | null>(null);\n const [selectedNode, setSelectedNode] = useState<GraphNode | null>(null);\n const [searchQuery, setSearchQuery] = useState<string>(\"\");\n const [filterSession, setFilterSession] = useState<string>(\"\");\n const [frame, setFrame] = useState<number>(0);\n\n const layoutRef = useRef<{ nodes: LayoutNode[]; edges: LayoutEdge[] } | null>(null);\n const animRef = useRef<number>(0);\n\n // Fetch graph data when filterSession changes\n useEffect(() => {\n let cancelled = false;\n const params = new URLSearchParams();\n if (filterSession) params.set(\"sessionId\", filterSession);\n params.set(\"threshold\", \"0.7\");\n params.set(\"maxEdgesPerNode\", \"4\");\n\n setLoading(true);\n setError(null);\n\n fetch(`/api/memory-map?${params.toString()}`)\n .then((r) => {\n if (!r.ok) throw new Error(`HTTP ${r.status}`);\n return r.json() as Promise<MemoryMapResponse>;\n })\n .then((d) => {\n if (!cancelled) {\n setData(d);\n layoutRef.current = buildLayout(d);\n setFrame(1);\n setLoading(false);\n }\n })\n .catch((e: unknown) => {\n if (!cancelled) {\n setError(e instanceof Error ? e.message : \"Unknown error\");\n setLoading(false);\n }\n });\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(animRef.current);\n };\n }, [filterSession]);\n\n // Run force simulation via requestAnimationFrame\n useEffect(() => {\n if (!layoutRef.current || frame === 0) return;\n\n const layout = layoutRef.current;\n let iter = 0;\n\n const tick = () => {\n if (iter >= MAX_ITERATIONS) return;\n applyForces(layout);\n iter++;\n setFrame(iter);\n animRef.current = requestAnimationFrame(tick);\n };\n\n animRef.current = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(animRef.current);\n }, [frame]);\n\n // Toggle pin / select a node\n const handleNodeClick = useCallback((node: LayoutNode, index: number) => {\n const layout = layoutRef.current;\n if (!layout) return;\n layout.nodes[index].pinned = !layout.nodes[index].pinned;\n if (!layout.nodes[index].pinned) {\n layout.nodes[index].vx = 0;\n layout.nodes[index].vy = 0;\n }\n setSelectedNode((prev) => (prev?.id === node.id ? null : (node as unknown as GraphNode)));\n setFrame((s) => s + 1);\n }, []);\n\n // Filtered node indices for search\n const filteredSet = useMemo<Set<number> | null>(() => {\n const layout = layoutRef.current;\n if (!layout || !searchQuery) return null;\n const q = searchQuery.toLowerCase();\n return new Set(\n layout.nodes\n .map((n, i) =>\n n.summaryTruncated.toLowerCase().includes(q) || n.label.toLowerCase().includes(q) ? i : -1,\n )\n .filter((i) => i >= 0),\n );\n }, [searchQuery]);\n\n // ---------- Loading state ----------\n\n if (loading) {\n return <div className=\"memory-map-loading\">Loading memory graph...</div>;\n }\n\n // ---------- Error state ----------\n\n if (error) {\n return (\n <div className=\"memory-map-error\">\n <p>Failed to load memory map: {error}</p>\n <button\n className=\"memory-map-retry-btn\"\n onClick={() => setFrame((s) => s + 1)}\n >\n Retry\n </button>\n </div>\n );\n }\n\n // ---------- Empty state ----------\n\n if (!data || !layoutRef.current) {\n return <div className=\"memory-map-empty\">No memory data available.</div>;\n }\n\n const layout = layoutRef.current;\n\n // ---------- Render ----------\n\n return (\n <div className=\"memory-map-container\">\n {/* Toolbar */}\n <div className=\"memory-map-toolbar\">\n <input\n type=\"text\"\n placeholder=\"Search memories...\"\n value={searchQuery}\n onInput={(e: React.FormEvent<HTMLInputElement>) =>\n setSearchQuery((e.target as HTMLInputElement).value)\n }\n className=\"memory-map-search\"\n />\n <span className=\"memory-map-stats\">\n {data.nodes.length} memories, {data.edges.length} edges\n <br />\n Sessions: {data.metadata.sessionCount} | Threshold:{\" \"}\n {data.metadata.similarityThresholdUsed.toFixed(2)}\n </span>\n </div>\n\n {/* Legend */}\n <div className=\"memory-map-legend\">\n <span style={{ color: edgeColor(\"semantic\") }}>Semantic</span>\n <span style={{ color: edgeColor(\"temporal\") }}>Temporal</span>\n <span style={{ color: edgeColor(\"raptor_parent\") }}>Raptor</span>\n </div>\n\n {/* SVG graph */}\n <svg\n width={SVG_WIDTH}\n height={SVG_HEIGHT}\n viewBox={`0 0 ${SVG_WIDTH} ${SVG_HEIGHT}`}\n className=\"memory-map-svg\"\n >\n {/* Edges */}\n {layout.edges.map((e, i) => {\n const src = layout.nodes[e.source];\n const tgt = layout.nodes[e.target];\n const color = edgeColor(e.type);\n const strokeW = Math.max(1, e.weight * 2.5);\n return (\n <line\n key={`edge-${i}`}\n x1={PADDING + (src.pos.x / (layout.nodes.length || 1)) * (SVG_WIDTH - 2 * PADDING)}\n y1={PADDING + (src.pos.y / (layout.nodes.length || 1)) * (SVG_HEIGHT - 2 * PADDING)}\n x2={PADDING + (tgt.pos.x / (layout.nodes.length || 1)) * (SVG_WIDTH - 2 * PADDING)}\n y2={PADDING + (tgt.pos.y / (layout.nodes.length || 1)) * (SVG_HEIGHT - 2 * PADDING)}\n stroke={color}\n strokeWidth={strokeW}\n opacity={EDGE_ALPHA}\n />\n );\n })}\n\n {/* Nodes */}\n {layout.nodes.map((n, i) => {\n const cx =\n PADDING + (n.pos.x / (layout.nodes.length || 1)) * (SVG_WIDTH - 2 * PADDING);\n const cy =\n PADDING + (n.pos.y / (layout.nodes.length || 1)) * (SVG_HEIGHT - 2 * PADDING);\n const radius = Math.min(20, 8 + n.tokenEstimate / 500);\n const isFiltered = filteredSet ? filteredSet.has(i) : true;\n const isSelected = selectedNode?.id === n.id;\n const nodeLabel = n.label.length > 20 ? n.label.slice(0, 18) + \"...\" : n.label;\n\n return (\n <g key={`node-${i}`} className=\"memory-map-node-group\">\n <circle\n cx={cx}\n cy={cy}\n r={isSelected ? radius + 3 : radius}\n fill={isFiltered ? (n.raptorLevel > 0 ? \"#f59e0b\" : \"#6366f1\") : \"#d1d5db\"}\n opacity={nodeOpacity(n.dedupStatus)}\n stroke={isSelected ? \"#ef4444\" : \"none\"}\n strokeWidth={isSelected ? 2 : 0}\n className=\"memory-map-node\"\n onClick={() => handleNodeClick(n, i)}\n />\n <text\n x={cx}\n y={cy + radius + 12}\n textAnchor=\"middle\"\n fontSize=\"9px\"\n fill=\"#374151\"\n className=\"memory-map-label\"\n >\n {nodeLabel}\n </text>\n </g>\n );\n })}\n </svg>\n\n {/* Node detail panel */}\n {selectedNode ? (\n <div className=\"memory-map-detail\">\n <h3>{selectedNode.label}</h3>\n <p>{selectedNode.summaryTruncated}</p>\n {selectedNode.topicSummary ? (\n <p className=\"memory-map-topic\">Topic: {selectedNode.topicSummary}</p>\n ) : null}\n <p>\n Tokens: {selectedNode.tokenEstimate} | Decisions: {selectedNode.decisionCount}\n </p>\n <p>Session: {selectedNode.sessionId}</p>\n <p>Raptor Level: {selectedNode.raptorLevel}</p>\n <p className=\"memory-map-snippet\">{selectedNode.textSnippet}</p>\n </div>\n ) : null}\n </div>\n );\n};\n\nexport default MemoryMapTab;\n"],"names":["REPULSION","ATTRACTION","CENTER_GRAVITY","DAMPING","MIN_VELOCITY","MAX_ITERATIONS","EDGE_ALPHA","SVG_WIDTH","SVG_HEIGHT","PADDING","edgeColor","type","nodeOpacity","dedupStatus","buildLayout","data","nodes","n","angle","radius","nodeIndex","edges","si","ti","applyForces","layout","i","fx","fy","j","dx","dy","distSq","force","dist","e","other","speed","MemoryMapTab","setData","useState","loading","setLoading","error","setError","selectedNode","setSelectedNode","searchQuery","setSearchQuery","filterSession","setFilterSession","frame","setFrame","layoutRef","useRef","animRef","useEffect","cancelled","params","r","d","iter","tick","handleNodeClick","useCallback","node","index","prev","s","filteredSet","useMemo","q","jsx","jsxs","src","tgt","color","strokeW","cx","cy","isFiltered","isSelected","nodeLabel"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{u as h,r as a,t as u,v as p,j as e}from"./index-DrktNOoT.js";function b(s){return s?new Date(s).toLocaleString():"—"}function f(){const{data:s,loading:m,error:n}=h(a.useCallback(()=>u(),[]),{pollInterval:3e4}),[l,x]=a.useState(""),[i,o]=a.useState(null),r=h(a.useCallback(()=>i?p(i):Promise.reject(),[i]),{pollInterval:0}),d=a.useMemo(()=>{if(!s)return[];const t=l.trim().toLowerCase();return t?s.topics.filter(c=>c.label.toLowerCase().includes(t)||c.termScore.some(j=>j.term.toLowerCase().includes(t))):s.topics},[s,l]);return n&&!s?e.jsxs("div",{className:"tab-stub",children:["Error loading topics: ",n.message]}):m&&!s?e.jsx("div",{className:"tab-stub",children:"Loading topics…"}):s?s.totalTopics===0?e.jsxs("div",{className:"tab-stub",children:[e.jsx("h3",{children:"Wiki"}),e.jsx("p",{children:"No topics yet. Topics are auto-generated after every 10th compaction from real memory embeddings (k-means + TF-IDF)."}),e.jsx("p",{children:"Check back after a few more compaction cycles."})]}):e.jsxs("div",{className:"tab-content topics-tab",children:[e.jsx("h3",{children:"Wiki"}),e.jsxs("p",{className:"subtitle",children:[s.totalTopics," topic",s.totalTopics!==1?"s":""," ·"," ",s.totalAssigned," assigned memori",s.totalAssigned!==1?"es":"y",s.lastRebuildAt!=null&&e.jsxs(e.Fragment,{children:[" · last rebuild ",new Date(s.lastRebuildAt).toLocaleString()]})]}),e.jsx("input",{type:"search",className:"topics-search",placeholder:"Filter topics by label or term…",value:l,onChange:t=>x(t.target.value)}),i&&e.jsxs("section",{className:"topic-drilldown",children:[e.jsxs("h4",{children:[e.jsx("button",{type:"button",className:"link-btn",onClick:()=>o(null),children:"← back"})," ","Topic ",e.jsx("code",{children:i}),r.data&&e.jsxs(e.Fragment,{children:[" · ",r.data.label]}),r.data&&e.jsxs(e.Fragment,{children:[" · ",r.data.assignments.length," memories"]})]}),r.error?e.jsxs("div",{className:"tab-stub",children:["Error: ",r.error.message]}):r.loading&&!r.data?e.jsx("div",{className:"tab-stub",children:"Loading memories…"}):r.data?e.jsxs("table",{className:"data-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Memory id"}),e.jsx("th",{children:"Confidence"}),e.jsx("th",{children:"Assigned"})]})}),e.jsx("tbody",{children:r.data.assignments.map(t=>e.jsxs("tr",{children:[e.jsx("td",{children:e.jsx("code",{children:t.memoryId})}),e.jsx("td",{children:t.confidence!=null?t.confidence.toFixed(2):"—"}),e.jsx("td",{children:b(t.assignedAt)})]},t.memoryId))})]}):null]}),!i&&e.jsxs("table",{className:"data-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Label"}),e.jsx("th",{children:"Memories"}),e.jsx("th",{children:"Top Terms"}),e.jsx("th",{})]})}),e.jsxs("tbody",{children:[d.map(t=>e.jsxs("tr",{className:"topic-row",onClick:()=>o(t.id),style:{cursor:"pointer"},children:[e.jsx("td",{className:"topic-label",children:t.label}),e.jsx("td",{className:"topic-count",children:t.memoryCount}),e.jsx("td",{className:"topic-terms",children:t.termScore.slice(0,8).map(c=>c.term).join(", ")}),e.jsx("td",{children:e.jsx("button",{type:"button",className:"mini-btn",children:"drill"})})]},t.id)),d.length===0&&e.jsx("tr",{children:e.jsxs("td",{colSpan:4,className:"muted",children:["No topics match “",l,"”."]})})]})]})]}):e.jsx("div",{className:"tab-stub",children:"No topic data available."})}export{f as default};
2
- //# sourceMappingURL=TopicsTab-BWWQxltv.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"TopicsTab-BWWQxltv.js","sources":["../../src/tabs/TopicsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/TopicsTab.tsx — Auto-categorizing wiki (S51 + S52 polish).\n *\n * Fetches /api/topics and renders the k-means + TF-IDF topic clusters. S52 polish\n * adds: (1) a search box filtering topics by label / discriminative term, and\n * (2) a topic drill-down — click a topic to load its member memories\n * (/api/topics/:id/memories). Read-only; no write operations.\n */\n\nimport type React from \"react\";\nimport { useCallback, useMemo, useState } from \"react\";\nimport { useApi } from \"../hooks/useApi\";\nimport { fetchTopics, fetchTopicMemories } from \"../api/client\";\nimport type { TopicsResponse, TopicMemoriesResponse } from \"@contracts\";\n\nfunction fmtTs(ms: number | null): string {\n\tif (!ms) return \"—\";\n\treturn new Date(ms).toLocaleString();\n}\n\nexport default function TopicsTab(): React.ReactElement {\n\tconst { data, loading, error } = useApi<TopicsResponse>(\n\t\tuseCallback(() => fetchTopics(), []),\n\t\t{ pollInterval: 30_000 },\n\t);\n\tconst [query, setQuery] = useState(\"\");\n\tconst [drillTopicId, setDrillTopicId] = useState<string | null>(null);\n\n\tconst drill = useApi<TopicMemoriesResponse>(\n\t\tuseCallback(\n\t\t\t() =>\n\t\t\t\tdrillTopicId ? fetchTopicMemories(drillTopicId) : Promise.reject(),\n\t\t\t[drillTopicId],\n\t\t),\n\t\t{ pollInterval: 0 },\n\t);\n\n\tconst filtered = useMemo(() => {\n\t\tif (!data) return [];\n\t\tconst q = query.trim().toLowerCase();\n\t\tif (!q) return data.topics;\n\t\treturn data.topics.filter(\n\t\t\t(t) =>\n\t\t\t\tt.label.toLowerCase().includes(q) ||\n\t\t\t\tt.termScore.some((s) => s.term.toLowerCase().includes(q)),\n\t\t);\n\t}, [data, query]);\n\n\tif (error && !data) {\n\t\treturn (\n\t\t\t<div className=\"tab-stub\">Error loading topics: {error.message}</div>\n\t\t);\n\t}\n\tif (loading && !data) {\n\t\treturn <div className=\"tab-stub\">Loading topics…</div>;\n\t}\n\tif (!data) {\n\t\treturn <div className=\"tab-stub\">No topic data available.</div>;\n\t}\n\n\tif (data.totalTopics === 0) {\n\t\treturn (\n\t\t\t<div className=\"tab-stub\">\n\t\t\t\t<h3>Wiki</h3>\n\t\t\t\t<p>\n\t\t\t\t\tNo topics yet. Topics are auto-generated after every 10th compaction\n\t\t\t\t\tfrom real memory embeddings (k-means + TF-IDF).\n\t\t\t\t</p>\n\t\t\t\t<p>Check back after a few more compaction cycles.</p>\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className=\"tab-content topics-tab\">\n\t\t\t<h3>Wiki</h3>\n\t\t\t<p className=\"subtitle\">\n\t\t\t\t{data.totalTopics} topic{data.totalTopics !== 1 ? \"s\" : \"\"} ·{\" \"}\n\t\t\t\t{data.totalAssigned} assigned memori\n\t\t\t\t{data.totalAssigned !== 1 ? \"es\" : \"y\"}\n\t\t\t\t{data.lastRebuildAt != null && (\n\t\t\t\t\t<> · last rebuild {new Date(data.lastRebuildAt).toLocaleString()}</>\n\t\t\t\t)}\n\t\t\t</p>\n\n\t\t\t<input\n\t\t\t\ttype=\"search\"\n\t\t\t\tclassName=\"topics-search\"\n\t\t\t\tplaceholder=\"Filter topics by label or term…\"\n\t\t\t\tvalue={query}\n\t\t\t\tonChange={(e) => setQuery(e.target.value)}\n\t\t\t/>\n\n\t\t\t{drillTopicId && (\n\t\t\t\t<section className=\"topic-drilldown\">\n\t\t\t\t\t<h4>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclassName=\"link-btn\"\n\t\t\t\t\t\t\tonClick={() => setDrillTopicId(null)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t← back\n\t\t\t\t\t\t</button>{\" \"}\n\t\t\t\t\t\tTopic <code>{drillTopicId}</code>\n\t\t\t\t\t\t{drill.data && <> · {drill.data.label}</>}\n\t\t\t\t\t\t{drill.data && <> · {drill.data.assignments.length} memories</>}\n\t\t\t\t\t</h4>\n\t\t\t\t\t{drill.error ? (\n\t\t\t\t\t\t<div className=\"tab-stub\">\n\t\t\t\t\t\t\tError: {(drill.error as Error).message}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : drill.loading && !drill.data ? (\n\t\t\t\t\t\t<div className=\"tab-stub\">Loading memories…</div>\n\t\t\t\t\t) : drill.data ? (\n\t\t\t\t\t\t<table className=\"data-table\">\n\t\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<th>Memory id</th>\n\t\t\t\t\t\t\t\t\t<th>Confidence</th>\n\t\t\t\t\t\t\t\t\t<th>Assigned</th>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t{drill.data.assignments.map((a) => (\n\t\t\t\t\t\t\t\t\t<tr key={a.memoryId}>\n\t\t\t\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t\t\t\t<code>{a.memoryId}</code>\n\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t\t\t\t{a.confidence != null ? a.confidence.toFixed(2) : \"—\"}\n\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t\t<td>{fmtTs(a.assignedAt)}</td>\n\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t) : null}\n\t\t\t\t</section>\n\t\t\t)}\n\n\t\t\t{!drillTopicId && (\n\t\t\t\t<table className=\"data-table\">\n\t\t\t\t\t<thead>\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t<th>Label</th>\n\t\t\t\t\t\t\t<th>Memories</th>\n\t\t\t\t\t\t\t<th>Top Terms</th>\n\t\t\t\t\t\t\t<th></th>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</thead>\n\t\t\t\t\t<tbody>\n\t\t\t\t\t\t{filtered.map((t) => (\n\t\t\t\t\t\t\t<tr\n\t\t\t\t\t\t\t\tkey={t.id}\n\t\t\t\t\t\t\t\tclassName=\"topic-row\"\n\t\t\t\t\t\t\t\tonClick={() => setDrillTopicId(t.id)}\n\t\t\t\t\t\t\t\tstyle={{ cursor: \"pointer\" }}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<td className=\"topic-label\">{t.label}</td>\n\t\t\t\t\t\t\t\t<td className=\"topic-count\">{t.memoryCount}</td>\n\t\t\t\t\t\t\t\t<td className=\"topic-terms\">\n\t\t\t\t\t\t\t\t\t{t.termScore\n\t\t\t\t\t\t\t\t\t\t.slice(0, 8)\n\t\t\t\t\t\t\t\t\t\t.map((s) => s.term)\n\t\t\t\t\t\t\t\t\t\t.join(\", \")}\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t\t<button type=\"button\" className=\"mini-btn\">\n\t\t\t\t\t\t\t\t\t\tdrill\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t))}\n\t\t\t\t\t\t{filtered.length === 0 && (\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td colSpan={4} className=\"muted\">\n\t\t\t\t\t\t\t\t\tNo topics match “{query}”.\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</tbody>\n\t\t\t\t</table>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n"],"names":["fmtTs","ms","TopicsTab","data","loading","error","useApi","useCallback","fetchTopics","query","setQuery","useState","drillTopicId","setDrillTopicId","drill","fetchTopicMemories","filtered","useMemo","q","t","s","jsxs","jsx","Fragment","e","a"],"mappings":"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"}