claude-memory-layer 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.claude/settings.local.json +7 -1
  2. package/.claude-memory/test.sqlite +0 -0
  3. package/.history/package_20260202114053.json +49 -0
  4. package/.history/package_20260202121115.json +49 -0
  5. package/HANDOFF.md +92 -0
  6. package/dist/cli/index.js +1257 -74
  7. package/dist/cli/index.js.map +4 -4
  8. package/dist/core/index.js +1111 -47
  9. package/dist/core/index.js.map +4 -4
  10. package/dist/hooks/post-tool-use.js +5693 -0
  11. package/dist/hooks/post-tool-use.js.map +7 -0
  12. package/dist/hooks/session-end.js +1224 -67
  13. package/dist/hooks/session-end.js.map +4 -4
  14. package/dist/hooks/session-start.js +1219 -66
  15. package/dist/hooks/session-start.js.map +4 -4
  16. package/dist/hooks/stop.js +1224 -67
  17. package/dist/hooks/stop.js.map +4 -4
  18. package/dist/hooks/user-prompt-submit.js +1252 -98
  19. package/dist/hooks/user-prompt-submit.js.map +4 -4
  20. package/dist/server/api/index.js +1252 -73
  21. package/dist/server/api/index.js.map +4 -4
  22. package/dist/server/index.js +1252 -73
  23. package/dist/server/index.js.map +4 -4
  24. package/dist/services/memory-service.js +1246 -68
  25. package/dist/services/memory-service.js.map +4 -4
  26. package/dist/ui/app.js +304 -0
  27. package/dist/ui/index.html +195 -1188
  28. package/dist/ui/style.css +595 -0
  29. package/package.json +3 -1
  30. package/scripts/build.ts +2 -0
  31. package/src/core/event-store.ts +18 -0
  32. package/src/core/index.ts +3 -0
  33. package/src/core/retriever.ts +4 -1
  34. package/src/core/sqlite-event-store.ts +947 -0
  35. package/src/core/sqlite-wrapper.ts +108 -0
  36. package/src/core/sync-worker.ts +228 -0
  37. package/src/core/vector-worker.ts +44 -14
  38. package/src/hooks/user-prompt-submit.ts +40 -17
  39. package/src/server/api/stats.ts +37 -7
  40. package/src/services/memory-service.ts +239 -43
  41. package/src/ui/app.js +304 -0
  42. package/src/ui/index.html +195 -1188
  43. package/src/ui/style.css +595 -0
  44. package/test_access.js +49 -0
@@ -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
- <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>
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
- <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>
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
- </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
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
- <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>
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
- <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>
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
- <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>
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
- <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>
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
- <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>
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
- <span class="shared-count" id="shared-errors">0</span>
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
- <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>
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 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>
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
- 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>
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 class="session-meta">
1078
- <span>📝 ${eventCount} events</span>
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
- </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';
188
+ </div>
1126
189
 
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>
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
- <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>
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
- 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();
226
+ </div>
227
+ </main>
228
+ </div>
1220
229
 
1221
- // Auto-refresh every 30 seconds
1222
- refreshInterval = setInterval(refreshData, 30000);
1223
- </script>
230
+ <script src="app.js"></script>
1224
231
  </body>
1225
232
  </html>