pi-mega-compact 0.11.6 → 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.
- package/dist/extensions/dashboard-server/routes-memory-map.js +8 -15
- package/dist/extensions/mega-commands.js +65 -5
- package/dist/extensions/mega-config.js +6 -0
- package/dist/extensions/mega-events/context-handler.js +65 -0
- package/dist/src/config/turns.js +2 -1
- package/dist/src/memoryGraph/embedding.js +50 -0
- package/dist/src/memoryGraph/gates.js +379 -0
- package/dist/src/memoryGraph/sources.js +390 -0
- package/dist/src/memoryGraph.js +133 -259
- package/dist/src/recall.js +136 -1
- package/dist/src/store/sqlite/embedding-cache.js +71 -0
- package/dist/src/store/sqlite.js +1 -0
- package/dist/src/topics/cluster.js +10 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js → AchievementTiles-BESF1HT1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js.map → AchievementTiles-BESF1HT1.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js → AchievementsTab-8RzNnz8u.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js.map → AchievementsTab-8RzNnz8u.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-ASwhOMjR.js → CacheTab-CUIiUgh3.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-ASwhOMjR.js.map → CacheTab-CUIiUgh3.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js → ConfigTab-D85fB9HP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js.map → ConfigTab-D85fB9HP.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js → EventsTab-TCLR81G5.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js.map → EventsTab-TCLR81G5.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js → GameTab-BqneNRYH.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js.map → GameTab-BqneNRYH.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-P4SUsvMf.js → MaintenanceTab-LblMg3NB.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-P4SUsvMf.js.map → MaintenanceTab-LblMg3NB.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-CpjMQwRr.js +2 -0
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-CpjMQwRr.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js → MetricsTab-LxNtNzLQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js.map → MetricsTab-LxNtNzLQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js → OverviewTab-BXKUZbT6.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js.map → OverviewTab-BXKUZbT6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js → ReposTab-DTooKeAi.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js.map → ReposTab-DTooKeAi.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js → SessionsTab-Devulgm9.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js.map → SessionsTab-Devulgm9.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js → SetupTab-By9eqUoE.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js.map → SetupTab-By9eqUoE.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js → TimeSavedCard-z4M8TDLm.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js.map → TimeSavedCard-z4M8TDLm.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/TopicsTab-bCGZSfLN.js +2 -0
- package/extensions/dashboard-client/dist/assets/TopicsTab-bCGZSfLN.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js → TurnsTab-m_XzE0H4.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js.map → TurnsTab-m_XzE0H4.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js → index-PRkilPXw.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js.map → index-PRkilPXw.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js → useSSE-CGzgc2IK.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js.map → useSSE-CGzgc2IK.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/memory-map-layout.ts +161 -0
- package/extensions/dashboard-client/src/memory-map-shapes.tsx +126 -0
- package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +201 -165
- package/extensions/dashboard-client/src/tabs/ReposTab.tsx +21 -0
- package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +1 -1
- package/extensions/dashboard-server/api-contracts/index.ts +2 -1
- package/extensions/dashboard-server/api-contracts/memory-map.ts +14 -6
- package/extensions/dashboard-server/routes-memory-map.ts +21 -29
- package/extensions/mega-commands.ts +63 -6
- package/extensions/mega-config.ts +18 -0
- package/extensions/mega-events/context-handler.ts +80 -0
- package/package.json +1 -1
- package/src/config/turns.ts +5 -2
- package/src/memoryGraph/embedding.ts +63 -0
- package/src/memoryGraph/gates.ts +451 -0
- package/src/memoryGraph/sources.ts +484 -0
- package/src/memoryGraph.ts +188 -350
- package/src/recall.ts +168 -4
- package/src/store/sqlite/embedding-cache.ts +81 -0
- package/src/store/sqlite.ts +1 -0
- package/src/topics/cluster.ts +11 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js +0 -2
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js +0 -2
- package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js.map +0 -1
package/src/recall.ts
CHANGED
|
@@ -28,6 +28,21 @@ import { rehydrateRaptorTree, isShadowMode } from "./dedup/raptor/index.js";
|
|
|
28
28
|
import { maxCheckpointTimestamp } from "./store/sqlite.js";
|
|
29
29
|
import { normalizeSessionId } from "./store.js";
|
|
30
30
|
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// S57 RAG suite feature flags + module imports. Each flag defaults OFF — the
|
|
33
|
+
// flag-OFF path below is byte-identical to the pre-S57 recall path (recallQuery
|
|
34
|
+
// === opts.query, the single searchRecall call), so the helpers are only reached
|
|
35
|
+
// when their flag is ON. Wiring is additive; the proven v0.11.5 behavior is the
|
|
36
|
+
// default.
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
import { RAG_QUERY_REFORMULATION, RAG_TIERED_ROUTER, RAG_RECALL_METRICS } from "./config.js";
|
|
39
|
+
import { reformulateQuery, isVagueQuery } from "./queryReformulation.js";
|
|
40
|
+
import type { CorpusStats, EmbedderLike, NeighborScanner } from "./queryReformulation.js";
|
|
41
|
+
import { VAGUE_MIN_WORDS, VAGUE_VERY_SHORT_WORDS } from "./queryReformulation/cache.js";
|
|
42
|
+
import { getTieredRouter } from "./tieredRouter.js";
|
|
43
|
+
import { computeRecallMetrics } from "./recallMetrics.js";
|
|
44
|
+
import { Logger } from "./log.js";
|
|
45
|
+
|
|
31
46
|
export type RecallSource = "resume" | "command" | "sentinel";
|
|
32
47
|
|
|
33
48
|
export interface RecallInjectOptions {
|
|
@@ -133,6 +148,135 @@ export function formatRaptorBlock(
|
|
|
133
148
|
);
|
|
134
149
|
}
|
|
135
150
|
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// S57 RAG suite helpers (B1/B2/B3). Each is only called when its feature flag
|
|
153
|
+
// is ON; flag-OFF never reaches them. All degrade non-fatally to the standard
|
|
154
|
+
// path on any error.
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* B1: If the query is vague, reformulate it via embedding-neighbor TF-IDF
|
|
159
|
+
* expansion. Returns the original query when reformulation is not supported
|
|
160
|
+
* or the query is already specific. Non-fatal: any error → original query.
|
|
161
|
+
*/
|
|
162
|
+
function reformulateRecallQuery(
|
|
163
|
+
query: string,
|
|
164
|
+
store: VectorStore,
|
|
165
|
+
sessionId: string,
|
|
166
|
+
): string {
|
|
167
|
+
try {
|
|
168
|
+
if (
|
|
169
|
+
!isVagueQuery(query, {
|
|
170
|
+
vagueMinWords: VAGUE_MIN_WORDS,
|
|
171
|
+
vagueVeryShortWords: VAGUE_VERY_SHORT_WORDS,
|
|
172
|
+
})
|
|
173
|
+
)
|
|
174
|
+
return query;
|
|
175
|
+
// Pre-compute neighbor candidates once. reformulateQuery embeds the query
|
|
176
|
+
// and calls `scan` internally; we return these pre-computed neighbors (same
|
|
177
|
+
// query) so the store needs no by-embedding lookup.
|
|
178
|
+
const { hits } = searchRecall(
|
|
179
|
+
{ sessionId, query, limit: 10, skipInjected: false },
|
|
180
|
+
store,
|
|
181
|
+
);
|
|
182
|
+
if (hits.length < 2) return query; // too few neighbors for TF-IDF
|
|
183
|
+
const neighbors = hits.map((h) => ({
|
|
184
|
+
id: h.checkpoint.checkpointId,
|
|
185
|
+
score: h.score,
|
|
186
|
+
}));
|
|
187
|
+
const textById = new Map<string, string>();
|
|
188
|
+
for (const h of hits) textById.set(h.checkpoint.checkpointId, h.checkpoint.summary);
|
|
189
|
+
const scan: NeighborScanner = () => neighbors;
|
|
190
|
+
const corpus: CorpusStats = {
|
|
191
|
+
totalDocs: neighbors.length,
|
|
192
|
+
docFreq: (term: string) => {
|
|
193
|
+
let df = 0;
|
|
194
|
+
for (const text of textById.values()) {
|
|
195
|
+
if (text.toLowerCase().includes(term.toLowerCase())) df++;
|
|
196
|
+
}
|
|
197
|
+
return Math.max(df, 1);
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
const neighborTexts = (ids: string[]) =>
|
|
201
|
+
ids.map((id) => ({ id, text: textById.get(id) ?? "" }));
|
|
202
|
+
const { result } = reformulateQuery(
|
|
203
|
+
query,
|
|
204
|
+
defaultEmbedder() as unknown as EmbedderLike,
|
|
205
|
+
scan,
|
|
206
|
+
corpus,
|
|
207
|
+
neighborTexts,
|
|
208
|
+
);
|
|
209
|
+
return result.expanded.length > query.length ? result.expanded : query;
|
|
210
|
+
} catch {
|
|
211
|
+
return query; // non-fatal: fall back to original query
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* B2: Synchronous tiered recall. The TieredRouter.route() is async, so the
|
|
217
|
+
* sync path can only peek the L0 in-memory cache; on miss it falls through to
|
|
218
|
+
* the standard sync vector scan. The router's L1/L2 value is realized on the
|
|
219
|
+
* async path (recallAndInlineAsync). Non-fatal.
|
|
220
|
+
*/
|
|
221
|
+
function runTieredRecall(
|
|
222
|
+
query: string,
|
|
223
|
+
sessionId: string,
|
|
224
|
+
limit: number,
|
|
225
|
+
skip: boolean,
|
|
226
|
+
store: VectorStore,
|
|
227
|
+
): { newHits: SearchHit[]; tier: string } {
|
|
228
|
+
try {
|
|
229
|
+
const router = getTieredRouter();
|
|
230
|
+
if (!router) {
|
|
231
|
+
const result = searchRecall(
|
|
232
|
+
{ sessionId, query, limit, skipInjected: skip },
|
|
233
|
+
store,
|
|
234
|
+
);
|
|
235
|
+
return { newHits: result.newHits, tier: "off" };
|
|
236
|
+
}
|
|
237
|
+
// Peek the L0 in-memory cache synchronously; fall through on miss.
|
|
238
|
+
const cached = router.peekCache(sessionId, query, limit);
|
|
239
|
+
if (cached && cached.length > 0) return { newHits: cached, tier: "L0-cache" };
|
|
240
|
+
const result = searchRecall(
|
|
241
|
+
{ sessionId, query, limit, skipInjected: skip },
|
|
242
|
+
store,
|
|
243
|
+
);
|
|
244
|
+
return { newHits: result.newHits, tier: "sync-fallback" };
|
|
245
|
+
} catch {
|
|
246
|
+
const result = searchRecall(
|
|
247
|
+
{ sessionId, query, limit, skipInjected: skip },
|
|
248
|
+
store,
|
|
249
|
+
);
|
|
250
|
+
return { newHits: result.newHits, tier: "fallback-error" };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* B3: Compute recall-quality metrics on the injected hits and log them.
|
|
256
|
+
* Only called when RAG_RECALL_METRICS() is ON. Non-fatal.
|
|
257
|
+
*/
|
|
258
|
+
function scoreAndLogRecallMetrics(query: string, toInject: SearchHit[]): void {
|
|
259
|
+
try {
|
|
260
|
+
const logger = new Logger();
|
|
261
|
+
const metrics = computeRecallMetrics(query, toInject);
|
|
262
|
+
logger.info("recall_metrics", {
|
|
263
|
+
hitCount: toInject.length,
|
|
264
|
+
score: metrics.score,
|
|
265
|
+
pass: metrics.pass,
|
|
266
|
+
});
|
|
267
|
+
if (!metrics.pass && toInject.length > 0) {
|
|
268
|
+
logger.info("recall_metrics_low_quality", {
|
|
269
|
+
score: metrics.score,
|
|
270
|
+
relevance: metrics.breakdown.relevance,
|
|
271
|
+
coverage: metrics.breakdown.coverage,
|
|
272
|
+
diversity: metrics.breakdown.diversity,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
} catch {
|
|
276
|
+
/* non-fatal: metrics never break recall */
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
136
280
|
/**
|
|
137
281
|
* Run the unified recall+dudupe+prepare-inject pipeline. Does NOT touch pi;
|
|
138
282
|
* it records injections via `markInjected` so the next call dedupes. The
|
|
@@ -189,10 +333,24 @@ export function recallAndInline(
|
|
|
189
333
|
// F4: thread skipInjected through to searchRecall instead of hardcoding false
|
|
190
334
|
// and re-implementing the filter here. newHits is already deduped when skip
|
|
191
335
|
// is true (default); equals hits when skip is false (openclaw command path).
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
336
|
+
//
|
|
337
|
+
// S57 B1: optionally expand a vague query via TF-IDF neighbor terms.
|
|
338
|
+
// S57 B2: optionally route via the TieredRouter's sync L0 cache peek.
|
|
339
|
+
// Both flags default OFF — recallQuery === opts.query and the single
|
|
340
|
+
// searchRecall call reproduce the pre-S57 byte-identical path.
|
|
341
|
+
const recallQuery = RAG_QUERY_REFORMULATION()
|
|
342
|
+
? reformulateRecallQuery(opts.query, store, opts.sessionId)
|
|
343
|
+
: opts.query;
|
|
344
|
+
let newHits: SearchHit[];
|
|
345
|
+
if (RAG_TIERED_ROUTER()) {
|
|
346
|
+
newHits = runTieredRecall(recallQuery, opts.sessionId, limit, skip, store).newHits;
|
|
347
|
+
} else {
|
|
348
|
+
const result = searchRecall(
|
|
349
|
+
{ sessionId: opts.sessionId, query: recallQuery, limit, skipInjected: skip },
|
|
350
|
+
store,
|
|
351
|
+
);
|
|
352
|
+
newHits = result.newHits;
|
|
353
|
+
}
|
|
196
354
|
|
|
197
355
|
// F1: hoist one embedder instance for inline dedupe (matches the async path).
|
|
198
356
|
// defaultEmbedder() is deterministic but creating it per hit wastes allocations.
|
|
@@ -228,6 +386,12 @@ export function recallAndInline(
|
|
|
228
386
|
vectorMarkInjected(store, opts.sessionId, h.checkpoint.checkpointId);
|
|
229
387
|
}
|
|
230
388
|
|
|
389
|
+
// S57 B3: optionally score + log recall quality metrics (flag-OFF: skipped,
|
|
390
|
+
// byte-identical). Scores the injected hits against the ORIGINAL query so the
|
|
391
|
+
// metric measures whether the (possibly expanded) search results stay
|
|
392
|
+
// relevant to what the user actually asked.
|
|
393
|
+
if (RAG_RECALL_METRICS()) scoreAndLogRecallMetrics(opts.query, toInject);
|
|
394
|
+
|
|
231
395
|
// F3: format once — one preamble, correct [1..n] numbering.
|
|
232
396
|
const recallBlock = toInject.length > 0 ? formatRecallBlock(toInject) : "";
|
|
233
397
|
const report = toInject.map(
|
|
@@ -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
|
+
}
|
package/src/store/sqlite.ts
CHANGED
package/src/topics/cluster.ts
CHANGED
|
@@ -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 =
|
|
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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"}
|