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.
- 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/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/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/dist/src/memoryGraph.js
CHANGED
|
@@ -1,299 +1,173 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* memoryGraph.ts — S46 visual memory map: build a graph of graphs.
|
|
2
|
+
* memoryGraph.ts — S46/D3 visual memory map: build a graph of graphs.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Delegated-shell pattern: implementation lives in memoryGraph/gates.ts,
|
|
5
|
+
* memoryGraph/sources.ts, and memoryGraph/embedding.ts. This file owns
|
|
6
|
+
* the public types, the entry point (buildMemoryGraph), and exports.
|
|
7
7
|
*
|
|
8
8
|
* pi-agnostic (no pi runtime types). Zero network calls (PREVENT-PI-004).
|
|
9
|
-
* Synchronous read-only interface
|
|
9
|
+
* Synchronous read-only interface -- non-fatal on store errors.
|
|
10
10
|
*
|
|
11
11
|
* Re-exports the public types as both MemoryGraph* and shorter aliases
|
|
12
12
|
* (GraphNode, GraphEdge, GraphMetadata) so dashboard api-contracts can import
|
|
13
13
|
* from the canonical source.
|
|
14
14
|
*/
|
|
15
|
-
import { getStateDir
|
|
16
|
-
import { openStore
|
|
17
|
-
import { listRaptorNodes } from "./store/sqlite/raptor.js";
|
|
15
|
+
import { getStateDir } from "./store.js";
|
|
16
|
+
import { openStore } from "./store/sqlite/utils.js";
|
|
18
17
|
import { Logger } from "./log.js";
|
|
18
|
+
import { runValidationPipeline } from "./memoryGraph/gates.js";
|
|
19
|
+
import { buildCheckpointNodes, buildTurnNodes, buildTurnContentNodes, buildMemoryNodes, addRaptorAnnotations, deduplicateEdges, areTurnsEnabled, isTurnContentEnabled, isTurnContentFlaggedOn, areMemoriesEnabled, } from "./memoryGraph/sources.js";
|
|
19
20
|
const log = new Logger();
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
|
-
//
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
const DEFAULT_SIMILARITY_THRESHOLD = 0.7;
|
|
24
|
-
const DEFAULT_MAX_EDGES_PER_NODE = 4;
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// Public API
|
|
22
|
+
// Public entry point
|
|
27
23
|
// ---------------------------------------------------------------------------
|
|
28
24
|
/**
|
|
29
|
-
* Build a memory graph from
|
|
25
|
+
* Build a memory graph from three sources (checkpoints / turns / memories)
|
|
26
|
+
* wired through a 9-gate validation pipeline.
|
|
30
27
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* Returns a MemoryGraph or an empty graph on failure (non-fatal).
|
|
28
|
+
* Returns an empty graph (byte-identical pre-change for checkpoint-only) when
|
|
29
|
+
* no sources are available.
|
|
34
30
|
*/
|
|
35
|
-
export function buildMemoryGraph(
|
|
36
|
-
const {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (includeTemporalEdges) {
|
|
44
|
-
edges.push(...buildTemporalEdges(nodes));
|
|
45
|
-
}
|
|
46
|
-
// Semantic edges: cosine similarity above threshold
|
|
47
|
-
edges.push(...buildSemanticEdges(nodes, nodeIndex, similarityThreshold, maxEdgesPerNode));
|
|
48
|
-
// RAPTOR parent–child edges
|
|
49
|
-
if (includeRaptorEdges) {
|
|
50
|
-
edges.push(...buildRaptorEdges(nodes, sessionId, stateDir, nodeIndex));
|
|
51
|
-
}
|
|
52
|
-
// Deduplicate edges (keep highest weight for each source-target pair)
|
|
53
|
-
const dedupedEdges = deduplicateEdges(edges);
|
|
54
|
-
// Count sessions
|
|
55
|
-
const sessionSet = new Set();
|
|
56
|
-
for (const n of nodes) {
|
|
57
|
-
sessionSet.add(n.sessionId);
|
|
58
|
-
}
|
|
59
|
-
const meta = {
|
|
60
|
-
sessionCount: sessionSet.size,
|
|
61
|
-
totalCheckpoints: nodes.length,
|
|
62
|
-
totalEdges: dedupedEdges.length,
|
|
63
|
-
semanticEdgeCount: dedupedEdges.filter((e) => e.type === "semantic").length,
|
|
64
|
-
temporalEdgeCount: dedupedEdges.filter((e) => e.type === "temporal").length,
|
|
65
|
-
raptorEdgeCount: dedupedEdges.filter((e) => e.type === "raptor_parent").length,
|
|
66
|
-
similarityThresholdUsed: similarityThreshold,
|
|
67
|
-
builtAt,
|
|
68
|
-
};
|
|
69
|
-
return { nodes, edges: dedupedEdges, metadata: meta };
|
|
31
|
+
export function buildMemoryGraph(sessionId, stateDir = getStateDir()) {
|
|
32
|
+
const { nodes, edges } = buildUnvalidatedGraph(sessionId, stateDir);
|
|
33
|
+
// Gate pipeline -- filters, deduplicates, and validates
|
|
34
|
+
const { ws, stats } = runValidationPipeline({ nodes, edges });
|
|
35
|
+
const nodeTypeBreakdown = {};
|
|
36
|
+
const edgeTypeBreakdown = {};
|
|
37
|
+
for (const n of ws.nodes) {
|
|
38
|
+
nodeTypeBreakdown[n.nodeType] = (nodeTypeBreakdown[n.nodeType] ?? 0) + 1;
|
|
70
39
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
nodes: [],
|
|
75
|
-
edges: [],
|
|
76
|
-
metadata: {
|
|
77
|
-
sessionCount: 0,
|
|
78
|
-
totalCheckpoints: 0,
|
|
79
|
-
totalEdges: 0,
|
|
80
|
-
semanticEdgeCount: 0,
|
|
81
|
-
temporalEdgeCount: 0,
|
|
82
|
-
raptorEdgeCount: 0,
|
|
83
|
-
similarityThresholdUsed: similarityThreshold,
|
|
84
|
-
builtAt,
|
|
85
|
-
},
|
|
86
|
-
};
|
|
40
|
+
for (const e of ws.edges) {
|
|
41
|
+
edgeTypeBreakdown[e.type] = (edgeTypeBreakdown[e.type] ?? 0) + 1;
|
|
87
42
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
43
|
+
const avgWeight = ws.edges.length > 0
|
|
44
|
+
? ws.edges.reduce((s, e) => s + e.weight, 0) / ws.edges.length
|
|
45
|
+
: 0;
|
|
46
|
+
const sources = countSourcesByType(ws.nodes);
|
|
47
|
+
const validation = {
|
|
48
|
+
gatesRun: stats.gatesRun.length,
|
|
49
|
+
gatesPassed: stats.gatesPassed.length,
|
|
50
|
+
dropped: stats.dropped,
|
|
51
|
+
warnings: stats.warnings.map((w) => ({
|
|
52
|
+
gate: w.gate,
|
|
53
|
+
code: w.code,
|
|
54
|
+
count: w.count,
|
|
55
|
+
})),
|
|
56
|
+
sources,
|
|
57
|
+
builtAt: Date.now(),
|
|
58
|
+
};
|
|
104
59
|
return {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
? safeJsonCount(row.key_decisions)
|
|
116
|
-
: 0,
|
|
117
|
-
textSnippet: summary.length > 200 ? summary.slice(0, 197) + "..." : summary,
|
|
60
|
+
nodes: ws.nodes,
|
|
61
|
+
edges: ws.edges,
|
|
62
|
+
metadata: {
|
|
63
|
+
totalNodes: ws.nodes.length,
|
|
64
|
+
totalEdges: ws.edges.length,
|
|
65
|
+
avgWeight,
|
|
66
|
+
nodeTypeBreakdown,
|
|
67
|
+
edgeTypeBreakdown,
|
|
68
|
+
},
|
|
69
|
+
validation,
|
|
118
70
|
};
|
|
119
71
|
}
|
|
120
|
-
/** Parse a JSON array stored in a text column and return its length. */
|
|
121
|
-
function safeJsonCount(raw) {
|
|
122
|
-
if (!raw)
|
|
123
|
-
return 0;
|
|
124
|
-
try {
|
|
125
|
-
const arr = JSON.parse(raw);
|
|
126
|
-
return Array.isArray(arr) ? arr.length : 0;
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
return 0;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
72
|
// ---------------------------------------------------------------------------
|
|
133
|
-
//
|
|
73
|
+
// Source orchestrator
|
|
134
74
|
// ---------------------------------------------------------------------------
|
|
135
|
-
function
|
|
136
|
-
const
|
|
137
|
-
nodes.forEach((n, i) => idx.set(n.id, i));
|
|
138
|
-
return idx;
|
|
139
|
-
}
|
|
140
|
-
// ---------------------------------------------------------------------------
|
|
141
|
-
// Edges: temporal
|
|
142
|
-
// ---------------------------------------------------------------------------
|
|
143
|
-
function buildTemporalEdges(nodes) {
|
|
75
|
+
function buildUnvalidatedGraph(sessionId, stateDir) {
|
|
76
|
+
const nodes = [];
|
|
144
77
|
const edges = [];
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
for (let i = 1; i < nodes.length; i++) {
|
|
149
|
-
const cur = nodes[i];
|
|
150
|
-
if (cur.sessionId === prev.sessionId) {
|
|
151
|
-
edges.push({
|
|
152
|
-
source: prev.id,
|
|
153
|
-
target: cur.id,
|
|
154
|
-
weight: 1.0,
|
|
155
|
-
type: "temporal",
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
prev = cur;
|
|
78
|
+
let db;
|
|
79
|
+
try {
|
|
80
|
+
db = openStore(stateDir);
|
|
159
81
|
}
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Edges: semantic (cosine similarity)
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
function buildSemanticEdges(nodes, _nodeIndex, threshold, maxPerNode) {
|
|
166
|
-
if (nodes.length < 2)
|
|
167
|
-
return [];
|
|
168
|
-
const db = openStore();
|
|
169
|
-
// Retrieve embeddings from DB for each node
|
|
170
|
-
const embeddings = new Map();
|
|
171
|
-
const placeholders = nodes.map(() => "?").join(",");
|
|
172
|
-
const ids = nodes.map((n) => n.id);
|
|
173
|
-
const rows = db
|
|
174
|
-
.prepare(`SELECT id, embedding_blob FROM context_chunks WHERE id IN (${placeholders})`)
|
|
175
|
-
.all(...ids);
|
|
176
|
-
for (const row of rows) {
|
|
177
|
-
const emb = decodeEmbedding(row.embedding_blob);
|
|
178
|
-
if (emb.length > 0) {
|
|
179
|
-
embeddings.set(row.id, emb);
|
|
180
|
-
}
|
|
82
|
+
catch {
|
|
83
|
+
log.warn("graph_source_unavailable", { detail: "Cannot open store" });
|
|
84
|
+
return { nodes: [], edges: [] };
|
|
181
85
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
continue;
|
|
196
|
-
const sim = cosineSimilarity(aEmb, bEmb);
|
|
197
|
-
if (sim >= threshold) {
|
|
198
|
-
candidates.push({ target: other.id, weight: sim });
|
|
86
|
+
try {
|
|
87
|
+
// Source: checkpoints (always on)
|
|
88
|
+
buildCheckpointNodes(db, sessionId, nodes, edges);
|
|
89
|
+
// Source A: turns (structural, metadata-only)
|
|
90
|
+
if (areTurnsEnabled()) {
|
|
91
|
+
try {
|
|
92
|
+
buildTurnNodes(db, sessionId, nodes, edges);
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
log.warn("graph_source_unavailable", {
|
|
96
|
+
source: "turns",
|
|
97
|
+
detail: String(err),
|
|
98
|
+
});
|
|
199
99
|
}
|
|
200
100
|
}
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
101
|
+
// Source B: turn content (raw_transcript join, embedding-based)
|
|
102
|
+
if (isTurnContentEnabled()) {
|
|
103
|
+
try {
|
|
104
|
+
buildTurnContentNodes(db, sessionId, nodes, edges);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
log.warn("graph_source_unavailable", {
|
|
108
|
+
source: "turn-content",
|
|
109
|
+
detail: String(err),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
211
112
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// ---------------------------------------------------------------------------
|
|
218
|
-
function buildRaptorEdges(nodes, sessionId, stateDir, nodeIndex) {
|
|
219
|
-
const edges = [];
|
|
220
|
-
try {
|
|
221
|
-
if (sessionId) {
|
|
222
|
-
enrichRaptorForSession(sessionId, stateDir, nodeIndex, nodes, edges);
|
|
113
|
+
else if (isTurnContentFlaggedOn() && !isTurnContentEnabled()) {
|
|
114
|
+
log.warn("graph_source_unavailable", {
|
|
115
|
+
source: "turn-content",
|
|
116
|
+
detail: "dbMirror is OFF -- no raw_transcript available",
|
|
117
|
+
});
|
|
223
118
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
119
|
+
// Source C: memories
|
|
120
|
+
if (areMemoriesEnabled()) {
|
|
121
|
+
try {
|
|
122
|
+
buildMemoryNodes(db, sessionId, nodes, edges);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
log.warn("graph_source_unavailable", {
|
|
126
|
+
source: "memories",
|
|
127
|
+
detail: String(err),
|
|
128
|
+
});
|
|
232
129
|
}
|
|
233
130
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
return edges;
|
|
239
|
-
}
|
|
240
|
-
function enrichRaptorForSession(sessId, stateDir, nodeIndex, nodes, edges) {
|
|
241
|
-
const raptorNodes = listRaptorNodes(sessId, stateDir);
|
|
242
|
-
for (const rn of raptorNodes) {
|
|
243
|
-
const idx = nodeIndex.get(rn.id);
|
|
244
|
-
if (idx !== undefined) {
|
|
245
|
-
// Annotate the graph node with the RAPTOR level
|
|
246
|
-
nodes[idx].raptorLevel = rn.level;
|
|
131
|
+
// RAPTOR annotations on checkpoint nodes
|
|
132
|
+
try {
|
|
133
|
+
addRaptorAnnotations(sessionId, stateDir, nodes, edges);
|
|
247
134
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
// Only create edge if both nodes are in the graph
|
|
251
|
-
if (nodeIndex.has(rn.id) && nodeIndex.has(rn.parentId)) {
|
|
252
|
-
edges.push({
|
|
253
|
-
source: rn.id,
|
|
254
|
-
target: rn.parentId,
|
|
255
|
-
weight: Math.max(0.1, 1.0 - rn.level * 0.1), // higher levels = weaker link
|
|
256
|
-
type: "raptor_parent",
|
|
257
|
-
});
|
|
135
|
+
catch {
|
|
136
|
+
// non-fatal
|
|
258
137
|
}
|
|
259
138
|
}
|
|
260
|
-
|
|
261
|
-
//
|
|
262
|
-
// Edge deduplication
|
|
263
|
-
// ---------------------------------------------------------------------------
|
|
264
|
-
function deduplicateEdges(edges) {
|
|
265
|
-
const seen = new Map();
|
|
266
|
-
for (const e of edges) {
|
|
267
|
-
const keyA = `${e.source}|${e.target}`;
|
|
268
|
-
const keyB = `${e.target}|${e.source}`;
|
|
269
|
-
const existing = seen.get(keyA) ?? seen.get(keyB);
|
|
270
|
-
if (!existing) {
|
|
271
|
-
seen.set(keyA, e);
|
|
272
|
-
}
|
|
273
|
-
else if (e.weight > existing.weight) {
|
|
274
|
-
// Replace with higher-weight edge
|
|
275
|
-
seen.delete(keyA);
|
|
276
|
-
seen.delete(keyB);
|
|
277
|
-
seen.set(keyA, e);
|
|
278
|
-
}
|
|
139
|
+
finally {
|
|
140
|
+
// DatabaseSync has no close in Node 22 -- leave GC to clean up
|
|
279
141
|
}
|
|
280
|
-
|
|
142
|
+
// Pre-gate edge deduplication
|
|
143
|
+
const deduped = deduplicateEdges(edges);
|
|
144
|
+
edges.length = 0;
|
|
145
|
+
edges.push(...deduped);
|
|
146
|
+
return { nodes, edges };
|
|
281
147
|
}
|
|
282
148
|
// ---------------------------------------------------------------------------
|
|
283
|
-
//
|
|
284
|
-
// ---------------------------------------------------------------------------
|
|
285
|
-
function
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
let
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
149
|
+
// Helpers
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
function countSourcesByType(nodes) {
|
|
152
|
+
let checkpoint = 0;
|
|
153
|
+
let turn = 0;
|
|
154
|
+
let turnContent = 0;
|
|
155
|
+
let memory = 0;
|
|
156
|
+
for (const n of nodes) {
|
|
157
|
+
switch (n.nodeType) {
|
|
158
|
+
case "checkpoint":
|
|
159
|
+
checkpoint++;
|
|
160
|
+
break;
|
|
161
|
+
case "turn":
|
|
162
|
+
turn++;
|
|
163
|
+
break;
|
|
164
|
+
case "turn-content":
|
|
165
|
+
turnContent++;
|
|
166
|
+
break;
|
|
167
|
+
case "memory":
|
|
168
|
+
memory++;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
296
171
|
}
|
|
297
|
-
|
|
298
|
-
return denom === 0 ? 0 : dot / denom;
|
|
172
|
+
return { checkpoint, turn, turnContent, memory };
|
|
299
173
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import { defaultEmbedder } from "../../embedder.js";
|
|
12
|
+
import { encodeEmbedding } from "./utils.js";
|
|
13
|
+
/**
|
|
14
|
+
* Decode a Float32 BLOB back to a number[].
|
|
15
|
+
*
|
|
16
|
+
* Mirrors decodeEmbedding from utils.ts but accepts a Buffer explicitly (the
|
|
17
|
+
* type node:sqlite returns for BLOB columns). Also handles Uint8Array (the
|
|
18
|
+
* common node:sqlite runtime type) via DataView.
|
|
19
|
+
*
|
|
20
|
+
* Returns an empty array for null/undefined/zero-length input.
|
|
21
|
+
*/
|
|
22
|
+
export function decodeEmbeddingBlob(buf) {
|
|
23
|
+
if (!buf || buf.byteLength === 0)
|
|
24
|
+
return [];
|
|
25
|
+
const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
26
|
+
const n = buf.byteLength / 4;
|
|
27
|
+
const out = new Array(n);
|
|
28
|
+
for (let i = 0; i < n; i++)
|
|
29
|
+
out[i] = dv.getFloat32(i * 4, true);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Read an embedding for `content` from the cache, or compute and store it.
|
|
34
|
+
*
|
|
35
|
+
* 1. Hash content with SHA-256 → hex digest (matches the content_hash convention).
|
|
36
|
+
* 2. SELECT from embedding_cache — hit → decode blob → return number[].
|
|
37
|
+
* 3. Miss → embed via defaultEmbedder() → encode → INSERT OR REPLACE → return.
|
|
38
|
+
*
|
|
39
|
+
* Non-fatal: any error is caught, logged, and returns [].
|
|
40
|
+
*
|
|
41
|
+
* @param db - Open node:sqlite DatabaseSync (embedding_cache table must exist).
|
|
42
|
+
* @param content - Text to embed (e.g. a context chunk's raw text).
|
|
43
|
+
* @returns The 512‑dim embedding vector, or [] on cache-hit decode failure or
|
|
44
|
+
* any error.
|
|
45
|
+
*/
|
|
46
|
+
export function getOrComputeEmbedding(db, content) {
|
|
47
|
+
try {
|
|
48
|
+
const hash = createHash("sha256").update(content).digest("hex");
|
|
49
|
+
// Parameterized query — PREVENT-002
|
|
50
|
+
const row = db
|
|
51
|
+
.prepare("SELECT embedding FROM embedding_cache WHERE content_hash = ?")
|
|
52
|
+
.get(hash);
|
|
53
|
+
if (row) {
|
|
54
|
+
return decodeEmbeddingBlob(row.embedding);
|
|
55
|
+
}
|
|
56
|
+
// Cache miss: embed, store, return
|
|
57
|
+
const embedder = defaultEmbedder();
|
|
58
|
+
const vec = embedder.embed(content);
|
|
59
|
+
const blob = encodeEmbedding(vec);
|
|
60
|
+
db.prepare("INSERT OR REPLACE INTO embedding_cache (content_hash, embedding, computed_at) VALUES (?, ?, ?)").run(hash, blob, Date.now());
|
|
61
|
+
return vec;
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
// Non-fatal: log and degrade gracefully
|
|
65
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
66
|
+
// Logger not imported here to keep this leaf self-contained; stderr is
|
|
67
|
+
// acceptable for an error path that signals a bug, not a user‑facing log.
|
|
68
|
+
process.stderr.write(`[embedding-cache] getOrComputeEmbedding error: ${msg}\n`);
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
package/dist/src/store/sqlite.js
CHANGED
|
@@ -50,10 +50,18 @@ export function loadEmbeddings(mainDb) {
|
|
|
50
50
|
* Build a topic model from the real stored chunks. Picks k by silhouette when
|
|
51
51
|
* computable, else elbow; degenerate/small corpus → single `general` cluster.
|
|
52
52
|
* Never throws on degenerate input — returns the safe single-cluster model.
|
|
53
|
+
*
|
|
54
|
+
* When `source` is provided, uses those pre-loaded EmbeddedChunks instead of
|
|
55
|
+
* reading from `mainDb` (used by D1 seed path from raw_transcript). Source can
|
|
56
|
+
* be partial — the function still falls back to mainDb for additional chunks
|
|
57
|
+
* when source is non-empty but mainDb also has rows. Default behavior unchanged
|
|
58
|
+
* when source is omitted (backward-compatible).
|
|
53
59
|
*/
|
|
54
|
-
export function buildTopicModel(mainDb, cfg = DEFAULT_CLUSTER_CONFIG) {
|
|
60
|
+
export function buildTopicModel(mainDb, cfg = DEFAULT_CLUSTER_CONFIG, source) {
|
|
55
61
|
const builtAt = Date.now();
|
|
56
|
-
const chunks =
|
|
62
|
+
const chunks = source !== undefined && source.length > 0
|
|
63
|
+
? source
|
|
64
|
+
: loadEmbeddings(mainDb);
|
|
57
65
|
const totalChunks = chunks.length;
|
|
58
66
|
// Too small to cluster meaningfully → single general cluster.
|
|
59
67
|
if (totalChunks < Math.max(2, cfg.kRange[0])) {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as o,j as n}from"./index-
|
|
2
|
-
//# sourceMappingURL=AchievementTiles-
|
|
1
|
+
import{r as o,j as n}from"./index-PRkilPXw.js";function x(s){try{return new Date(s*1e3).toLocaleString()}catch{return"—"}}function f({achievements:s}){const i=o.useRef(0),[r,a]=o.useState(null),l=s.reduce((e,t)=>Math.max(e,t.unlocked_at??0),0),c=i.current;o.useEffect(()=>{l>i.current&&(i.current=l)},[l]),o.useEffect(()=>{if(c>0&&l>c){const e=s.filter(t=>t.unlocked_at!=null&&(t.unlocked_at??0)>c);if(e.length>0){const t=e.map(d=>`${d.icon??""} ${d.title}`.trim()).join(", ")+" unlocked!";a(t);const h=setTimeout(()=>a(null),4e3);return()=>clearTimeout(h)}}},[s,l,c]);const u=s.filter(e=>!(e.hidden===1&&e.unlocked_at==null));return n.jsxs("div",{className:"ach-section",children:[r!=null&&n.jsx("div",{className:"ach-toast show",children:r}),n.jsxs("div",{className:"ach-tiles",children:[u.length===0&&n.jsx("span",{className:"repo-none",children:"no achievements yet"}),u.map(e=>{if(e.unlocked_at!=null){const t=c>0&&(e.unlocked_at??0)>c;return n.jsxs("div",{className:`ach-tile unlocked${t?" just-unlocked":""}`,children:[e.icon??""," ",e.title,n.jsxs("span",{className:"ach-detail",children:["unlocked ",x(e.unlocked_at)]})]},e.id)}return n.jsxs("div",{className:"ach-tile locked",children:["??? ",e.title]},e.id)})]})]})}export{f as A};
|
|
2
|
+
//# sourceMappingURL=AchievementTiles-BESF1HT1.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AchievementTiles-
|
|
1
|
+
{"version":3,"file":"AchievementTiles-BESF1HT1.js","sources":["../../src/components/AchievementTiles.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/AchievementTiles.tsx — shared achievement grid.\n *\n * Renders a grid of achievement tiles from fetchAchievements() data.\n * Tile states: unlocked (icon+title+date), visible-locked (??? teaser),\n * hidden+locked (not rendered). Just-unlocked tiles get a pulse animation.\n * Toast notification fires for newly-unlocked achievements.\n *\n * Used by both GameTab (sub-section) and AchievementsTab (standalone).\n */\n\nimport type React from \"react\";\nimport { useRef, useState, useEffect } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\n\nexport interface AchievementTilesProps {\n\t/** Achievement rows from /api/achievements. */\n\tachievements: AchievementRow[];\n}\n\n/** Format unlock timestamp (seconds → locale string). */\nfunction fmtUnlockDate(ts: number): string {\n\ttry {\n\t\treturn new Date(ts * 1000).toLocaleString();\n\t} catch {\n\t\treturn \"\\u2014\";\n\t}\n}\n\nexport function AchievementTiles({\n\tachievements,\n}: AchievementTilesProps): React.ReactElement {\n\tconst lastMaxTsRef = useRef(0);\n\tconst [toastText, setToastText] = useState<string | null>(null);\n\n\tconst maxTs = achievements.reduce(\n\t\t(mx, a) => Math.max(mx, a.unlocked_at ?? 0),\n\t\t0,\n\t);\n\tconst prevMaxTs = lastMaxTsRef.current;\n\n\t/* Update ref after render so next poll sees previous baseline. */\n\tuseEffect(() => {\n\t\tif (maxTs > lastMaxTsRef.current) {\n\t\t\tlastMaxTsRef.current = maxTs;\n\t\t}\n\t}, [maxTs]);\n\n\t/* Toast for newly-unlocked achievements. */\n\tuseEffect(() => {\n\t\tif (prevMaxTs > 0 && maxTs > prevMaxTs) {\n\t\t\tconst newly = achievements.filter(\n\t\t\t\t(a) => a.unlocked_at != null && (a.unlocked_at ?? 0) > prevMaxTs,\n\t\t\t);\n\t\t\tif (newly.length > 0) {\n\t\t\t\tconst text =\n\t\t\t\t\tnewly\n\t\t\t\t\t\t.map((a) => `${a.icon ?? \"\"} ${a.title}`.trim())\n\t\t\t\t\t\t.join(\", \") + \" unlocked!\";\n\t\t\t\tsetToastText(text);\n\t\t\t\tconst t = setTimeout(() => setToastText(null), 4000);\n\t\t\t\treturn (): void => clearTimeout(t);\n\t\t\t}\n\t\t}\n\t}, [achievements, maxTs, prevMaxTs]);\n\n\t/* Visible tiles: skip hidden+locked (hidden === 1 && unlocked_at == null). */\n\tconst tiles = achievements.filter(\n\t\t(a) => !(a.hidden === 1 && a.unlocked_at == null),\n\t);\n\n\treturn (\n\t\t<div className=\"ach-section\">\n\t\t\t{toastText != null && (\n\t\t\t\t<div className=\"ach-toast show\">{toastText}</div>\n\t\t\t)}\n\t\t\t<div className=\"ach-tiles\">\n\t\t\t\t{tiles.length === 0 && (\n\t\t\t\t\t<span className=\"repo-none\">no achievements yet</span>\n\t\t\t\t)}\n\t\t\t\t{tiles.map((a) => {\n\t\t\t\t\tif (a.unlocked_at != null) {\n\t\t\t\t\t\tconst isNew = prevMaxTs > 0 && (a.unlocked_at ?? 0) > prevMaxTs;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tkey={a.id}\n\t\t\t\t\t\t\t\tclassName={`ach-tile unlocked${isNew ? \" just-unlocked\" : \"\"}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{a.icon ?? \"\"} {a.title}\n\t\t\t\t\t\t\t\t<span className=\"ach-detail\">\n\t\t\t\t\t\t\t\t\tunlocked {fmtUnlockDate(a.unlocked_at)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div key={a.id} className=\"ach-tile locked\">\n\t\t\t\t\t\t\t??? {a.title}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["fmtUnlockDate","ts","AchievementTiles","achievements","lastMaxTsRef","useRef","toastText","setToastText","useState","maxTs","mx","a","prevMaxTs","useEffect","newly","text","t","tiles","jsxs","jsx","isNew"],"mappings":"+CAqBA,SAASA,EAAcC,EAAoB,CAC1C,GAAI,CACH,OAAO,IAAI,KAAKA,EAAK,GAAI,EAAE,eAAA,CAC5B,MAAQ,CACP,MAAO,GACR,CACD,CAEO,SAASC,EAAiB,CAChC,aAAAC,CACD,EAA8C,CAC7C,MAAMC,EAAeC,EAAAA,OAAO,CAAC,EACvB,CAACC,EAAWC,CAAY,EAAIC,EAAAA,SAAwB,IAAI,EAExDC,EAAQN,EAAa,OAC1B,CAACO,EAAIC,IAAM,KAAK,IAAID,EAAIC,EAAE,aAAe,CAAC,EAC1C,CAAA,EAEKC,EAAYR,EAAa,QAG/BS,EAAAA,UAAU,IAAM,CACXJ,EAAQL,EAAa,UACxBA,EAAa,QAAUK,EAEzB,EAAG,CAACA,CAAK,CAAC,EAGVI,EAAAA,UAAU,IAAM,CACf,GAAID,EAAY,GAAKH,EAAQG,EAAW,CACvC,MAAME,EAAQX,EAAa,OACzBQ,GAAMA,EAAE,aAAe,OAASA,EAAE,aAAe,GAAKC,CAAA,EAExD,GAAIE,EAAM,OAAS,EAAG,CACrB,MAAMC,EACLD,EACE,IAAKH,GAAM,GAAGA,EAAE,MAAQ,EAAE,IAAIA,EAAE,KAAK,GAAG,KAAA,CAAM,EAC9C,KAAK,IAAI,EAAI,aAChBJ,EAAaQ,CAAI,EACjB,MAAMC,EAAI,WAAW,IAAMT,EAAa,IAAI,EAAG,GAAI,EACnD,MAAO,IAAY,aAAaS,CAAC,CAClC,CACD,CACD,EAAG,CAACb,EAAcM,EAAOG,CAAS,CAAC,EAGnC,MAAMK,EAAQd,EAAa,OACzBQ,GAAM,EAAEA,EAAE,SAAW,GAAKA,EAAE,aAAe,KAAA,EAG7C,OACCO,EAAAA,KAAC,MAAA,CAAI,UAAU,cACb,SAAA,CAAAZ,GAAa,MACba,EAAAA,IAAC,MAAA,CAAI,UAAU,iBAAkB,SAAAb,EAAU,EAE5CY,EAAAA,KAAC,MAAA,CAAI,UAAU,YACb,SAAA,CAAAD,EAAM,SAAW,GACjBE,MAAC,OAAA,CAAK,UAAU,YAAY,SAAA,sBAAmB,EAE/CF,EAAM,IAAKN,GAAM,CACjB,GAAIA,EAAE,aAAe,KAAM,CAC1B,MAAMS,EAAQR,EAAY,IAAMD,EAAE,aAAe,GAAKC,EACtD,OACCM,EAAAA,KAAC,MAAA,CAEA,UAAW,oBAAoBE,EAAQ,iBAAmB,EAAE,GAE3D,SAAA,CAAAT,EAAE,MAAQ,GAAG,IAAEA,EAAE,MAClBO,EAAAA,KAAC,OAAA,CAAK,UAAU,aAAa,SAAA,CAAA,YAClBlB,EAAcW,EAAE,WAAW,CAAA,CAAA,CACtC,CAAA,CAAA,EANKA,EAAE,EAAA,CASV,CACA,OACCO,EAAAA,KAAC,MAAA,CAAe,UAAU,kBAAkB,SAAA,CAAA,OACtCP,EAAE,KAAA,CAAA,EADEA,EAAE,EAEZ,CAEF,CAAC,CAAA,CAAA,CACF,CAAA,EACD,CAEF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as t,n as m,j as s}from"./index-
|
|
2
|
-
//# sourceMappingURL=AchievementsTab-
|
|
1
|
+
import{r as t,n as m,j as s}from"./index-PRkilPXw.js";import{A as v}from"./AchievementTiles-BESF1HT1.js";function x(){const[r,o]=t.useState([]),[l,u]=t.useState(!0),[a,n]=t.useState(null),c=t.useCallback(async()=>{try{const e=await m();o(e),n(null)}catch(e){n(e instanceof Error?e:new Error(String(e)))}finally{u(!1)}},[]);return t.useEffect(()=>{let e=!0;const i=async()=>{e&&await c()};i();const h=setInterval(()=>void i(),15e3);return()=>{e=!1,clearInterval(h)}},[c]),l&&r.length===0?s.jsx("div",{className:"tab-stub",children:"Loading achievements…"}):a&&r.length===0?s.jsxs("div",{className:"tab-stub",children:["Error: ",a.message]}):s.jsxs("div",{className:"achievements-tab",children:[s.jsx("h2",{children:"Achievements"}),s.jsx(v,{achievements:r})]})}export{x as default};
|
|
2
|
+
//# sourceMappingURL=AchievementsTab-8RzNnz8u.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AchievementsTab-
|
|
1
|
+
{"version":3,"file":"AchievementsTab-8RzNnz8u.js","sources":["../../src/tabs/AchievementsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/AchievementsTab.tsx — Achievements tab (NEW).\n *\n * Renders the shared AchievementTiles component standalone, plus a toast area.\n * Polls /api/achievements every 15s.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\nimport { fetchAchievements } from \"../api/client\";\nimport { AchievementTiles } from \"../components/AchievementTiles\";\n\nexport default function AchievementsTab(): React.ReactElement {\n\tconst [achievements, setAchievements] = useState<AchievementRow[]>([]);\n\tconst [loading, setLoading] = useState(true);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst doFetch = useCallback(async (): Promise<void> => {\n\t\ttry {\n\t\t\tconst rows = await fetchAchievements();\n\t\t\tsetAchievements(rows);\n\t\t\tsetError(null);\n\t\t} catch (e) {\n\t\t\tsetError(e instanceof Error ? e : new Error(String(e)));\n\t\t} finally {\n\t\t\tsetLoading(false);\n\t\t}\n\t}, []);\n\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst run = async (): Promise<void> => {\n\t\t\tif (active) await doFetch();\n\t\t};\n\t\tvoid run();\n\t\tconst timer = setInterval(() => void run(), 15000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, [doFetch]);\n\n\tif (loading && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Loading achievements…</div>;\n\tif (error && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Error: {error.message}</div>;\n\n\treturn (\n\t\t<div className=\"achievements-tab\">\n\t\t\t<h2>Achievements</h2>\n\t\t\t<AchievementTiles achievements={achievements} />\n\t\t</div>\n\t);\n}\n"],"names":["AchievementsTab","achievements","setAchievements","useState","loading","setLoading","error","setError","doFetch","useCallback","rows","fetchAchievements","useEffect","active","run","timer","jsx","jsxs","AchievementTiles"],"mappings":"yGAaA,SAAwBA,GAAsC,CAC7D,KAAM,CAACC,EAAcC,CAAe,EAAIC,EAAAA,SAA2B,CAAA,CAAE,EAC/D,CAACC,EAASC,CAAU,EAAIF,EAAAA,SAAS,EAAI,EACrC,CAACG,EAAOC,CAAQ,EAAIJ,EAAAA,SAAuB,IAAI,EAE/CK,EAAUC,EAAAA,YAAY,SAA2B,CACtD,GAAI,CACH,MAAMC,EAAO,MAAMC,EAAA,EACnBT,EAAgBQ,CAAI,EACpBH,EAAS,IAAI,CACd,OAAS,EAAG,CACXA,EAAS,aAAa,MAAQ,EAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,CACvD,QAAA,CACCF,EAAW,EAAK,CACjB,CACD,EAAG,CAAA,CAAE,EAeL,OAbAO,EAAAA,UAAU,IAAM,CACf,IAAIC,EAAS,GACb,MAAMC,EAAM,SAA2B,CAClCD,SAAcL,EAAA,CACnB,EACKM,EAAA,EACL,MAAMC,EAAQ,YAAY,IAAM,KAAKD,EAAA,EAAO,IAAK,EACjD,MAAO,IAAM,CACZD,EAAS,GACT,cAAcE,CAAK,CACpB,CACD,EAAG,CAACP,CAAO,CAAC,EAERJ,GAAWH,EAAa,SAAW,EAC/Be,EAAAA,IAAC,MAAA,CAAI,UAAU,WAAW,SAAA,wBAAqB,EACnDV,GAASL,EAAa,SAAW,EAC7BgB,EAAAA,KAAC,MAAA,CAAI,UAAU,WAAW,SAAA,CAAA,UAAQX,EAAM,OAAA,EAAQ,EAGvDW,EAAAA,KAAC,MAAA,CAAI,UAAU,mBACd,SAAA,CAAAD,EAAAA,IAAC,MAAG,SAAA,cAAA,CAAY,EAChBA,MAACE,GAAiB,aAAAjB,CAAA,CAA4B,CAAA,EAC/C,CAEF"}
|