claude-memory-layer 1.0.8 → 1.0.9
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 +7 -1
- package/.claude-memory/test.sqlite +0 -0
- package/.history/package_20260202114053.json +49 -0
- package/HANDOFF.md +92 -0
- package/dist/cli/index.js +1150 -71
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +1033 -47
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +5589 -0
- package/dist/hooks/post-tool-use.js.map +7 -0
- package/dist/hooks/session-end.js +1117 -64
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +1112 -63
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +1117 -64
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +1151 -67
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +1145 -70
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +1145 -70
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +1122 -65
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/app.js +304 -0
- package/dist/ui/index.html +195 -1188
- package/dist/ui/style.css +595 -0
- package/package.json +3 -1
- package/scripts/build.ts +2 -0
- package/src/core/event-store.ts +18 -0
- package/src/core/index.ts +3 -0
- package/src/core/retriever.ts +4 -1
- package/src/core/sqlite-event-store.ts +849 -0
- package/src/core/sqlite-wrapper.ts +108 -0
- package/src/core/sync-worker.ts +228 -0
- package/src/core/vector-worker.ts +44 -14
- package/src/hooks/user-prompt-submit.ts +53 -4
- package/src/server/api/stats.ts +37 -7
- package/src/services/memory-service.ts +168 -39
- package/src/ui/app.js +304 -0
- package/src/ui/index.html +195 -1188
- package/src/ui/style.css +595 -0
- package/test_access.js +49 -0
package/dist/ui/index.html
CHANGED
|
@@ -3,1223 +3,230 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Code Memory Dashboard</title>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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>
|
|
6
|
+
<title>Code Memory | Deep Space Dashboard</title>
|
|
7
|
+
|
|
8
|
+
<!-- Fonts -->
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
12
|
+
|
|
13
|
+
<!-- Icons -->
|
|
14
|
+
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
|
15
|
+
|
|
16
|
+
<!-- Styles -->
|
|
17
|
+
<link rel="stylesheet" href="style.css">
|
|
18
|
+
|
|
19
|
+
<!-- Charts -->
|
|
20
|
+
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
|
|
514
21
|
</head>
|
|
515
22
|
<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
23
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
>
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
<!--
|
|
582
|
-
<
|
|
583
|
-
<div class="
|
|
584
|
-
<
|
|
585
|
-
<
|
|
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>
|
|
24
|
+
<div class="app-container">
|
|
25
|
+
|
|
26
|
+
<!-- Sidebar -->
|
|
27
|
+
<aside class="sidebar">
|
|
28
|
+
<div class="logo-area">
|
|
29
|
+
<div class="logo-icon">🧠</div>
|
|
30
|
+
<div class="logo-text">CodeMemory</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<nav>
|
|
34
|
+
<ul class="nav-menu">
|
|
35
|
+
<li class="nav-item active">
|
|
36
|
+
<i class="ri-dashboard-line"></i>
|
|
37
|
+
<span>Overview</span>
|
|
38
|
+
</li>
|
|
39
|
+
<li class="nav-item">
|
|
40
|
+
<i class="ri-database-2-line"></i>
|
|
41
|
+
<span>Knowledge Graph</span>
|
|
42
|
+
</li>
|
|
43
|
+
<li class="nav-item">
|
|
44
|
+
<i class="ri-brain-line"></i>
|
|
45
|
+
<span>Memory Banks</span>
|
|
46
|
+
</li>
|
|
47
|
+
<li class="nav-item">
|
|
48
|
+
<i class="ri-settings-4-line"></i>
|
|
49
|
+
<span>Configuration</span>
|
|
50
|
+
</li>
|
|
51
|
+
</ul>
|
|
52
|
+
</nav>
|
|
53
|
+
</aside>
|
|
54
|
+
|
|
55
|
+
<!-- Main Content -->
|
|
56
|
+
<main class="main-content">
|
|
57
|
+
|
|
58
|
+
<!-- Header -->
|
|
59
|
+
<header class="top-header">
|
|
60
|
+
<div class="page-title">
|
|
61
|
+
<h1>Dashboard</h1>
|
|
62
|
+
<p>Real-time memory visualization & management</p>
|
|
602
63
|
</div>
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
|
64
|
+
|
|
65
|
+
<div class="header-actions">
|
|
66
|
+
<div class="search-wrapper">
|
|
67
|
+
<i class="ri-search-line"></i>
|
|
68
|
+
<input type="text" id="search-input" class="search-input" placeholder="Search memories...">
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<button id="refresh-btn" class="btn btn-secondary">
|
|
72
|
+
<i class="ri-refresh-line"></i>
|
|
73
|
+
<span>Refresh</span>
|
|
626
74
|
</button>
|
|
627
75
|
</div>
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
76
|
+
</header>
|
|
77
|
+
|
|
78
|
+
<!-- Stats Grid -->
|
|
79
|
+
<div class="stats-grid">
|
|
80
|
+
<div class="stat-card">
|
|
81
|
+
<div class="stat-value" id="stat-events">0</div>
|
|
82
|
+
<div class="stat-label">
|
|
83
|
+
<i class="ri-file-list-3-line"></i> Total Events
|
|
636
84
|
</div>
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
• Cross-session: ≥1
|
|
643
|
-
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="stat-card">
|
|
87
|
+
<div class="stat-value" id="stat-sessions">0</div>
|
|
88
|
+
<div class="stat-label">
|
|
89
|
+
<i class="ri-discuss-line"></i> Active Sessions
|
|
644
90
|
</div>
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
• Cross-session: ≥2
|
|
651
|
-
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="stat-card">
|
|
93
|
+
<div class="stat-value" id="stat-shared">0</div>
|
|
94
|
+
<div class="stat-label">
|
|
95
|
+
<i class="ri-share-forward-line"></i> Shared Items
|
|
652
96
|
</div>
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
• Cross-session: ≥3
|
|
659
|
-
</div>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="stat-card">
|
|
99
|
+
<div class="stat-value" id="stat-vectors">0</div>
|
|
100
|
+
<div class="stat-label">
|
|
101
|
+
<i class="ri-node-tree"></i> Vector Nodes
|
|
660
102
|
</div>
|
|
661
103
|
</div>
|
|
662
|
-
<div id="graduation-result" style="margin-top: 12px; display: none;"></div>
|
|
663
104
|
</div>
|
|
664
|
-
</div>
|
|
665
105
|
|
|
666
|
-
|
|
667
|
-
<div class="
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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>
|
|
106
|
+
<!-- Main Grid -->
|
|
107
|
+
<div class="dashboard-grid">
|
|
108
|
+
|
|
109
|
+
<!-- Left Column -->
|
|
110
|
+
<div class="left-col">
|
|
111
|
+
|
|
112
|
+
<!-- Memory Pipeline -->
|
|
113
|
+
<div class="card">
|
|
114
|
+
<div class="card-header">
|
|
115
|
+
<div class="card-title">
|
|
116
|
+
<i class="ri-flow-chart"></i>
|
|
117
|
+
<span>Memory Pipeline</span>
|
|
118
|
+
</div>
|
|
695
119
|
</div>
|
|
696
|
-
|
|
120
|
+
|
|
121
|
+
<!-- Pipeline Steps (Tabs) -->
|
|
122
|
+
<div class="pipeline-container">
|
|
123
|
+
<div class="pipeline-steps">
|
|
124
|
+
<div class="p-step active" data-level="L0">
|
|
125
|
+
<span class="p-step-name">Raw Events</span>
|
|
126
|
+
<span class="p-step-count">0</span>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="p-step" data-level="L1">
|
|
129
|
+
<span class="p-step-name">Structured</span>
|
|
130
|
+
<span class="p-step-count">0</span>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="p-step" data-level="L2">
|
|
133
|
+
<span class="p-step-name">Validated</span>
|
|
134
|
+
<span class="p-step-count">0</span>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="p-step" data-level="L3">
|
|
137
|
+
<span class="p-step-name">Verified</span>
|
|
138
|
+
<span class="p-step-count">0</span>
|
|
139
|
+
</div>
|
|
140
|
+
<div class="p-step" data-level="L4">
|
|
141
|
+
<span class="p-step-name">Active</span>
|
|
142
|
+
<span class="p-step-count">0</span>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<!-- Events List -->
|
|
148
|
+
<div id="event-list-container" class="event-list">
|
|
149
|
+
<!-- Events injected by JS -->
|
|
150
|
+
<div class="event-item">
|
|
151
|
+
<div class="event-header">
|
|
152
|
+
<span class="event-type-badge type-system">System</span>
|
|
153
|
+
<span class="event-time">Just now</span>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="event-content">Initializing dashboard interface...</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
697
158
|
</div>
|
|
698
|
-
</div>
|
|
699
159
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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>
|
|
160
|
+
<!-- Activity Chart -->
|
|
161
|
+
<div class="card">
|
|
162
|
+
<div class="card-header">
|
|
163
|
+
<div class="card-title">
|
|
164
|
+
<i class="ri-bar-chart-grouped-line"></i>
|
|
165
|
+
<span>Activity Persistence</span>
|
|
711
166
|
</div>
|
|
712
167
|
</div>
|
|
713
|
-
<div
|
|
714
|
-
<span>Working Set: <strong id="working-set-size">0</strong></span>
|
|
715
|
-
<span>Consolidated: <strong id="consolidated-count">0</strong></span>
|
|
716
|
-
</div>
|
|
168
|
+
<div id="activity-chart"></div>
|
|
717
169
|
</div>
|
|
170
|
+
|
|
718
171
|
</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
172
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
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>
|
|
173
|
+
<!-- Right Column -->
|
|
174
|
+
<div class="right-col">
|
|
175
|
+
|
|
176
|
+
<!-- Endless Mode Status -->
|
|
177
|
+
<div class="card endless-card">
|
|
178
|
+
<div class="card-header">
|
|
179
|
+
<div class="card-title">
|
|
180
|
+
<i class="ri-infinite-loop-line"></i>
|
|
181
|
+
<span>Endless Mode</span>
|
|
182
|
+
</div>
|
|
1076
183
|
</div>
|
|
1077
|
-
<div
|
|
1078
|
-
<
|
|
184
|
+
<div style="display:flex; align-items:center; gap:12px; margin-top:10px;">
|
|
185
|
+
<div id="status-dot" class="status-dot"></div>
|
|
186
|
+
<span id="status-text" style="color:var(--text-muted); font-weight:500;">Idle</span>
|
|
1079
187
|
</div>
|
|
1080
|
-
</
|
|
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';
|
|
188
|
+
</div>
|
|
1126
189
|
|
|
1127
|
-
|
|
1128
|
-
<div class="
|
|
1129
|
-
<div class="
|
|
1130
|
-
<
|
|
1131
|
-
|
|
190
|
+
<!-- Shared Knowledge -->
|
|
191
|
+
<div class="card">
|
|
192
|
+
<div class="card-header">
|
|
193
|
+
<div class="card-title">
|
|
194
|
+
<i class="ri-global-line"></i>
|
|
195
|
+
<span>Global Knowledge</span>
|
|
196
|
+
</div>
|
|
1132
197
|
</div>
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
<
|
|
198
|
+
|
|
199
|
+
<div class="shared-list">
|
|
200
|
+
<div class="shared-item">
|
|
201
|
+
<div class="shared-info">
|
|
202
|
+
<div class="shared-icon">🔧</div>
|
|
203
|
+
<span>Troubleshooting</span>
|
|
204
|
+
</div>
|
|
205
|
+
<div class="shared-count" id="shared-troubleshooting">0</div>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="shared-item">
|
|
208
|
+
<div class="shared-info">
|
|
209
|
+
<div class="shared-icon">✨</div>
|
|
210
|
+
<span>Best Practices</span>
|
|
211
|
+
</div>
|
|
212
|
+
<div class="shared-count" id="shared-best-practices">0</div>
|
|
213
|
+
</div>
|
|
214
|
+
<div class="shared-item">
|
|
215
|
+
<div class="shared-info">
|
|
216
|
+
<div class="shared-icon">⚠️</div>
|
|
217
|
+
<span>Common Errors</span>
|
|
218
|
+
</div>
|
|
219
|
+
<div class="shared-count" id="shared-errors">0</div>
|
|
220
|
+
</div>
|
|
1136
221
|
</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
222
|
</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>
|
|
223
|
+
|
|
1164
224
|
</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
225
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
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();
|
|
226
|
+
</div>
|
|
227
|
+
</main>
|
|
228
|
+
</div>
|
|
1220
229
|
|
|
1221
|
-
|
|
1222
|
-
refreshInterval = setInterval(refreshData, 30000);
|
|
1223
|
-
</script>
|
|
230
|
+
<script src="app.js"></script>
|
|
1224
231
|
</body>
|
|
1225
232
|
</html>
|