claude-memory-layer 1.0.6 → 1.0.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/.claude/settings.local.json +4 -1
- package/.claude-plugin/plugin.json +3 -3
- package/.history/package_20260201142928.json +46 -0
- package/.history/package_20260201192048.json +47 -0
- package/README.md +26 -26
- package/dist/.claude-plugin/plugin.json +3 -3
- package/dist/cli/index.js +1109 -25
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +192 -5
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/session-end.js +262 -18
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +262 -18
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +262 -18
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +262 -18
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +4728 -0
- package/dist/server/api/index.js.map +7 -0
- package/dist/server/index.js +4790 -0
- package/dist/server/index.js.map +7 -0
- package/dist/services/memory-service.js +269 -18
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/index.html +1225 -0
- package/package.json +4 -2
- package/scripts/build.ts +33 -3
- package/src/cli/index.ts +311 -6
- package/src/core/db-wrapper.ts +8 -1
- package/src/core/event-store.ts +52 -3
- package/src/core/graduation-worker.ts +171 -0
- package/src/core/graduation.ts +15 -2
- package/src/core/index.ts +1 -0
- package/src/core/retriever.ts +18 -0
- package/src/core/types.ts +1 -1
- package/src/mcp/index.ts +2 -2
- package/src/mcp/tools.ts +1 -1
- package/src/server/api/citations.ts +7 -3
- package/src/server/api/events.ts +7 -3
- package/src/server/api/search.ts +7 -3
- package/src/server/api/sessions.ts +7 -3
- package/src/server/api/stats.ts +175 -5
- package/src/server/index.ts +18 -9
- package/src/services/memory-service.ts +107 -19
- package/src/ui/index.html +1225 -0
|
@@ -0,0 +1,1225 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Code Memory Dashboard</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg-primary: #1a1a2e;
|
|
10
|
+
--bg-secondary: #16213e;
|
|
11
|
+
--bg-card: #0f3460;
|
|
12
|
+
--text-primary: #e6e6e6;
|
|
13
|
+
--text-secondary: #a0a0a0;
|
|
14
|
+
--accent: #e94560;
|
|
15
|
+
--accent-secondary: #533483;
|
|
16
|
+
--success: #4ade80;
|
|
17
|
+
--warning: #fbbf24;
|
|
18
|
+
--border: rgba(255, 255, 255, 0.1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
* {
|
|
22
|
+
margin: 0;
|
|
23
|
+
padding: 0;
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
body {
|
|
28
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
29
|
+
background: var(--bg-primary);
|
|
30
|
+
color: var(--text-primary);
|
|
31
|
+
min-height: 100vh;
|
|
32
|
+
line-height: 1.6;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.container {
|
|
36
|
+
max-width: 1400px;
|
|
37
|
+
margin: 0 auto;
|
|
38
|
+
padding: 20px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
header {
|
|
42
|
+
display: flex;
|
|
43
|
+
justify-content: space-between;
|
|
44
|
+
align-items: center;
|
|
45
|
+
padding: 20px 0;
|
|
46
|
+
border-bottom: 1px solid var(--border);
|
|
47
|
+
margin-bottom: 30px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.logo {
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
gap: 12px;
|
|
54
|
+
font-size: 1.5rem;
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.logo-icon {
|
|
59
|
+
font-size: 2rem;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.refresh-btn {
|
|
63
|
+
background: var(--bg-card);
|
|
64
|
+
border: 1px solid var(--border);
|
|
65
|
+
color: var(--text-primary);
|
|
66
|
+
padding: 10px 20px;
|
|
67
|
+
border-radius: 8px;
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
gap: 8px;
|
|
72
|
+
transition: all 0.2s;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.refresh-btn:hover {
|
|
76
|
+
background: var(--accent);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.refresh-btn.loading {
|
|
80
|
+
opacity: 0.6;
|
|
81
|
+
pointer-events: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.stats-grid {
|
|
85
|
+
display: grid;
|
|
86
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
87
|
+
gap: 20px;
|
|
88
|
+
margin-bottom: 30px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.stat-card {
|
|
92
|
+
background: var(--bg-card);
|
|
93
|
+
border-radius: 12px;
|
|
94
|
+
padding: 24px;
|
|
95
|
+
text-align: center;
|
|
96
|
+
border: 1px solid var(--border);
|
|
97
|
+
transition: transform 0.2s;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.stat-card:hover {
|
|
101
|
+
transform: translateY(-2px);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.stat-value {
|
|
105
|
+
font-size: 2.5rem;
|
|
106
|
+
font-weight: 700;
|
|
107
|
+
color: var(--accent);
|
|
108
|
+
margin-bottom: 8px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.stat-label {
|
|
112
|
+
color: var(--text-secondary);
|
|
113
|
+
font-size: 0.9rem;
|
|
114
|
+
text-transform: uppercase;
|
|
115
|
+
letter-spacing: 1px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.search-container {
|
|
119
|
+
margin-bottom: 30px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.search-input {
|
|
123
|
+
width: 100%;
|
|
124
|
+
padding: 16px 20px;
|
|
125
|
+
background: var(--bg-secondary);
|
|
126
|
+
border: 1px solid var(--border);
|
|
127
|
+
border-radius: 12px;
|
|
128
|
+
color: var(--text-primary);
|
|
129
|
+
font-size: 1rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.search-input::placeholder {
|
|
133
|
+
color: var(--text-secondary);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.search-input:focus {
|
|
137
|
+
outline: none;
|
|
138
|
+
border-color: var(--accent);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.main-grid {
|
|
142
|
+
display: grid;
|
|
143
|
+
grid-template-columns: 1fr 1fr;
|
|
144
|
+
gap: 30px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@media (max-width: 900px) {
|
|
148
|
+
.main-grid {
|
|
149
|
+
grid-template-columns: 1fr;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.section {
|
|
154
|
+
background: var(--bg-secondary);
|
|
155
|
+
border-radius: 12px;
|
|
156
|
+
padding: 24px;
|
|
157
|
+
border: 1px solid var(--border);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.section-title {
|
|
161
|
+
font-size: 1.2rem;
|
|
162
|
+
margin-bottom: 20px;
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
gap: 10px;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.session-list {
|
|
169
|
+
list-style: none;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.session-item {
|
|
173
|
+
padding: 16px;
|
|
174
|
+
background: var(--bg-card);
|
|
175
|
+
border-radius: 8px;
|
|
176
|
+
margin-bottom: 12px;
|
|
177
|
+
cursor: pointer;
|
|
178
|
+
transition: all 0.2s;
|
|
179
|
+
border: 1px solid transparent;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.session-item:hover {
|
|
183
|
+
border-color: var(--accent);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.session-header {
|
|
187
|
+
display: flex;
|
|
188
|
+
justify-content: space-between;
|
|
189
|
+
align-items: center;
|
|
190
|
+
margin-bottom: 8px;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.session-id {
|
|
194
|
+
font-family: monospace;
|
|
195
|
+
font-size: 0.9rem;
|
|
196
|
+
color: var(--accent);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.session-time {
|
|
200
|
+
color: var(--text-secondary);
|
|
201
|
+
font-size: 0.85rem;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.session-meta {
|
|
205
|
+
display: flex;
|
|
206
|
+
gap: 16px;
|
|
207
|
+
color: var(--text-secondary);
|
|
208
|
+
font-size: 0.85rem;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.shared-item {
|
|
212
|
+
padding: 16px;
|
|
213
|
+
background: var(--bg-card);
|
|
214
|
+
border-radius: 8px;
|
|
215
|
+
margin-bottom: 12px;
|
|
216
|
+
display: flex;
|
|
217
|
+
justify-content: space-between;
|
|
218
|
+
align-items: center;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.shared-type {
|
|
222
|
+
display: flex;
|
|
223
|
+
align-items: center;
|
|
224
|
+
gap: 10px;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.shared-icon {
|
|
228
|
+
font-size: 1.5rem;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.shared-count {
|
|
232
|
+
background: var(--accent);
|
|
233
|
+
padding: 4px 12px;
|
|
234
|
+
border-radius: 20px;
|
|
235
|
+
font-weight: 600;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.timeline-container {
|
|
239
|
+
margin-top: 30px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.timeline-bar {
|
|
243
|
+
display: flex;
|
|
244
|
+
gap: 4px;
|
|
245
|
+
height: 60px;
|
|
246
|
+
align-items: flex-end;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.timeline-day {
|
|
250
|
+
flex: 1;
|
|
251
|
+
background: var(--accent);
|
|
252
|
+
border-radius: 4px 4px 0 0;
|
|
253
|
+
min-height: 4px;
|
|
254
|
+
transition: opacity 0.2s;
|
|
255
|
+
position: relative;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.timeline-day:hover {
|
|
259
|
+
opacity: 0.8;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.timeline-day:hover::after {
|
|
263
|
+
content: attr(data-tooltip);
|
|
264
|
+
position: absolute;
|
|
265
|
+
bottom: 100%;
|
|
266
|
+
left: 50%;
|
|
267
|
+
transform: translateX(-50%);
|
|
268
|
+
background: var(--bg-card);
|
|
269
|
+
padding: 8px 12px;
|
|
270
|
+
border-radius: 6px;
|
|
271
|
+
font-size: 0.8rem;
|
|
272
|
+
white-space: nowrap;
|
|
273
|
+
z-index: 10;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.timeline-labels {
|
|
277
|
+
display: flex;
|
|
278
|
+
justify-content: space-between;
|
|
279
|
+
margin-top: 8px;
|
|
280
|
+
color: var(--text-secondary);
|
|
281
|
+
font-size: 0.8rem;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.endless-status {
|
|
285
|
+
display: flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
gap: 12px;
|
|
288
|
+
padding: 16px;
|
|
289
|
+
background: var(--bg-card);
|
|
290
|
+
border-radius: 8px;
|
|
291
|
+
margin-bottom: 16px;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.status-indicator {
|
|
295
|
+
width: 12px;
|
|
296
|
+
height: 12px;
|
|
297
|
+
border-radius: 50%;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.status-indicator.active {
|
|
301
|
+
background: var(--success);
|
|
302
|
+
box-shadow: 0 0 10px var(--success);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.status-indicator.inactive {
|
|
306
|
+
background: var(--text-secondary);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.progress-bar {
|
|
310
|
+
height: 8px;
|
|
311
|
+
background: var(--bg-primary);
|
|
312
|
+
border-radius: 4px;
|
|
313
|
+
overflow: hidden;
|
|
314
|
+
margin-top: 8px;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.progress-fill {
|
|
318
|
+
height: 100%;
|
|
319
|
+
background: linear-gradient(90deg, var(--accent), var(--accent-secondary));
|
|
320
|
+
border-radius: 4px;
|
|
321
|
+
transition: width 0.3s;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.loading-spinner {
|
|
325
|
+
text-align: center;
|
|
326
|
+
padding: 40px;
|
|
327
|
+
color: var(--text-secondary);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.error-message {
|
|
331
|
+
background: rgba(233, 69, 96, 0.2);
|
|
332
|
+
border: 1px solid var(--accent);
|
|
333
|
+
padding: 16px;
|
|
334
|
+
border-radius: 8px;
|
|
335
|
+
color: var(--accent);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.empty-state {
|
|
339
|
+
text-align: center;
|
|
340
|
+
padding: 40px;
|
|
341
|
+
color: var(--text-secondary);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.badge {
|
|
345
|
+
display: inline-block;
|
|
346
|
+
padding: 2px 8px;
|
|
347
|
+
border-radius: 4px;
|
|
348
|
+
font-size: 0.75rem;
|
|
349
|
+
font-weight: 600;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.badge-success {
|
|
353
|
+
background: rgba(74, 222, 128, 0.2);
|
|
354
|
+
color: var(--success);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.badge-warning {
|
|
358
|
+
background: rgba(251, 191, 36, 0.2);
|
|
359
|
+
color: var(--warning);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/* Memory Level Navigation */
|
|
363
|
+
.level-nav {
|
|
364
|
+
display: flex;
|
|
365
|
+
gap: 8px;
|
|
366
|
+
margin-bottom: 20px;
|
|
367
|
+
flex-wrap: wrap;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.level-tab {
|
|
371
|
+
padding: 12px 20px;
|
|
372
|
+
background: var(--bg-card);
|
|
373
|
+
border: 1px solid var(--border);
|
|
374
|
+
border-radius: 8px;
|
|
375
|
+
cursor: pointer;
|
|
376
|
+
transition: all 0.2s;
|
|
377
|
+
display: flex;
|
|
378
|
+
flex-direction: column;
|
|
379
|
+
align-items: center;
|
|
380
|
+
min-width: 80px;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.level-tab:hover {
|
|
384
|
+
border-color: var(--accent);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.level-tab.active {
|
|
388
|
+
background: var(--accent);
|
|
389
|
+
border-color: var(--accent);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.level-tab .level-name {
|
|
393
|
+
font-weight: 600;
|
|
394
|
+
font-size: 1.1rem;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.level-tab .level-count {
|
|
398
|
+
font-size: 0.75rem;
|
|
399
|
+
color: var(--text-secondary);
|
|
400
|
+
margin-top: 4px;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.level-tab.active .level-count {
|
|
404
|
+
color: rgba(255, 255, 255, 0.8);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.level-description {
|
|
408
|
+
font-size: 0.85rem;
|
|
409
|
+
color: var(--text-secondary);
|
|
410
|
+
margin-bottom: 16px;
|
|
411
|
+
padding: 12px;
|
|
412
|
+
background: var(--bg-card);
|
|
413
|
+
border-radius: 8px;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.level-pipeline {
|
|
417
|
+
display: flex;
|
|
418
|
+
align-items: center;
|
|
419
|
+
justify-content: center;
|
|
420
|
+
gap: 8px;
|
|
421
|
+
margin-bottom: 20px;
|
|
422
|
+
flex-wrap: wrap;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.pipeline-step {
|
|
426
|
+
padding: 8px 16px;
|
|
427
|
+
background: var(--bg-card);
|
|
428
|
+
border-radius: 20px;
|
|
429
|
+
font-size: 0.8rem;
|
|
430
|
+
opacity: 0.5;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.pipeline-step.active {
|
|
434
|
+
opacity: 1;
|
|
435
|
+
background: var(--accent);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.pipeline-arrow {
|
|
439
|
+
color: var(--text-secondary);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.event-card {
|
|
443
|
+
padding: 16px;
|
|
444
|
+
background: var(--bg-card);
|
|
445
|
+
border-radius: 8px;
|
|
446
|
+
margin-bottom: 12px;
|
|
447
|
+
border: 1px solid transparent;
|
|
448
|
+
transition: all 0.2s;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.event-card:hover {
|
|
452
|
+
border-color: var(--accent);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.event-type {
|
|
456
|
+
display: inline-block;
|
|
457
|
+
padding: 2px 8px;
|
|
458
|
+
border-radius: 4px;
|
|
459
|
+
font-size: 0.75rem;
|
|
460
|
+
font-weight: 600;
|
|
461
|
+
margin-right: 8px;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.event-type.user_prompt {
|
|
465
|
+
background: rgba(59, 130, 246, 0.2);
|
|
466
|
+
color: #3b82f6;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.event-type.agent_response {
|
|
470
|
+
background: rgba(16, 185, 129, 0.2);
|
|
471
|
+
color: #10b981;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.event-type.tool_observation {
|
|
475
|
+
background: rgba(245, 158, 11, 0.2);
|
|
476
|
+
color: #f59e0b;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.event-type.session_summary {
|
|
480
|
+
background: rgba(139, 92, 246, 0.2);
|
|
481
|
+
color: #8b5cf6;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.event-content {
|
|
485
|
+
margin-top: 8px;
|
|
486
|
+
font-size: 0.9rem;
|
|
487
|
+
color: var(--text-secondary);
|
|
488
|
+
line-height: 1.5;
|
|
489
|
+
white-space: pre-wrap;
|
|
490
|
+
word-break: break-word;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.load-more-btn {
|
|
494
|
+
width: 100%;
|
|
495
|
+
padding: 12px;
|
|
496
|
+
background: var(--bg-card);
|
|
497
|
+
border: 1px solid var(--border);
|
|
498
|
+
color: var(--text-primary);
|
|
499
|
+
border-radius: 8px;
|
|
500
|
+
cursor: pointer;
|
|
501
|
+
transition: all 0.2s;
|
|
502
|
+
margin-top: 16px;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.load-more-btn:hover {
|
|
506
|
+
border-color: var(--accent);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.load-more-btn:disabled {
|
|
510
|
+
opacity: 0.5;
|
|
511
|
+
cursor: not-allowed;
|
|
512
|
+
}
|
|
513
|
+
</style>
|
|
514
|
+
</head>
|
|
515
|
+
<body>
|
|
516
|
+
<div class="container">
|
|
517
|
+
<header>
|
|
518
|
+
<div class="logo">
|
|
519
|
+
<span class="logo-icon">🧠</span>
|
|
520
|
+
<span>Code Memory Dashboard</span>
|
|
521
|
+
</div>
|
|
522
|
+
<button class="refresh-btn" onclick="refreshData()">
|
|
523
|
+
<span id="refresh-icon">🔄</span>
|
|
524
|
+
<span>Refresh</span>
|
|
525
|
+
</button>
|
|
526
|
+
</header>
|
|
527
|
+
|
|
528
|
+
<div id="stats-container" class="stats-grid">
|
|
529
|
+
<div class="stat-card">
|
|
530
|
+
<div class="stat-value" id="stat-events">-</div>
|
|
531
|
+
<div class="stat-label">Total Events</div>
|
|
532
|
+
</div>
|
|
533
|
+
<div class="stat-card">
|
|
534
|
+
<div class="stat-value" id="stat-sessions">-</div>
|
|
535
|
+
<div class="stat-label">Sessions</div>
|
|
536
|
+
</div>
|
|
537
|
+
<div class="stat-card">
|
|
538
|
+
<div class="stat-value" id="stat-shared">-</div>
|
|
539
|
+
<div class="stat-label">Shared Entries</div>
|
|
540
|
+
</div>
|
|
541
|
+
<div class="stat-card">
|
|
542
|
+
<div class="stat-value" id="stat-vectors">-</div>
|
|
543
|
+
<div class="stat-label">Vectors</div>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
|
|
547
|
+
<div class="search-container">
|
|
548
|
+
<input
|
|
549
|
+
type="text"
|
|
550
|
+
class="search-input"
|
|
551
|
+
placeholder="🔍 Search memories..."
|
|
552
|
+
id="search-input"
|
|
553
|
+
onkeyup="handleSearch(event)"
|
|
554
|
+
>
|
|
555
|
+
</div>
|
|
556
|
+
|
|
557
|
+
<div id="search-results" style="display: none; margin-bottom: 30px;">
|
|
558
|
+
<div class="section">
|
|
559
|
+
<h2 class="section-title">🔍 Search Results</h2>
|
|
560
|
+
<div id="search-results-content"></div>
|
|
561
|
+
</div>
|
|
562
|
+
</div>
|
|
563
|
+
|
|
564
|
+
<!-- Memory Level Explorer -->
|
|
565
|
+
<div class="section" style="margin-bottom: 30px;">
|
|
566
|
+
<h2 class="section-title">🎯 Memory Level Explorer</h2>
|
|
567
|
+
|
|
568
|
+
<!-- Pipeline Visualization -->
|
|
569
|
+
<div class="level-pipeline" id="level-pipeline">
|
|
570
|
+
<span class="pipeline-step active" data-level="L0">L0 Raw</span>
|
|
571
|
+
<span class="pipeline-arrow">→</span>
|
|
572
|
+
<span class="pipeline-step" data-level="L1">L1 Structured</span>
|
|
573
|
+
<span class="pipeline-arrow">→</span>
|
|
574
|
+
<span class="pipeline-step" data-level="L2">L2 Validated</span>
|
|
575
|
+
<span class="pipeline-arrow">→</span>
|
|
576
|
+
<span class="pipeline-step" data-level="L3">L3 Verified</span>
|
|
577
|
+
<span class="pipeline-arrow">→</span>
|
|
578
|
+
<span class="pipeline-step" data-level="L4">L4 Active</span>
|
|
579
|
+
</div>
|
|
580
|
+
|
|
581
|
+
<!-- Level Tabs -->
|
|
582
|
+
<div class="level-nav" id="level-nav">
|
|
583
|
+
<div class="level-tab active" data-level="L0" onclick="selectLevel('L0')">
|
|
584
|
+
<span class="level-name">L0</span>
|
|
585
|
+
<span class="level-count" id="level-count-L0">0</span>
|
|
586
|
+
</div>
|
|
587
|
+
<div class="level-tab" data-level="L1" onclick="selectLevel('L1')">
|
|
588
|
+
<span class="level-name">L1</span>
|
|
589
|
+
<span class="level-count" id="level-count-L1">0</span>
|
|
590
|
+
</div>
|
|
591
|
+
<div class="level-tab" data-level="L2" onclick="selectLevel('L2')">
|
|
592
|
+
<span class="level-name">L2</span>
|
|
593
|
+
<span class="level-count" id="level-count-L2">0</span>
|
|
594
|
+
</div>
|
|
595
|
+
<div class="level-tab" data-level="L3" onclick="selectLevel('L3')">
|
|
596
|
+
<span class="level-name">L3</span>
|
|
597
|
+
<span class="level-count" id="level-count-L3">0</span>
|
|
598
|
+
</div>
|
|
599
|
+
<div class="level-tab" data-level="L4" onclick="selectLevel('L4')">
|
|
600
|
+
<span class="level-name">L4</span>
|
|
601
|
+
<span class="level-count" id="level-count-L4">0</span>
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
|
|
605
|
+
<!-- Level Description -->
|
|
606
|
+
<div class="level-description" id="level-description">
|
|
607
|
+
<strong>L0 - Raw Events:</strong> Unprocessed events from conversations. Includes user prompts, agent responses, and tool observations.
|
|
608
|
+
</div>
|
|
609
|
+
|
|
610
|
+
<!-- Events List -->
|
|
611
|
+
<div id="level-events-list">
|
|
612
|
+
<div class="loading-spinner">Loading...</div>
|
|
613
|
+
</div>
|
|
614
|
+
|
|
615
|
+
<!-- Load More Button -->
|
|
616
|
+
<button class="load-more-btn" id="load-more-btn" onclick="loadMoreEvents()" style="display: none;">
|
|
617
|
+
Load More Events
|
|
618
|
+
</button>
|
|
619
|
+
|
|
620
|
+
<!-- Graduation Controls -->
|
|
621
|
+
<div class="graduation-section" style="margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--border);">
|
|
622
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
|
623
|
+
<h3 style="font-size: 1rem; color: var(--text-secondary);">🎓 Graduation Pipeline</h3>
|
|
624
|
+
<button class="refresh-btn" id="run-graduation-btn" onclick="runGraduation()" style="padding: 8px 16px; font-size: 0.85rem;">
|
|
625
|
+
<span id="graduation-icon">⚡</span> Run Graduation
|
|
626
|
+
</button>
|
|
627
|
+
</div>
|
|
628
|
+
<div class="graduation-criteria" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px;">
|
|
629
|
+
<div class="criteria-card" style="background: var(--bg-card); padding: 12px; border-radius: 8px;">
|
|
630
|
+
<div style="color: var(--accent); font-weight: 600; margin-bottom: 8px;">L0 → L1</div>
|
|
631
|
+
<div style="font-size: 0.85rem; color: var(--text-secondary);">
|
|
632
|
+
• Access: ≥1<br>
|
|
633
|
+
• Confidence: ≥50%<br>
|
|
634
|
+
• Cross-session: 0
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
<div class="criteria-card" style="background: var(--bg-card); padding: 12px; border-radius: 8px;">
|
|
638
|
+
<div style="color: var(--accent); font-weight: 600; margin-bottom: 8px;">L1 → L2</div>
|
|
639
|
+
<div style="font-size: 0.85rem; color: var(--text-secondary);">
|
|
640
|
+
• Access: ≥3<br>
|
|
641
|
+
• Confidence: ≥70%<br>
|
|
642
|
+
• Cross-session: ≥1
|
|
643
|
+
</div>
|
|
644
|
+
</div>
|
|
645
|
+
<div class="criteria-card" style="background: var(--bg-card); padding: 12px; border-radius: 8px;">
|
|
646
|
+
<div style="color: var(--accent); font-weight: 600; margin-bottom: 8px;">L2 → L3</div>
|
|
647
|
+
<div style="font-size: 0.85rem; color: var(--text-secondary);">
|
|
648
|
+
• Access: ≥5<br>
|
|
649
|
+
• Confidence: ≥85%<br>
|
|
650
|
+
• Cross-session: ≥2
|
|
651
|
+
</div>
|
|
652
|
+
</div>
|
|
653
|
+
<div class="criteria-card" style="background: var(--bg-card); padding: 12px; border-radius: 8px;">
|
|
654
|
+
<div style="color: var(--accent); font-weight: 600; margin-bottom: 8px;">L3 → L4</div>
|
|
655
|
+
<div style="font-size: 0.85rem; color: var(--text-secondary);">
|
|
656
|
+
• Access: ≥10<br>
|
|
657
|
+
• Confidence: ≥92%<br>
|
|
658
|
+
• Cross-session: ≥3
|
|
659
|
+
</div>
|
|
660
|
+
</div>
|
|
661
|
+
</div>
|
|
662
|
+
<div id="graduation-result" style="margin-top: 12px; display: none;"></div>
|
|
663
|
+
</div>
|
|
664
|
+
</div>
|
|
665
|
+
|
|
666
|
+
<div class="main-grid">
|
|
667
|
+
<div class="section">
|
|
668
|
+
<h2 class="section-title">📋 Recent Sessions</h2>
|
|
669
|
+
<ul class="session-list" id="session-list">
|
|
670
|
+
<li class="loading-spinner">Loading...</li>
|
|
671
|
+
</ul>
|
|
672
|
+
</div>
|
|
673
|
+
|
|
674
|
+
<div class="section">
|
|
675
|
+
<h2 class="section-title">🌐 Shared Knowledge</h2>
|
|
676
|
+
<div id="shared-stats">
|
|
677
|
+
<div class="shared-item">
|
|
678
|
+
<div class="shared-type">
|
|
679
|
+
<span class="shared-icon">🔧</span>
|
|
680
|
+
<span>Troubleshooting</span>
|
|
681
|
+
</div>
|
|
682
|
+
<span class="shared-count" id="shared-troubleshooting">0</span>
|
|
683
|
+
</div>
|
|
684
|
+
<div class="shared-item">
|
|
685
|
+
<div class="shared-type">
|
|
686
|
+
<span class="shared-icon">✨</span>
|
|
687
|
+
<span>Best Practices</span>
|
|
688
|
+
</div>
|
|
689
|
+
<span class="shared-count" id="shared-best-practices">0</span>
|
|
690
|
+
</div>
|
|
691
|
+
<div class="shared-item">
|
|
692
|
+
<div class="shared-type">
|
|
693
|
+
<span class="shared-icon">⚠️</span>
|
|
694
|
+
<span>Common Errors</span>
|
|
695
|
+
</div>
|
|
696
|
+
<span class="shared-count" id="shared-errors">0</span>
|
|
697
|
+
</div>
|
|
698
|
+
</div>
|
|
699
|
+
|
|
700
|
+
<h3 class="section-title" style="margin-top: 24px;">♾️ Endless Mode</h3>
|
|
701
|
+
<div id="endless-status">
|
|
702
|
+
<div class="endless-status">
|
|
703
|
+
<div class="status-indicator inactive" id="endless-indicator"></div>
|
|
704
|
+
<span id="endless-mode-text">Loading...</span>
|
|
705
|
+
</div>
|
|
706
|
+
<div id="endless-details" style="display: none;">
|
|
707
|
+
<div style="margin-bottom: 12px;">
|
|
708
|
+
<span style="color: var(--text-secondary);">Continuity Score</span>
|
|
709
|
+
<div class="progress-bar">
|
|
710
|
+
<div class="progress-fill" id="continuity-bar" style="width: 0%"></div>
|
|
711
|
+
</div>
|
|
712
|
+
</div>
|
|
713
|
+
<div style="display: flex; justify-content: space-between; color: var(--text-secondary); font-size: 0.85rem;">
|
|
714
|
+
<span>Working Set: <strong id="working-set-size">0</strong></span>
|
|
715
|
+
<span>Consolidated: <strong id="consolidated-count">0</strong></span>
|
|
716
|
+
</div>
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
</div>
|
|
721
|
+
|
|
722
|
+
<div class="section" style="margin-top: 30px;">
|
|
723
|
+
<h2 class="section-title">🔥 Most Referenced Memories</h2>
|
|
724
|
+
<div id="most-accessed-list">
|
|
725
|
+
<div class="loading-spinner">Loading...</div>
|
|
726
|
+
</div>
|
|
727
|
+
</div>
|
|
728
|
+
|
|
729
|
+
<div class="timeline-container section" style="margin-top: 30px;">
|
|
730
|
+
<h2 class="section-title">📊 Activity Timeline (Last 7 Days)</h2>
|
|
731
|
+
<div class="timeline-bar" id="timeline-bar">
|
|
732
|
+
<!-- Filled by JS -->
|
|
733
|
+
</div>
|
|
734
|
+
<div class="timeline-labels" id="timeline-labels">
|
|
735
|
+
<!-- Filled by JS -->
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
</div>
|
|
739
|
+
|
|
740
|
+
<script>
|
|
741
|
+
const API_BASE = '/api';
|
|
742
|
+
let refreshInterval = null;
|
|
743
|
+
|
|
744
|
+
// Level Explorer State
|
|
745
|
+
let currentLevel = 'L0';
|
|
746
|
+
let currentOffset = 0;
|
|
747
|
+
const pageSize = 20;
|
|
748
|
+
let hasMoreEvents = false;
|
|
749
|
+
|
|
750
|
+
const LEVEL_DESCRIPTIONS = {
|
|
751
|
+
L0: '<strong>L0 - Raw Events:</strong> Unprocessed events from conversations. Includes user prompts, agent responses, and tool observations.',
|
|
752
|
+
L1: '<strong>L1 - Structured:</strong> Events that have been analyzed and structured. Contains session summaries and extracted patterns.',
|
|
753
|
+
L2: '<strong>L2 - Validated:</strong> Type candidates with validated schemas. Events that have been cross-referenced and verified.',
|
|
754
|
+
L3: '<strong>L3 - Verified:</strong> Cross-session validated knowledge. High-confidence information verified across multiple sessions.',
|
|
755
|
+
L4: '<strong>L4 - Active:</strong> Indexed and readily searchable memories. The most refined and accessible knowledge.'
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
async function fetchStats() {
|
|
759
|
+
try {
|
|
760
|
+
const response = await fetch(`${API_BASE}/stats`);
|
|
761
|
+
if (!response.ok) throw new Error('Failed to fetch stats');
|
|
762
|
+
return await response.json();
|
|
763
|
+
} catch (error) {
|
|
764
|
+
console.error('Stats fetch error:', error);
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async function fetchSharedStats() {
|
|
770
|
+
try {
|
|
771
|
+
const response = await fetch(`${API_BASE}/stats/shared`);
|
|
772
|
+
if (!response.ok) throw new Error('Failed to fetch shared stats');
|
|
773
|
+
return await response.json();
|
|
774
|
+
} catch (error) {
|
|
775
|
+
console.error('Shared stats fetch error:', error);
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
async function fetchEndlessStatus() {
|
|
781
|
+
try {
|
|
782
|
+
const response = await fetch(`${API_BASE}/stats/endless`);
|
|
783
|
+
if (!response.ok) throw new Error('Failed to fetch endless status');
|
|
784
|
+
return await response.json();
|
|
785
|
+
} catch (error) {
|
|
786
|
+
console.error('Endless status fetch error:', error);
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
async function fetchSessions() {
|
|
792
|
+
try {
|
|
793
|
+
const response = await fetch(`${API_BASE}/sessions?limit=10`);
|
|
794
|
+
if (!response.ok) throw new Error('Failed to fetch sessions');
|
|
795
|
+
return await response.json();
|
|
796
|
+
} catch (error) {
|
|
797
|
+
console.error('Sessions fetch error:', error);
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
async function fetchTimeline() {
|
|
803
|
+
try {
|
|
804
|
+
const response = await fetch(`${API_BASE}/stats/timeline?days=7`);
|
|
805
|
+
if (!response.ok) throw new Error('Failed to fetch timeline');
|
|
806
|
+
return await response.json();
|
|
807
|
+
} catch (error) {
|
|
808
|
+
console.error('Timeline fetch error:', error);
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
async function fetchMostAccessed() {
|
|
814
|
+
try {
|
|
815
|
+
const response = await fetch(`${API_BASE}/stats/most-accessed?limit=10`);
|
|
816
|
+
if (!response.ok) throw new Error('Failed to fetch most accessed');
|
|
817
|
+
return await response.json();
|
|
818
|
+
} catch (error) {
|
|
819
|
+
console.error('Most accessed fetch error:', error);
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
async function fetchLevelEvents(level, offset = 0) {
|
|
825
|
+
try {
|
|
826
|
+
const response = await fetch(`${API_BASE}/stats/levels/${level}?limit=${pageSize}&offset=${offset}`);
|
|
827
|
+
if (!response.ok) throw new Error('Failed to fetch level events');
|
|
828
|
+
return await response.json();
|
|
829
|
+
} catch (error) {
|
|
830
|
+
console.error('Level events fetch error:', error);
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function selectLevel(level) {
|
|
836
|
+
currentLevel = level;
|
|
837
|
+
currentOffset = 0;
|
|
838
|
+
|
|
839
|
+
// Update tab styles
|
|
840
|
+
document.querySelectorAll('.level-tab').forEach(tab => {
|
|
841
|
+
tab.classList.toggle('active', tab.dataset.level === level);
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
// Update pipeline visualization
|
|
845
|
+
document.querySelectorAll('.pipeline-step').forEach(step => {
|
|
846
|
+
step.classList.toggle('active', step.dataset.level === level);
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
// Update description
|
|
850
|
+
document.getElementById('level-description').innerHTML = LEVEL_DESCRIPTIONS[level];
|
|
851
|
+
|
|
852
|
+
// Load events for this level
|
|
853
|
+
loadLevelEvents(level, true);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
async function loadLevelEvents(level, reset = false) {
|
|
857
|
+
const container = document.getElementById('level-events-list');
|
|
858
|
+
const loadMoreBtn = document.getElementById('load-more-btn');
|
|
859
|
+
|
|
860
|
+
if (reset) {
|
|
861
|
+
currentOffset = 0;
|
|
862
|
+
container.innerHTML = '<div class="loading-spinner">Loading...</div>';
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const data = await fetchLevelEvents(level, currentOffset);
|
|
866
|
+
|
|
867
|
+
if (!data || !data.events) {
|
|
868
|
+
if (reset) {
|
|
869
|
+
container.innerHTML = '<div class="empty-state">Failed to load events</div>';
|
|
870
|
+
}
|
|
871
|
+
loadMoreBtn.style.display = 'none';
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (data.events.length === 0 && reset) {
|
|
876
|
+
container.innerHTML = `<div class="empty-state">No events at level ${level} yet</div>`;
|
|
877
|
+
loadMoreBtn.style.display = 'none';
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const eventsHtml = data.events.map(event => {
|
|
882
|
+
const typeClass = event.eventType.replace('_', '-');
|
|
883
|
+
const time = formatTimeAgo(event.timestamp);
|
|
884
|
+
return `
|
|
885
|
+
<div class="event-card">
|
|
886
|
+
<div>
|
|
887
|
+
<span class="event-type ${event.eventType}">${event.eventType}</span>
|
|
888
|
+
<span style="color: var(--text-secondary); font-size: 0.85rem;">${time}</span>
|
|
889
|
+
<span style="color: var(--text-secondary); font-size: 0.75rem; margin-left: 8px;">
|
|
890
|
+
Session: ${event.sessionId.slice(0, 8)}...
|
|
891
|
+
</span>
|
|
892
|
+
</div>
|
|
893
|
+
<div class="event-content">${escapeHtml(event.content)}</div>
|
|
894
|
+
</div>
|
|
895
|
+
`;
|
|
896
|
+
}).join('');
|
|
897
|
+
|
|
898
|
+
if (reset) {
|
|
899
|
+
container.innerHTML = eventsHtml;
|
|
900
|
+
} else {
|
|
901
|
+
container.innerHTML += eventsHtml;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
hasMoreEvents = data.hasMore;
|
|
905
|
+
loadMoreBtn.style.display = hasMoreEvents ? 'block' : 'none';
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function loadMoreEvents() {
|
|
909
|
+
currentOffset += pageSize;
|
|
910
|
+
loadLevelEvents(currentLevel, false);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
async function runGraduation() {
|
|
914
|
+
const btn = document.getElementById('run-graduation-btn');
|
|
915
|
+
const icon = document.getElementById('graduation-icon');
|
|
916
|
+
const resultDiv = document.getElementById('graduation-result');
|
|
917
|
+
|
|
918
|
+
btn.classList.add('loading');
|
|
919
|
+
icon.textContent = '⏳';
|
|
920
|
+
resultDiv.style.display = 'none';
|
|
921
|
+
|
|
922
|
+
try {
|
|
923
|
+
const response = await fetch('/api/stats/graduation/run', { method: 'POST' });
|
|
924
|
+
const data = await response.json();
|
|
925
|
+
|
|
926
|
+
if (data.success) {
|
|
927
|
+
let message = `✅ Evaluated ${data.evaluated} events, graduated ${data.graduated}`;
|
|
928
|
+
if (data.graduated > 0 && Object.keys(data.byLevel).length > 0) {
|
|
929
|
+
const levels = Object.entries(data.byLevel)
|
|
930
|
+
.map(([level, count]) => `${level}: ${count}`)
|
|
931
|
+
.join(', ');
|
|
932
|
+
message += ` (${levels})`;
|
|
933
|
+
}
|
|
934
|
+
resultDiv.innerHTML = `<div style="padding: 12px; background: rgba(74, 222, 128, 0.1); border-radius: 8px; color: var(--success);">${message}</div>`;
|
|
935
|
+
resultDiv.style.display = 'block';
|
|
936
|
+
|
|
937
|
+
// Refresh data to show updated counts
|
|
938
|
+
if (data.graduated > 0) {
|
|
939
|
+
setTimeout(() => {
|
|
940
|
+
refreshData();
|
|
941
|
+
}, 500);
|
|
942
|
+
}
|
|
943
|
+
} else {
|
|
944
|
+
resultDiv.innerHTML = `<div style="padding: 12px; background: rgba(233, 69, 96, 0.1); border-radius: 8px; color: var(--accent);">❌ ${data.error || 'Graduation failed'}</div>`;
|
|
945
|
+
resultDiv.style.display = 'block';
|
|
946
|
+
}
|
|
947
|
+
} catch (error) {
|
|
948
|
+
resultDiv.innerHTML = `<div style="padding: 12px; background: rgba(233, 69, 96, 0.1); border-radius: 8px; color: var(--accent);">❌ Error: ${error.message}</div>`;
|
|
949
|
+
resultDiv.style.display = 'block';
|
|
950
|
+
} finally {
|
|
951
|
+
btn.classList.remove('loading');
|
|
952
|
+
icon.textContent = '⚡';
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function escapeHtml(text) {
|
|
957
|
+
const div = document.createElement('div');
|
|
958
|
+
div.textContent = text;
|
|
959
|
+
return div.innerHTML;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function updateLevelCounts(stats) {
|
|
963
|
+
if (!stats || !stats.levelStats) return;
|
|
964
|
+
|
|
965
|
+
// Reset all counts to 0
|
|
966
|
+
['L0', 'L1', 'L2', 'L3', 'L4'].forEach(level => {
|
|
967
|
+
const el = document.getElementById(`level-count-${level}`);
|
|
968
|
+
if (el) el.textContent = '0';
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
// Update with actual counts
|
|
972
|
+
stats.levelStats.forEach(stat => {
|
|
973
|
+
const el = document.getElementById(`level-count-${stat.level}`);
|
|
974
|
+
if (el) el.textContent = formatNumber(stat.count);
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
async function searchMemories(query) {
|
|
979
|
+
try {
|
|
980
|
+
const response = await fetch(`${API_BASE}/search?q=${encodeURIComponent(query)}&limit=10`);
|
|
981
|
+
if (!response.ok) throw new Error('Failed to search');
|
|
982
|
+
return await response.json();
|
|
983
|
+
} catch (error) {
|
|
984
|
+
console.error('Search error:', error);
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function formatNumber(num) {
|
|
990
|
+
if (num >= 1000) {
|
|
991
|
+
return (num / 1000).toFixed(1) + 'K';
|
|
992
|
+
}
|
|
993
|
+
return num.toString();
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function formatTimeAgo(dateStr) {
|
|
997
|
+
const date = new Date(dateStr);
|
|
998
|
+
const now = new Date();
|
|
999
|
+
const diffMs = now - date;
|
|
1000
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
1001
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
1002
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
1003
|
+
|
|
1004
|
+
if (diffMins < 1) return 'Just now';
|
|
1005
|
+
if (diffMins < 60) return `${diffMins} min ago`;
|
|
1006
|
+
if (diffHours < 24) return `${diffHours} hr ago`;
|
|
1007
|
+
return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function updateStats(stats) {
|
|
1011
|
+
if (!stats) return;
|
|
1012
|
+
|
|
1013
|
+
document.getElementById('stat-events').textContent = formatNumber(stats.storage?.eventCount || 0);
|
|
1014
|
+
document.getElementById('stat-sessions').textContent = formatNumber(stats.sessions?.total || 0);
|
|
1015
|
+
document.getElementById('stat-vectors').textContent = formatNumber(stats.storage?.vectorCount || 0);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function updateSharedStats(stats) {
|
|
1019
|
+
if (!stats) {
|
|
1020
|
+
document.getElementById('stat-shared').textContent = '0';
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const total = (stats.troubleshooting || 0) + (stats.bestPractices || 0) + (stats.commonErrors || 0);
|
|
1025
|
+
document.getElementById('stat-shared').textContent = formatNumber(total);
|
|
1026
|
+
document.getElementById('shared-troubleshooting').textContent = stats.troubleshooting || 0;
|
|
1027
|
+
document.getElementById('shared-best-practices').textContent = stats.bestPractices || 0;
|
|
1028
|
+
document.getElementById('shared-errors').textContent = stats.commonErrors || 0;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function updateEndlessStatus(status) {
|
|
1032
|
+
const indicator = document.getElementById('endless-indicator');
|
|
1033
|
+
const text = document.getElementById('endless-mode-text');
|
|
1034
|
+
const details = document.getElementById('endless-details');
|
|
1035
|
+
|
|
1036
|
+
if (!status) {
|
|
1037
|
+
text.textContent = 'Unable to load';
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
if (status.mode === 'endless') {
|
|
1042
|
+
indicator.className = 'status-indicator active';
|
|
1043
|
+
text.textContent = 'Endless Mode Active';
|
|
1044
|
+
details.style.display = 'block';
|
|
1045
|
+
|
|
1046
|
+
const continuityPercent = (status.continuityScore || 0) * 100;
|
|
1047
|
+
document.getElementById('continuity-bar').style.width = `${continuityPercent}%`;
|
|
1048
|
+
document.getElementById('working-set-size').textContent = status.workingSetSize || 0;
|
|
1049
|
+
document.getElementById('consolidated-count').textContent = status.consolidatedCount || 0;
|
|
1050
|
+
} else {
|
|
1051
|
+
indicator.className = 'status-indicator inactive';
|
|
1052
|
+
text.textContent = 'Session Mode (Endless Mode Disabled)';
|
|
1053
|
+
details.style.display = 'none';
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function updateSessions(sessions) {
|
|
1058
|
+
const container = document.getElementById('session-list');
|
|
1059
|
+
|
|
1060
|
+
if (!sessions || sessions.length === 0) {
|
|
1061
|
+
container.innerHTML = '<li class="empty-state">No sessions found</li>';
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
container.innerHTML = sessions.map(session => {
|
|
1066
|
+
// Handle both API formats: id/sessionId, lastEventAt/lastActivity
|
|
1067
|
+
const sessionId = session.id || session.sessionId || 'unknown';
|
|
1068
|
+
const lastTime = session.lastEventAt || session.lastActivity || session.startedAt || session.startTime;
|
|
1069
|
+
const eventCount = session.eventCount || 0;
|
|
1070
|
+
|
|
1071
|
+
return `
|
|
1072
|
+
<li class="session-item">
|
|
1073
|
+
<div class="session-header">
|
|
1074
|
+
<span class="session-id">${sessionId.slice(0, 16)}...</span>
|
|
1075
|
+
<span class="session-time">${formatTimeAgo(lastTime)}</span>
|
|
1076
|
+
</div>
|
|
1077
|
+
<div class="session-meta">
|
|
1078
|
+
<span>📝 ${eventCount} events</span>
|
|
1079
|
+
</div>
|
|
1080
|
+
</li>
|
|
1081
|
+
`;
|
|
1082
|
+
}).join('');
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function updateTimeline(timeline) {
|
|
1086
|
+
const bar = document.getElementById('timeline-bar');
|
|
1087
|
+
const labels = document.getElementById('timeline-labels');
|
|
1088
|
+
|
|
1089
|
+
if (!timeline || !timeline.daily || timeline.daily.length === 0) {
|
|
1090
|
+
bar.innerHTML = '<div class="empty-state">No activity data</div>';
|
|
1091
|
+
labels.innerHTML = '';
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
const maxTotal = Math.max(...timeline.daily.map(d => d.total), 1);
|
|
1096
|
+
|
|
1097
|
+
bar.innerHTML = timeline.daily.map(day => {
|
|
1098
|
+
const height = Math.max(4, (day.total / maxTotal) * 100);
|
|
1099
|
+
const date = new Date(day.date).toLocaleDateString('en-US', { weekday: 'short' });
|
|
1100
|
+
return `
|
|
1101
|
+
<div
|
|
1102
|
+
class="timeline-day"
|
|
1103
|
+
style="height: ${height}%"
|
|
1104
|
+
data-tooltip="${date}: ${day.total} events"
|
|
1105
|
+
></div>
|
|
1106
|
+
`;
|
|
1107
|
+
}).join('');
|
|
1108
|
+
|
|
1109
|
+
const firstDate = new Date(timeline.daily[0].date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
1110
|
+
const lastDate = new Date(timeline.daily[timeline.daily.length - 1].date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
1111
|
+
labels.innerHTML = `<span>${firstDate}</span><span>${lastDate}</span>`;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function updateMostAccessed(data) {
|
|
1115
|
+
const container = document.getElementById('most-accessed-list');
|
|
1116
|
+
|
|
1117
|
+
if (!data || !data.memories || data.memories.length === 0) {
|
|
1118
|
+
container.innerHTML = '<div class="empty-state">No memory access data yet. Memories will appear here as they are referenced.</div>';
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
container.innerHTML = data.memories.map(memory => {
|
|
1123
|
+
const accessBars = '█'.repeat(Math.min(10, memory.accessCount));
|
|
1124
|
+
const accessEmpty = '░'.repeat(Math.max(0, 10 - memory.accessCount));
|
|
1125
|
+
const lastAccessed = memory.lastAccessed ? formatTimeAgo(memory.lastAccessed) : 'Never';
|
|
1126
|
+
|
|
1127
|
+
return `
|
|
1128
|
+
<div class="session-item">
|
|
1129
|
+
<div class="session-header">
|
|
1130
|
+
<span class="session-id">🧠 ${memory.topics?.slice(0, 2).join(', ') || 'Memory'}</span>
|
|
1131
|
+
<span class="session-time">${lastAccessed}</span>
|
|
1132
|
+
</div>
|
|
1133
|
+
<div style="margin: 8px 0;">
|
|
1134
|
+
<span style="color: var(--text-secondary); font-size: 0.85rem;">Access count:</span>
|
|
1135
|
+
<span style="font-family: monospace; color: var(--accent);">[${accessBars}${accessEmpty}] ${memory.accessCount}</span>
|
|
1136
|
+
</div>
|
|
1137
|
+
<p style="color: var(--text-secondary); font-size: 0.9rem; margin-top: 8px;">
|
|
1138
|
+
${memory.summary?.slice(0, 150) || 'No summary'}${memory.summary?.length > 150 ? '...' : ''}
|
|
1139
|
+
</p>
|
|
1140
|
+
</div>
|
|
1141
|
+
`;
|
|
1142
|
+
}).join('');
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
function showSearchResults(results) {
|
|
1146
|
+
const container = document.getElementById('search-results');
|
|
1147
|
+
const content = document.getElementById('search-results-content');
|
|
1148
|
+
|
|
1149
|
+
if (!results || results.length === 0) {
|
|
1150
|
+
content.innerHTML = '<div class="empty-state">No results found</div>';
|
|
1151
|
+
container.style.display = 'block';
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
content.innerHTML = results.map(result => `
|
|
1156
|
+
<div class="session-item">
|
|
1157
|
+
<div class="session-header">
|
|
1158
|
+
<span class="session-id">${result.eventType || 'memory'}</span>
|
|
1159
|
+
<span class="session-time">Score: ${(result.score * 100).toFixed(0)}%</span>
|
|
1160
|
+
</div>
|
|
1161
|
+
<p style="color: var(--text-secondary); margin-top: 8px;">
|
|
1162
|
+
${result.content?.slice(0, 200) || 'No content'}${result.content?.length > 200 ? '...' : ''}
|
|
1163
|
+
</p>
|
|
1164
|
+
</div>
|
|
1165
|
+
`).join('');
|
|
1166
|
+
|
|
1167
|
+
container.style.display = 'block';
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
let searchTimeout = null;
|
|
1171
|
+
function handleSearch(event) {
|
|
1172
|
+
const query = event.target.value.trim();
|
|
1173
|
+
|
|
1174
|
+
if (searchTimeout) clearTimeout(searchTimeout);
|
|
1175
|
+
|
|
1176
|
+
if (!query) {
|
|
1177
|
+
document.getElementById('search-results').style.display = 'none';
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
searchTimeout = setTimeout(async () => {
|
|
1182
|
+
const results = await searchMemories(query);
|
|
1183
|
+
showSearchResults(results?.results || results || []);
|
|
1184
|
+
}, 300);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
async function refreshData() {
|
|
1188
|
+
const btn = document.querySelector('.refresh-btn');
|
|
1189
|
+
btn.classList.add('loading');
|
|
1190
|
+
document.getElementById('refresh-icon').textContent = '⏳';
|
|
1191
|
+
|
|
1192
|
+
try {
|
|
1193
|
+
const [stats, sharedStats, endlessStatus, sessions, timeline, mostAccessed] = await Promise.all([
|
|
1194
|
+
fetchStats(),
|
|
1195
|
+
fetchSharedStats(),
|
|
1196
|
+
fetchEndlessStatus(),
|
|
1197
|
+
fetchSessions(),
|
|
1198
|
+
fetchTimeline(),
|
|
1199
|
+
fetchMostAccessed()
|
|
1200
|
+
]);
|
|
1201
|
+
|
|
1202
|
+
updateStats(stats);
|
|
1203
|
+
updateSharedStats(sharedStats);
|
|
1204
|
+
updateEndlessStatus(endlessStatus);
|
|
1205
|
+
updateSessions(sessions?.sessions || sessions || []);
|
|
1206
|
+
updateTimeline(timeline);
|
|
1207
|
+
updateMostAccessed(mostAccessed);
|
|
1208
|
+
updateLevelCounts(stats);
|
|
1209
|
+
loadLevelEvents(currentLevel, true);
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
console.error('Refresh error:', error);
|
|
1212
|
+
} finally {
|
|
1213
|
+
btn.classList.remove('loading');
|
|
1214
|
+
document.getElementById('refresh-icon').textContent = '🔄';
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// Initial load
|
|
1219
|
+
refreshData();
|
|
1220
|
+
|
|
1221
|
+
// Auto-refresh every 30 seconds
|
|
1222
|
+
refreshInterval = setInterval(refreshData, 30000);
|
|
1223
|
+
</script>
|
|
1224
|
+
</body>
|
|
1225
|
+
</html>
|