claude-memory-agent 2.0.0

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 (100) hide show
  1. package/.env.example +107 -0
  2. package/README.md +200 -0
  3. package/agent_card.py +512 -0
  4. package/bin/cli.js +181 -0
  5. package/bin/postinstall.js +216 -0
  6. package/config.py +104 -0
  7. package/dashboard.html +2689 -0
  8. package/hooks/README.md +196 -0
  9. package/hooks/__pycache__/auto-detect-response.cpython-312.pyc +0 -0
  10. package/hooks/__pycache__/auto_capture.cpython-312.pyc +0 -0
  11. package/hooks/__pycache__/session_end.cpython-312.pyc +0 -0
  12. package/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
  13. package/hooks/auto-detect-response.py +348 -0
  14. package/hooks/auto_capture.py +255 -0
  15. package/hooks/detect-correction.py +173 -0
  16. package/hooks/grounding-hook.py +348 -0
  17. package/hooks/log-tool-use.py +234 -0
  18. package/hooks/log-user-request.py +208 -0
  19. package/hooks/pre-tool-decision.py +218 -0
  20. package/hooks/problem-detector.py +343 -0
  21. package/hooks/session_end.py +192 -0
  22. package/hooks/session_start.py +227 -0
  23. package/install.py +887 -0
  24. package/main.py +2859 -0
  25. package/manager.py +997 -0
  26. package/package.json +55 -0
  27. package/requirements.txt +8 -0
  28. package/run_server.py +136 -0
  29. package/services/__init__.py +50 -0
  30. package/services/__pycache__/__init__.cpython-312.pyc +0 -0
  31. package/services/__pycache__/agent_registry.cpython-312.pyc +0 -0
  32. package/services/__pycache__/auth.cpython-312.pyc +0 -0
  33. package/services/__pycache__/auto_inject.cpython-312.pyc +0 -0
  34. package/services/__pycache__/claude_md_sync.cpython-312.pyc +0 -0
  35. package/services/__pycache__/cleanup.cpython-312.pyc +0 -0
  36. package/services/__pycache__/compaction_flush.cpython-312.pyc +0 -0
  37. package/services/__pycache__/confidence.cpython-312.pyc +0 -0
  38. package/services/__pycache__/daily_log.cpython-312.pyc +0 -0
  39. package/services/__pycache__/database.cpython-312.pyc +0 -0
  40. package/services/__pycache__/embeddings.cpython-312.pyc +0 -0
  41. package/services/__pycache__/insights.cpython-312.pyc +0 -0
  42. package/services/__pycache__/llm_analyzer.cpython-312.pyc +0 -0
  43. package/services/__pycache__/memory_md_sync.cpython-312.pyc +0 -0
  44. package/services/__pycache__/retry_queue.cpython-312.pyc +0 -0
  45. package/services/__pycache__/timeline.cpython-312.pyc +0 -0
  46. package/services/__pycache__/vector_index.cpython-312.pyc +0 -0
  47. package/services/__pycache__/websocket.cpython-312.pyc +0 -0
  48. package/services/agent_registry.py +753 -0
  49. package/services/auth.py +331 -0
  50. package/services/auto_inject.py +250 -0
  51. package/services/claude_md_sync.py +275 -0
  52. package/services/cleanup.py +667 -0
  53. package/services/compaction_flush.py +447 -0
  54. package/services/confidence.py +301 -0
  55. package/services/daily_log.py +333 -0
  56. package/services/database.py +2485 -0
  57. package/services/embeddings.py +358 -0
  58. package/services/insights.py +632 -0
  59. package/services/llm_analyzer.py +595 -0
  60. package/services/memory_md_sync.py +409 -0
  61. package/services/retry_queue.py +453 -0
  62. package/services/timeline.py +579 -0
  63. package/services/vector_index.py +398 -0
  64. package/services/websocket.py +257 -0
  65. package/skills/__init__.py +6 -0
  66. package/skills/__pycache__/__init__.cpython-312.pyc +0 -0
  67. package/skills/__pycache__/admin.cpython-312.pyc +0 -0
  68. package/skills/__pycache__/checkpoint.cpython-312.pyc +0 -0
  69. package/skills/__pycache__/claude_md.cpython-312.pyc +0 -0
  70. package/skills/__pycache__/cleanup.cpython-312.pyc +0 -0
  71. package/skills/__pycache__/grounding.cpython-312.pyc +0 -0
  72. package/skills/__pycache__/insights.cpython-312.pyc +0 -0
  73. package/skills/__pycache__/natural_language.cpython-312.pyc +0 -0
  74. package/skills/__pycache__/retrieve.cpython-312.pyc +0 -0
  75. package/skills/__pycache__/search.cpython-312.pyc +0 -0
  76. package/skills/__pycache__/state.cpython-312.pyc +0 -0
  77. package/skills/__pycache__/store.cpython-312.pyc +0 -0
  78. package/skills/__pycache__/summarize.cpython-312.pyc +0 -0
  79. package/skills/__pycache__/timeline.cpython-312.pyc +0 -0
  80. package/skills/__pycache__/verification.cpython-312.pyc +0 -0
  81. package/skills/admin.py +469 -0
  82. package/skills/checkpoint.py +198 -0
  83. package/skills/claude_md.py +363 -0
  84. package/skills/cleanup.py +241 -0
  85. package/skills/grounding.py +801 -0
  86. package/skills/insights.py +231 -0
  87. package/skills/natural_language.py +277 -0
  88. package/skills/retrieve.py +67 -0
  89. package/skills/search.py +213 -0
  90. package/skills/state.py +182 -0
  91. package/skills/store.py +179 -0
  92. package/skills/summarize.py +588 -0
  93. package/skills/timeline.py +387 -0
  94. package/skills/verification.py +391 -0
  95. package/start_daemon.py +155 -0
  96. package/test_automation.py +221 -0
  97. package/test_complete.py +338 -0
  98. package/test_full.py +322 -0
  99. package/update_system.py +817 -0
  100. package/verify_db.py +134 -0
package/dashboard.html ADDED
@@ -0,0 +1,2689 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Claude Memory Control Center</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
8
+ <style>
9
+ * { margin: 0; padding: 0; box-sizing: border-box; }
10
+ :root {
11
+ --bg-primary: #0a0a0f;
12
+ --bg-secondary: #12121a;
13
+ --bg-tertiary: #1a1a25;
14
+ --bg-card: #16161f;
15
+ --border-color: #2a2a3a;
16
+ --text-primary: #e6edf3;
17
+ --text-secondary: #8b949e;
18
+ --text-muted: #6e7681;
19
+ --accent-blue: #58a6ff;
20
+ --accent-green: #3fb950;
21
+ --accent-yellow: #d29922;
22
+ --accent-red: #f85149;
23
+ --accent-purple: #a371f7;
24
+ --accent-pink: #db61a2;
25
+ --accent-cyan: #39d4d4;
26
+ --accent-orange: #f0883e;
27
+ --gradient-1: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
28
+ --gradient-2: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
29
+ --gradient-3: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
30
+ --gradient-4: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
31
+ --shadow-glow: 0 0 40px rgba(88, 166, 255, 0.15);
32
+ }
33
+ body {
34
+ font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
35
+ background: var(--bg-primary);
36
+ color: var(--text-primary);
37
+ min-height: 100vh;
38
+ overflow-x: hidden;
39
+ }
40
+ .bg-animation {
41
+ position: fixed;
42
+ top: 0; left: 0; width: 100%; height: 100%;
43
+ z-index: -1;
44
+ opacity: 0.5;
45
+ background:
46
+ radial-gradient(ellipse at 20% 20%, rgba(88, 166, 255, 0.08) 0%, transparent 50%),
47
+ radial-gradient(ellipse at 80% 80%, rgba(163, 113, 247, 0.08) 0%, transparent 50%),
48
+ radial-gradient(ellipse at 50% 50%, rgba(63, 185, 80, 0.05) 0%, transparent 60%);
49
+ }
50
+ /* Header */
51
+ .header {
52
+ background: rgba(18, 18, 26, 0.8);
53
+ backdrop-filter: blur(20px);
54
+ border-bottom: 1px solid var(--border-color);
55
+ padding: 16px 32px;
56
+ position: sticky;
57
+ top: 0;
58
+ z-index: 100;
59
+ }
60
+ .header-content {
61
+ max-width: 1800px;
62
+ margin: 0 auto;
63
+ display: flex;
64
+ justify-content: space-between;
65
+ align-items: center;
66
+ }
67
+ .logo {
68
+ display: flex;
69
+ align-items: center;
70
+ gap: 16px;
71
+ }
72
+ .logo-icon {
73
+ width: 48px; height: 48px;
74
+ background: var(--gradient-1);
75
+ border-radius: 14px;
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ font-size: 24px;
80
+ box-shadow: var(--shadow-glow);
81
+ }
82
+ .logo-text {
83
+ font-size: 22px;
84
+ font-weight: 700;
85
+ background: linear-gradient(135deg, #fff 0%, #a8b4c4 100%);
86
+ -webkit-background-clip: text;
87
+ -webkit-text-fill-color: transparent;
88
+ }
89
+ .logo-subtitle {
90
+ font-size: 12px;
91
+ color: var(--text-secondary);
92
+ margin-top: 2px;
93
+ }
94
+ .header-controls {
95
+ display: flex;
96
+ align-items: center;
97
+ gap: 20px;
98
+ }
99
+ .project-dropdown { position: relative; }
100
+ .project-btn {
101
+ background: var(--bg-tertiary);
102
+ border: 1px solid var(--border-color);
103
+ color: var(--text-primary);
104
+ padding: 12px 20px;
105
+ border-radius: 12px;
106
+ font-size: 14px;
107
+ cursor: pointer;
108
+ display: flex;
109
+ align-items: center;
110
+ gap: 12px;
111
+ transition: all 0.2s;
112
+ min-width: 280px;
113
+ }
114
+ .project-btn:hover {
115
+ border-color: var(--accent-blue);
116
+ box-shadow: 0 0 20px rgba(88, 166, 255, 0.1);
117
+ }
118
+ .project-btn i { color: var(--accent-blue); }
119
+ .project-menu {
120
+ position: absolute;
121
+ top: 100%; left: 0; right: 0;
122
+ margin-top: 8px;
123
+ background: var(--bg-card);
124
+ border: 1px solid var(--border-color);
125
+ border-radius: 12px;
126
+ padding: 8px;
127
+ display: none;
128
+ max-height: 300px;
129
+ overflow-y: auto;
130
+ z-index: 1000;
131
+ }
132
+ .project-menu.show {
133
+ display: block;
134
+ animation: slideDown 0.2s ease;
135
+ }
136
+ @keyframes slideDown {
137
+ from { opacity: 0; transform: translateY(-10px); }
138
+ to { opacity: 1; transform: translateY(0); }
139
+ }
140
+ .project-item {
141
+ padding: 12px 16px;
142
+ border-radius: 8px;
143
+ cursor: pointer;
144
+ display: flex;
145
+ align-items: center;
146
+ gap: 12px;
147
+ transition: all 0.15s;
148
+ }
149
+ .project-item:hover { background: var(--bg-tertiary); }
150
+ .project-item.active {
151
+ background: rgba(88, 166, 255, 0.15);
152
+ border-left: 3px solid var(--accent-blue);
153
+ }
154
+ .project-item-icon {
155
+ width: 36px; height: 36px;
156
+ border-radius: 8px;
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ font-size: 16px;
161
+ }
162
+ .project-item-info { flex: 1; }
163
+ .project-item-name { font-weight: 500; font-size: 14px; }
164
+ .project-item-stats { font-size: 11px; color: var(--text-secondary); }
165
+ .status-indicator {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 8px;
169
+ padding: 8px 16px;
170
+ background: rgba(63, 185, 80, 0.1);
171
+ border: 1px solid rgba(63, 185, 80, 0.3);
172
+ border-radius: 20px;
173
+ font-size: 13px;
174
+ color: var(--accent-green);
175
+ }
176
+ .ws-status {
177
+ display: flex;
178
+ align-items: center;
179
+ gap: 6px;
180
+ padding: 6px 12px;
181
+ border-radius: 16px;
182
+ font-size: 12px;
183
+ font-weight: 500;
184
+ transition: all 0.3s ease;
185
+ }
186
+ .ws-status.connected {
187
+ background: rgba(63, 185, 80, 0.15);
188
+ border: 1px solid rgba(63, 185, 80, 0.4);
189
+ color: var(--accent-green);
190
+ }
191
+ .ws-status.connected i { animation: pulse 2s infinite; }
192
+ .ws-status.disconnected {
193
+ background: rgba(248, 81, 73, 0.15);
194
+ border: 1px solid rgba(248, 81, 73, 0.4);
195
+ color: var(--accent-red);
196
+ }
197
+ @keyframes pulse {
198
+ 0%, 100% { opacity: 1; }
199
+ 50% { opacity: 0.5; }
200
+ }
201
+ .status-dot {
202
+ width: 8px; height: 8px;
203
+ background: var(--accent-green);
204
+ border-radius: 50%;
205
+ animation: pulse 2s infinite;
206
+ }
207
+ /* Main Content */
208
+ .main-content {
209
+ max-width: 1800px;
210
+ margin: 0 auto;
211
+ padding: 32px;
212
+ }
213
+ /* Stats Row */
214
+ .stats-row {
215
+ display: grid;
216
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
217
+ gap: 20px;
218
+ margin-bottom: 32px;
219
+ }
220
+ .stat-card {
221
+ background: var(--bg-card);
222
+ border: 1px solid var(--border-color);
223
+ border-radius: 16px;
224
+ padding: 24px;
225
+ position: relative;
226
+ overflow: hidden;
227
+ transition: all 0.3s;
228
+ }
229
+ .stat-card:hover {
230
+ transform: translateY(-4px);
231
+ box-shadow: var(--shadow-glow);
232
+ border-color: var(--accent-blue);
233
+ }
234
+ .stat-card::before {
235
+ content: '';
236
+ position: absolute;
237
+ top: 0; left: 0; right: 0;
238
+ height: 3px;
239
+ background: var(--card-accent, var(--gradient-1));
240
+ }
241
+ .stat-icon {
242
+ width: 48px; height: 48px;
243
+ border-radius: 12px;
244
+ display: flex;
245
+ align-items: center;
246
+ justify-content: center;
247
+ font-size: 20px;
248
+ margin-bottom: 16px;
249
+ }
250
+ .stat-value {
251
+ font-size: 36px;
252
+ font-weight: 700;
253
+ margin-bottom: 4px;
254
+ }
255
+ .stat-label {
256
+ font-size: 14px;
257
+ color: var(--text-secondary);
258
+ }
259
+ .stat-change {
260
+ position: absolute;
261
+ top: 24px; right: 24px;
262
+ font-size: 12px;
263
+ padding: 4px 8px;
264
+ border-radius: 6px;
265
+ }
266
+ .stat-change.positive {
267
+ background: rgba(63, 185, 80, 0.15);
268
+ color: var(--accent-green);
269
+ }
270
+ /* Tabs */
271
+ .tabs {
272
+ display: flex;
273
+ gap: 4px;
274
+ margin-bottom: 24px;
275
+ background: var(--bg-secondary);
276
+ padding: 6px;
277
+ border-radius: 14px;
278
+ width: fit-content;
279
+ flex-wrap: wrap;
280
+ }
281
+ .tab {
282
+ padding: 10px 18px;
283
+ border-radius: 10px;
284
+ cursor: pointer;
285
+ font-size: 13px;
286
+ font-weight: 500;
287
+ color: var(--text-secondary);
288
+ transition: all 0.2s;
289
+ display: flex;
290
+ align-items: center;
291
+ gap: 8px;
292
+ white-space: nowrap;
293
+ }
294
+ .tab:hover {
295
+ color: var(--text-primary);
296
+ background: var(--bg-tertiary);
297
+ }
298
+ .tab.active {
299
+ background: var(--accent-blue);
300
+ color: white;
301
+ }
302
+ .tab .count {
303
+ background: rgba(255, 255, 255, 0.2);
304
+ padding: 2px 8px;
305
+ border-radius: 10px;
306
+ font-size: 11px;
307
+ }
308
+ .tab.active .count { background: rgba(255, 255, 255, 0.3); }
309
+ /* Category Filter */
310
+ .category-filter {
311
+ display: flex;
312
+ gap: 10px;
313
+ margin-bottom: 24px;
314
+ flex-wrap: wrap;
315
+ }
316
+ .category-chip {
317
+ padding: 8px 16px;
318
+ border-radius: 20px;
319
+ cursor: pointer;
320
+ font-size: 13px;
321
+ font-weight: 500;
322
+ border: 1px solid var(--border-color);
323
+ background: var(--bg-card);
324
+ color: var(--text-secondary);
325
+ transition: all 0.2s;
326
+ display: flex;
327
+ align-items: center;
328
+ gap: 8px;
329
+ }
330
+ .category-chip:hover {
331
+ border-color: var(--text-secondary);
332
+ color: var(--text-primary);
333
+ }
334
+ .category-chip.active {
335
+ border-color: var(--chip-color, var(--accent-blue));
336
+ background: rgba(88, 166, 255, 0.1);
337
+ color: var(--chip-color, var(--accent-blue));
338
+ }
339
+ .category-chip i { font-size: 14px; }
340
+ /* Search Bar */
341
+ .search-bar {
342
+ display: flex;
343
+ align-items: center;
344
+ gap: 12px;
345
+ background: var(--bg-card);
346
+ border: 1px solid var(--border-color);
347
+ border-radius: 12px;
348
+ padding: 12px 20px;
349
+ margin-bottom: 24px;
350
+ max-width: 400px;
351
+ }
352
+ .search-bar i { color: var(--text-secondary); }
353
+ .search-bar input {
354
+ flex: 1;
355
+ background: none;
356
+ border: none;
357
+ color: var(--text-primary);
358
+ font-size: 14px;
359
+ outline: none;
360
+ }
361
+ .search-bar input::placeholder { color: var(--text-muted); }
362
+ /* Agent Grid */
363
+ .agent-grid {
364
+ display: grid;
365
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
366
+ gap: 20px;
367
+ }
368
+ .agent-card {
369
+ background: var(--bg-card);
370
+ border: 1px solid var(--border-color);
371
+ border-radius: 16px;
372
+ padding: 24px;
373
+ transition: all 0.3s;
374
+ position: relative;
375
+ overflow: hidden;
376
+ }
377
+ .agent-card:hover {
378
+ transform: translateY(-2px);
379
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
380
+ }
381
+ .agent-card.disabled { opacity: 0.5; }
382
+ .agent-card-header {
383
+ display: flex;
384
+ justify-content: space-between;
385
+ align-items: flex-start;
386
+ margin-bottom: 16px;
387
+ }
388
+ .agent-icon {
389
+ width: 48px; height: 48px;
390
+ border-radius: 12px;
391
+ display: flex;
392
+ align-items: center;
393
+ justify-content: center;
394
+ font-size: 20px;
395
+ }
396
+ .agent-toggle {
397
+ position: relative;
398
+ width: 52px; height: 28px;
399
+ background: var(--bg-tertiary);
400
+ border-radius: 14px;
401
+ cursor: pointer;
402
+ transition: all 0.3s;
403
+ border: 1px solid var(--border-color);
404
+ }
405
+ .agent-toggle.active {
406
+ background: var(--accent-green);
407
+ border-color: var(--accent-green);
408
+ }
409
+ .agent-toggle::after {
410
+ content: '';
411
+ position: absolute;
412
+ top: 3px; left: 3px;
413
+ width: 20px; height: 20px;
414
+ background: white;
415
+ border-radius: 50%;
416
+ transition: all 0.3s;
417
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
418
+ }
419
+ .agent-toggle.active::after { left: 27px; }
420
+ .agent-name {
421
+ font-size: 16px;
422
+ font-weight: 600;
423
+ margin-bottom: 8px;
424
+ }
425
+ .agent-description {
426
+ font-size: 13px;
427
+ color: var(--text-secondary);
428
+ line-height: 1.5;
429
+ margin-bottom: 16px;
430
+ }
431
+ .agent-tags {
432
+ display: flex;
433
+ flex-wrap: wrap;
434
+ gap: 6px;
435
+ }
436
+ .agent-tag {
437
+ padding: 4px 10px;
438
+ background: var(--bg-tertiary);
439
+ border-radius: 6px;
440
+ font-size: 11px;
441
+ color: var(--text-secondary);
442
+ }
443
+ .agent-category-label {
444
+ position: absolute;
445
+ top: 0; left: 0; right: 0;
446
+ height: 3px;
447
+ }
448
+ /* Config Cards */
449
+ .config-card {
450
+ background: var(--bg-card);
451
+ border: 1px solid var(--border-color);
452
+ border-radius: 16px;
453
+ padding: 20px;
454
+ display: flex;
455
+ align-items: center;
456
+ gap: 16px;
457
+ transition: all 0.2s;
458
+ }
459
+ .config-card:hover { border-color: var(--accent-blue); }
460
+ .config-icon {
461
+ width: 48px; height: 48px;
462
+ border-radius: 12px;
463
+ display: flex;
464
+ align-items: center;
465
+ justify-content: center;
466
+ font-size: 20px;
467
+ flex-shrink: 0;
468
+ }
469
+ .config-info { flex: 1; }
470
+ .config-name {
471
+ font-weight: 600;
472
+ font-size: 15px;
473
+ margin-bottom: 4px;
474
+ }
475
+ .config-desc {
476
+ font-size: 12px;
477
+ color: var(--text-secondary);
478
+ }
479
+ .config-trigger {
480
+ font-size: 11px;
481
+ color: var(--accent-purple);
482
+ margin-top: 4px;
483
+ }
484
+ /* Section Headers */
485
+ .section-header {
486
+ display: flex;
487
+ justify-content: space-between;
488
+ align-items: center;
489
+ margin-bottom: 20px;
490
+ }
491
+ .section-title {
492
+ font-size: 18px;
493
+ font-weight: 600;
494
+ display: flex;
495
+ align-items: center;
496
+ gap: 12px;
497
+ }
498
+ .section-title i { color: var(--accent-blue); }
499
+ .section-actions {
500
+ display: flex;
501
+ gap: 12px;
502
+ }
503
+ .btn {
504
+ padding: 10px 20px;
505
+ border-radius: 10px;
506
+ font-size: 13px;
507
+ font-weight: 500;
508
+ cursor: pointer;
509
+ transition: all 0.2s;
510
+ display: flex;
511
+ align-items: center;
512
+ gap: 8px;
513
+ border: none;
514
+ }
515
+ .btn-primary {
516
+ background: var(--accent-blue);
517
+ color: white;
518
+ }
519
+ .btn-primary:hover {
520
+ background: #4a9aef;
521
+ box-shadow: 0 4px 15px rgba(88, 166, 255, 0.3);
522
+ }
523
+ .btn-secondary {
524
+ background: var(--bg-tertiary);
525
+ color: var(--text-primary);
526
+ border: 1px solid var(--border-color);
527
+ }
528
+ .btn-secondary:hover { background: var(--border-color); }
529
+ /* Tab Content */
530
+ .tab-content { display: none; }
531
+ .tab-content.active {
532
+ display: block;
533
+ animation: fadeIn 0.3s ease;
534
+ }
535
+ @keyframes fadeIn {
536
+ from { opacity: 0; }
537
+ to { opacity: 1; }
538
+ }
539
+ /* Timeline Styles */
540
+ .timeline-container {
541
+ background: var(--bg-card);
542
+ border: 1px solid var(--border-color);
543
+ border-radius: 16px;
544
+ padding: 24px;
545
+ max-height: 600px;
546
+ overflow-y: auto;
547
+ }
548
+ .timeline-filters {
549
+ display: flex;
550
+ gap: 8px;
551
+ margin-bottom: 20px;
552
+ flex-wrap: wrap;
553
+ align-items: center;
554
+ }
555
+ .timeline-filter-chip {
556
+ padding: 6px 14px;
557
+ border-radius: 16px;
558
+ font-size: 12px;
559
+ cursor: pointer;
560
+ border: 1px solid var(--border-color);
561
+ background: var(--bg-tertiary);
562
+ color: var(--text-secondary);
563
+ transition: all 0.2s;
564
+ display: flex;
565
+ align-items: center;
566
+ gap: 6px;
567
+ }
568
+ .timeline-filter-chip:hover,
569
+ .timeline-filter-chip.active {
570
+ border-color: var(--accent-blue);
571
+ color: var(--accent-blue);
572
+ background: rgba(88, 166, 255, 0.1);
573
+ }
574
+ .timeline-filter-divider {
575
+ width: 1px; height: 24px;
576
+ background: var(--border-color);
577
+ margin: 0 8px;
578
+ }
579
+ .timeline-time-filter {
580
+ padding: 6px 14px;
581
+ border-radius: 16px;
582
+ font-size: 12px;
583
+ cursor: pointer;
584
+ border: 1px solid var(--border-color);
585
+ background: var(--bg-tertiary);
586
+ color: var(--text-secondary);
587
+ transition: all 0.2s;
588
+ }
589
+ .timeline-time-filter:hover,
590
+ .timeline-time-filter.active {
591
+ border-color: var(--accent-purple);
592
+ color: var(--accent-purple);
593
+ background: rgba(163, 113, 247, 0.1);
594
+ }
595
+ .timeline-chain {
596
+ margin-bottom: 20px;
597
+ border: 1px solid var(--border-color);
598
+ border-radius: 12px;
599
+ overflow: hidden;
600
+ background: var(--bg-secondary);
601
+ transition: all 0.3s;
602
+ }
603
+ .timeline-chain.collapsed .timeline-children { display: none; }
604
+ .timeline-chain.collapsed .timeline-request { border-radius: 12px; }
605
+ .timeline-collapse-btn {
606
+ width: 24px; height: 24px;
607
+ border-radius: 6px;
608
+ background: rgba(255, 255, 255, 0.1);
609
+ border: none;
610
+ color: var(--text-secondary);
611
+ cursor: pointer;
612
+ display: flex;
613
+ align-items: center;
614
+ justify-content: center;
615
+ transition: all 0.2s;
616
+ font-size: 12px;
617
+ }
618
+ .timeline-collapse-btn:hover {
619
+ background: rgba(255, 255, 255, 0.2);
620
+ color: var(--text-primary);
621
+ }
622
+ .timeline-chain.collapsed .timeline-collapse-btn i { transform: rotate(-90deg); }
623
+ .timeline-children-count {
624
+ font-size: 11px;
625
+ padding: 2px 8px;
626
+ border-radius: 10px;
627
+ background: rgba(88, 166, 255, 0.2);
628
+ color: var(--accent-blue);
629
+ margin-left: 8px;
630
+ }
631
+ .timeline-request {
632
+ background: linear-gradient(135deg, rgba(63, 185, 80, 0.15) 0%, rgba(63, 185, 80, 0.05) 100%);
633
+ border-left: 4px solid var(--accent-green);
634
+ padding: 16px 20px;
635
+ }
636
+ .timeline-request-header {
637
+ display: flex;
638
+ align-items: center;
639
+ gap: 12px;
640
+ margin-bottom: 8px;
641
+ }
642
+ .timeline-request-icon {
643
+ width: 32px; height: 32px;
644
+ border-radius: 8px;
645
+ background: rgba(63, 185, 80, 0.2);
646
+ color: var(--accent-green);
647
+ display: flex;
648
+ align-items: center;
649
+ justify-content: center;
650
+ }
651
+ .timeline-request-title {
652
+ font-weight: 600;
653
+ font-size: 15px;
654
+ flex: 1;
655
+ }
656
+ .timeline-request-time {
657
+ font-size: 11px;
658
+ color: var(--text-muted);
659
+ }
660
+ .timeline-request-goal {
661
+ font-size: 13px;
662
+ color: var(--text-secondary);
663
+ padding-left: 44px;
664
+ }
665
+ .timeline-children { padding: 8px 0; }
666
+ .timeline-event {
667
+ display: flex;
668
+ gap: 14px;
669
+ padding: 12px 20px;
670
+ position: relative;
671
+ transition: background 0.2s;
672
+ }
673
+ .timeline-event:hover { background: rgba(255, 255, 255, 0.02); }
674
+ .timeline-connector {
675
+ position: absolute;
676
+ left: 35px; top: 0; bottom: 0;
677
+ width: 2px;
678
+ background: var(--border-color);
679
+ }
680
+ .timeline-event:last-child .timeline-connector { height: 50%; }
681
+ .timeline-dot {
682
+ width: 20px; height: 20px;
683
+ border-radius: 50%;
684
+ flex-shrink: 0;
685
+ z-index: 1;
686
+ display: flex;
687
+ align-items: center;
688
+ justify-content: center;
689
+ font-size: 10px;
690
+ color: white;
691
+ }
692
+ .timeline-content {
693
+ flex: 1;
694
+ min-width: 0;
695
+ }
696
+ .timeline-type-badge {
697
+ display: inline-flex;
698
+ align-items: center;
699
+ gap: 6px;
700
+ font-size: 10px;
701
+ font-weight: 600;
702
+ text-transform: uppercase;
703
+ letter-spacing: 0.5px;
704
+ padding: 3px 10px;
705
+ border-radius: 12px;
706
+ margin-bottom: 6px;
707
+ }
708
+ .timeline-summary {
709
+ font-size: 14px;
710
+ margin-bottom: 6px;
711
+ line-height: 1.5;
712
+ }
713
+ .timeline-summary.truncated { cursor: pointer; }
714
+ .timeline-summary.truncated:hover { color: var(--accent-blue); }
715
+ .timeline-summary.expanded {
716
+ white-space: pre-wrap;
717
+ word-break: break-word;
718
+ background: var(--bg-tertiary);
719
+ padding: 8px 12px;
720
+ border-radius: 6px;
721
+ font-family: 'SF Mono', Monaco, monospace;
722
+ font-size: 12px;
723
+ }
724
+ .timeline-details {
725
+ font-size: 12px;
726
+ color: var(--text-secondary);
727
+ margin-bottom: 8px;
728
+ padding: 8px 12px;
729
+ background: var(--bg-tertiary);
730
+ border-radius: 8px;
731
+ }
732
+ .timeline-meta {
733
+ display: flex;
734
+ gap: 12px;
735
+ align-items: center;
736
+ flex-wrap: wrap;
737
+ }
738
+ .timeline-time {
739
+ font-size: 11px;
740
+ color: var(--text-muted);
741
+ }
742
+ .timeline-confidence {
743
+ font-size: 10px;
744
+ padding: 3px 10px;
745
+ border-radius: 10px;
746
+ background: var(--bg-tertiary);
747
+ color: var(--text-secondary);
748
+ }
749
+ .timeline-confidence.high {
750
+ background: rgba(63, 185, 80, 0.15);
751
+ color: var(--accent-green);
752
+ }
753
+ .timeline-anchor-badge {
754
+ font-size: 10px;
755
+ padding: 3px 10px;
756
+ border-radius: 10px;
757
+ background: rgba(57, 212, 212, 0.2);
758
+ color: var(--accent-cyan);
759
+ font-weight: 600;
760
+ display: flex;
761
+ align-items: center;
762
+ gap: 4px;
763
+ }
764
+ .timeline-outcome-badge {
765
+ font-size: 11px;
766
+ padding: 4px 12px;
767
+ border-radius: 10px;
768
+ font-weight: 600;
769
+ display: flex;
770
+ align-items: center;
771
+ gap: 6px;
772
+ text-transform: uppercase;
773
+ letter-spacing: 0.5px;
774
+ }
775
+ .timeline-outcome-badge.success {
776
+ background: rgba(63, 185, 80, 0.2);
777
+ color: var(--accent-green);
778
+ border: 1px solid rgba(63, 185, 80, 0.4);
779
+ }
780
+ .timeline-outcome-badge.failed {
781
+ background: rgba(248, 81, 73, 0.2);
782
+ color: var(--accent-red);
783
+ border: 1px solid rgba(248, 81, 73, 0.4);
784
+ }
785
+ .timeline-event.anchor-event {
786
+ background: linear-gradient(135deg, rgba(57, 212, 212, 0.08) 0%, rgba(163, 113, 247, 0.05) 100%);
787
+ border-left: 3px solid var(--accent-cyan);
788
+ margin: 4px 8px;
789
+ border-radius: 8px;
790
+ }
791
+ .timeline-event.anchor-event .timeline-summary {
792
+ color: var(--accent-cyan);
793
+ font-weight: 500;
794
+ }
795
+ .timeline-file-link {
796
+ display: inline-flex;
797
+ align-items: center;
798
+ gap: 6px;
799
+ padding: 4px 10px;
800
+ background: var(--bg-tertiary);
801
+ border-radius: 6px;
802
+ font-size: 11px;
803
+ font-family: 'SF Mono', Monaco, monospace;
804
+ color: var(--accent-blue);
805
+ cursor: pointer;
806
+ transition: all 0.2s;
807
+ margin-top: 6px;
808
+ max-width: 100%;
809
+ overflow: hidden;
810
+ text-overflow: ellipsis;
811
+ white-space: nowrap;
812
+ }
813
+ .timeline-file-link:hover { background: rgba(88, 166, 255, 0.15); }
814
+ .timeline-file-link i { flex-shrink: 0; }
815
+ .timeline-file-link.copied {
816
+ background: rgba(63, 185, 80, 0.2);
817
+ color: var(--accent-green);
818
+ }
819
+ .timeline-event.outcome-event {
820
+ background: linear-gradient(135deg, rgba(63, 185, 80, 0.08) 0%, transparent 100%);
821
+ border-left: 3px solid var(--accent-green);
822
+ margin: 4px 8px;
823
+ border-radius: 8px;
824
+ }
825
+ .timeline-event.outcome-event.failed {
826
+ background: linear-gradient(135deg, rgba(248, 81, 73, 0.08) 0%, transparent 100%);
827
+ border-left-color: var(--accent-red);
828
+ }
829
+ .dot-user_request { background: var(--accent-green); }
830
+ .dot-decision { background: var(--accent-purple); }
831
+ .dot-observation { background: var(--accent-yellow); }
832
+ .dot-action { background: var(--accent-blue); }
833
+ .dot-outcome { background: var(--accent-cyan); }
834
+ .dot-anchor { background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple)); }
835
+ .dot-error { background: var(--accent-red); }
836
+ .badge-decision { background: rgba(163, 113, 247, 0.15); color: var(--accent-purple); }
837
+ .badge-observation { background: rgba(210, 153, 34, 0.15); color: var(--accent-yellow); }
838
+ .badge-action { background: rgba(88, 166, 255, 0.15); color: var(--accent-blue); }
839
+ .badge-outcome { background: rgba(57, 212, 212, 0.15); color: var(--accent-cyan); }
840
+ .badge-anchor { background: rgba(57, 212, 212, 0.2); color: var(--accent-cyan); }
841
+ .badge-error { background: rgba(248, 81, 73, 0.15); color: var(--accent-red); }
842
+ .timeline-empty {
843
+ text-align: center;
844
+ padding: 60px;
845
+ color: var(--text-secondary);
846
+ }
847
+ .timeline-empty i {
848
+ font-size: 48px;
849
+ margin-bottom: 16px;
850
+ opacity: 0.3;
851
+ }
852
+ /* Memory Cards */
853
+ .memory-card {
854
+ background: var(--bg-card);
855
+ border: 1px solid var(--border-color);
856
+ border-radius: 12px;
857
+ padding: 16px;
858
+ margin-bottom: 12px;
859
+ transition: all 0.2s;
860
+ }
861
+ .memory-card:hover {
862
+ border-color: var(--accent-blue);
863
+ transform: translateX(4px);
864
+ }
865
+ .memory-header {
866
+ display: flex;
867
+ justify-content: space-between;
868
+ align-items: flex-start;
869
+ margin-bottom: 12px;
870
+ }
871
+ .memory-type-badge {
872
+ padding: 4px 12px;
873
+ border-radius: 12px;
874
+ font-size: 11px;
875
+ font-weight: 600;
876
+ text-transform: uppercase;
877
+ }
878
+ .memory-type-decision { background: rgba(163, 113, 247, 0.2); color: var(--accent-purple); }
879
+ .memory-type-error { background: rgba(248, 81, 73, 0.2); color: var(--accent-red); }
880
+ .memory-type-code { background: rgba(88, 166, 255, 0.2); color: var(--accent-blue); }
881
+ .memory-type-preference { background: rgba(240, 136, 62, 0.2); color: var(--accent-orange); }
882
+ .memory-type-chunk { background: rgba(139, 148, 158, 0.2); color: var(--text-secondary); }
883
+ .memory-type-session { background: rgba(63, 185, 80, 0.2); color: var(--accent-green); }
884
+ .memory-content {
885
+ font-size: 14px;
886
+ line-height: 1.6;
887
+ color: var(--text-primary);
888
+ margin-bottom: 12px;
889
+ }
890
+ .memory-meta {
891
+ display: flex;
892
+ gap: 16px;
893
+ font-size: 12px;
894
+ color: var(--text-muted);
895
+ }
896
+ .memory-meta span {
897
+ display: flex;
898
+ align-items: center;
899
+ gap: 4px;
900
+ }
901
+ .memory-card { cursor: pointer; transition: transform 0.15s, box-shadow 0.15s; }
902
+ .memory-card:active { transform: scale(0.98); }
903
+
904
+ /* Memory Detail Modal */
905
+ .modal-overlay {
906
+ display: none;
907
+ position: fixed;
908
+ top: 0;
909
+ left: 0;
910
+ right: 0;
911
+ bottom: 0;
912
+ background: rgba(0, 0, 0, 0.7);
913
+ z-index: 1000;
914
+ align-items: center;
915
+ justify-content: center;
916
+ padding: 20px;
917
+ }
918
+ .modal-overlay.show { display: flex; }
919
+ .modal-content {
920
+ background: var(--bg-secondary);
921
+ border-radius: 16px;
922
+ max-width: 700px;
923
+ width: 100%;
924
+ max-height: 80vh;
925
+ overflow: hidden;
926
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
927
+ border: 1px solid var(--border-color);
928
+ }
929
+ .modal-header {
930
+ display: flex;
931
+ justify-content: space-between;
932
+ align-items: center;
933
+ padding: 20px 24px;
934
+ border-bottom: 1px solid var(--border-color);
935
+ background: var(--bg-tertiary);
936
+ }
937
+ .modal-header h3 {
938
+ margin: 0;
939
+ font-size: 18px;
940
+ display: flex;
941
+ align-items: center;
942
+ gap: 10px;
943
+ }
944
+ .modal-close {
945
+ background: none;
946
+ border: none;
947
+ color: var(--text-secondary);
948
+ font-size: 24px;
949
+ cursor: pointer;
950
+ padding: 4px 8px;
951
+ border-radius: 6px;
952
+ }
953
+ .modal-close:hover { background: var(--bg-hover); color: var(--text-primary); }
954
+ .modal-body {
955
+ padding: 24px;
956
+ overflow-y: auto;
957
+ max-height: calc(80vh - 140px);
958
+ }
959
+ .modal-body pre {
960
+ background: var(--bg-tertiary);
961
+ padding: 16px;
962
+ border-radius: 8px;
963
+ overflow-x: auto;
964
+ white-space: pre-wrap;
965
+ word-wrap: break-word;
966
+ font-family: 'Monaco', 'Menlo', monospace;
967
+ font-size: 13px;
968
+ line-height: 1.6;
969
+ color: var(--text-primary);
970
+ margin: 16px 0;
971
+ }
972
+ .modal-meta {
973
+ display: grid;
974
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
975
+ gap: 16px;
976
+ margin-top: 20px;
977
+ padding-top: 20px;
978
+ border-top: 1px solid var(--border-color);
979
+ }
980
+ .modal-meta-item {
981
+ display: flex;
982
+ flex-direction: column;
983
+ gap: 4px;
984
+ }
985
+ .modal-meta-item label {
986
+ font-size: 11px;
987
+ text-transform: uppercase;
988
+ color: var(--text-muted);
989
+ font-weight: 600;
990
+ }
991
+ .modal-meta-item span {
992
+ font-size: 14px;
993
+ color: var(--text-primary);
994
+ }
995
+ /* Pattern Cards */
996
+ .pattern-card {
997
+ background: var(--bg-card);
998
+ border: 1px solid var(--border-color);
999
+ border-radius: 12px;
1000
+ padding: 20px;
1001
+ margin-bottom: 16px;
1002
+ }
1003
+ .pattern-card:hover { border-color: var(--accent-purple); }
1004
+ .pattern-header {
1005
+ display: flex;
1006
+ justify-content: space-between;
1007
+ align-items: center;
1008
+ margin-bottom: 12px;
1009
+ }
1010
+ .pattern-name {
1011
+ font-size: 16px;
1012
+ font-weight: 600;
1013
+ color: var(--accent-purple);
1014
+ }
1015
+ .pattern-success-rate {
1016
+ padding: 4px 12px;
1017
+ border-radius: 12px;
1018
+ font-size: 12px;
1019
+ font-weight: 600;
1020
+ }
1021
+ .pattern-success-rate.high {
1022
+ background: rgba(63, 185, 80, 0.2);
1023
+ color: var(--accent-green);
1024
+ }
1025
+ .pattern-success-rate.medium {
1026
+ background: rgba(210, 153, 34, 0.2);
1027
+ color: var(--accent-yellow);
1028
+ }
1029
+ .pattern-solution {
1030
+ font-size: 13px;
1031
+ line-height: 1.6;
1032
+ color: var(--text-secondary);
1033
+ margin-bottom: 12px;
1034
+ padding: 12px;
1035
+ background: var(--bg-tertiary);
1036
+ border-radius: 8px;
1037
+ }
1038
+ .pattern-tags {
1039
+ display: flex;
1040
+ gap: 8px;
1041
+ flex-wrap: wrap;
1042
+ }
1043
+ .pattern-tag {
1044
+ padding: 4px 10px;
1045
+ background: rgba(163, 113, 247, 0.15);
1046
+ color: var(--accent-purple);
1047
+ border-radius: 6px;
1048
+ font-size: 11px;
1049
+ }
1050
+ /* Embedding Visualizer */
1051
+ .embedding-visualizer {
1052
+ background: var(--bg-card);
1053
+ border: 1px solid var(--border-color);
1054
+ border-radius: 16px;
1055
+ padding: 24px;
1056
+ margin-bottom: 24px;
1057
+ }
1058
+ .embedding-visualizer h3 {
1059
+ font-size: 16px;
1060
+ margin-bottom: 20px;
1061
+ display: flex;
1062
+ align-items: center;
1063
+ gap: 10px;
1064
+ }
1065
+ .embedding-visualizer h3 i { color: var(--accent-cyan); }
1066
+ .pipeline {
1067
+ display: flex;
1068
+ align-items: center;
1069
+ justify-content: center;
1070
+ gap: 16px;
1071
+ margin-bottom: 24px;
1072
+ }
1073
+ .pipeline-stage {
1074
+ padding: 16px 24px;
1075
+ background: var(--bg-tertiary);
1076
+ border: 1px solid var(--border-color);
1077
+ border-radius: 12px;
1078
+ text-align: center;
1079
+ min-width: 120px;
1080
+ transition: all 0.3s;
1081
+ }
1082
+ .pipeline-stage.active {
1083
+ border-color: var(--accent-cyan);
1084
+ box-shadow: 0 0 20px rgba(57, 212, 212, 0.2);
1085
+ }
1086
+ .pipeline-stage-icon {
1087
+ font-size: 24px;
1088
+ margin-bottom: 8px;
1089
+ }
1090
+ .pipeline-stage-label {
1091
+ font-size: 12px;
1092
+ color: var(--text-secondary);
1093
+ }
1094
+ .pipeline-arrow {
1095
+ font-size: 24px;
1096
+ color: var(--accent-cyan);
1097
+ animation: arrowPulse 1.5s infinite;
1098
+ }
1099
+ @keyframes arrowPulse {
1100
+ 0%, 100% { opacity: 0.5; transform: translateX(0); }
1101
+ 50% { opacity: 1; transform: translateX(4px); }
1102
+ }
1103
+ .embedding-metrics {
1104
+ display: flex;
1105
+ justify-content: center;
1106
+ gap: 32px;
1107
+ margin-bottom: 24px;
1108
+ }
1109
+ .embedding-metric {
1110
+ text-align: center;
1111
+ }
1112
+ .embedding-metric-value {
1113
+ font-size: 24px;
1114
+ font-weight: 700;
1115
+ color: var(--accent-cyan);
1116
+ }
1117
+ .embedding-metric-label {
1118
+ font-size: 12px;
1119
+ color: var(--text-secondary);
1120
+ }
1121
+ .vector-preview {
1122
+ height: 60px;
1123
+ display: flex;
1124
+ align-items: flex-end;
1125
+ gap: 2px;
1126
+ padding: 12px;
1127
+ background: var(--bg-tertiary);
1128
+ border-radius: 8px;
1129
+ overflow: hidden;
1130
+ }
1131
+ .vector-bar {
1132
+ flex: 1;
1133
+ background: var(--accent-cyan);
1134
+ border-radius: 2px 2px 0 0;
1135
+ min-width: 2px;
1136
+ transition: height 0.3s;
1137
+ }
1138
+ .embedding-input-preview {
1139
+ padding: 12px;
1140
+ background: var(--bg-tertiary);
1141
+ border-radius: 8px;
1142
+ margin-bottom: 16px;
1143
+ font-size: 13px;
1144
+ color: var(--text-secondary);
1145
+ font-family: 'SF Mono', Monaco, monospace;
1146
+ max-height: 80px;
1147
+ overflow: hidden;
1148
+ text-overflow: ellipsis;
1149
+ }
1150
+ /* Activity Feed */
1151
+ .activity-feed {
1152
+ background: var(--bg-card);
1153
+ border: 1px solid var(--border-color);
1154
+ border-radius: 16px;
1155
+ max-height: 500px;
1156
+ overflow-y: auto;
1157
+ }
1158
+ .activity-header {
1159
+ padding: 16px 20px;
1160
+ border-bottom: 1px solid var(--border-color);
1161
+ display: flex;
1162
+ justify-content: space-between;
1163
+ align-items: center;
1164
+ position: sticky;
1165
+ top: 0;
1166
+ background: var(--bg-card);
1167
+ z-index: 1;
1168
+ }
1169
+ .activity-header h3 {
1170
+ font-size: 14px;
1171
+ display: flex;
1172
+ align-items: center;
1173
+ gap: 8px;
1174
+ }
1175
+ .activity-header h3 i { color: var(--accent-green); }
1176
+ .activity-live-dot {
1177
+ width: 8px; height: 8px;
1178
+ background: var(--accent-green);
1179
+ border-radius: 50%;
1180
+ animation: pulse 2s infinite;
1181
+ }
1182
+ .activity-item {
1183
+ padding: 12px 20px;
1184
+ border-bottom: 1px solid var(--border-color);
1185
+ display: flex;
1186
+ gap: 12px;
1187
+ align-items: flex-start;
1188
+ animation: slideIn 0.3s ease;
1189
+ }
1190
+ @keyframes slideIn {
1191
+ from { opacity: 0; transform: translateX(-20px); }
1192
+ to { opacity: 1; transform: translateX(0); }
1193
+ }
1194
+ .activity-item:last-child { border-bottom: none; }
1195
+ .activity-icon {
1196
+ width: 32px; height: 32px;
1197
+ border-radius: 8px;
1198
+ display: flex;
1199
+ align-items: center;
1200
+ justify-content: center;
1201
+ font-size: 14px;
1202
+ flex-shrink: 0;
1203
+ }
1204
+ .activity-icon.context { background: rgba(88, 166, 255, 0.2); color: var(--accent-blue); }
1205
+ .activity-icon.embed { background: rgba(57, 212, 212, 0.2); color: var(--accent-cyan); }
1206
+ .activity-icon.store { background: rgba(63, 185, 80, 0.2); color: var(--accent-green); }
1207
+ .activity-icon.search { background: rgba(163, 113, 247, 0.2); color: var(--accent-purple); }
1208
+ .activity-content { flex: 1; min-width: 0; }
1209
+ .activity-title {
1210
+ font-size: 13px;
1211
+ font-weight: 500;
1212
+ margin-bottom: 2px;
1213
+ }
1214
+ .activity-detail {
1215
+ font-size: 12px;
1216
+ color: var(--text-secondary);
1217
+ overflow: hidden;
1218
+ text-overflow: ellipsis;
1219
+ white-space: nowrap;
1220
+ }
1221
+ .activity-time {
1222
+ font-size: 11px;
1223
+ color: var(--text-muted);
1224
+ white-space: nowrap;
1225
+ }
1226
+ /* Semantic Search */
1227
+ .search-container {
1228
+ background: var(--bg-card);
1229
+ border: 1px solid var(--border-color);
1230
+ border-radius: 16px;
1231
+ padding: 24px;
1232
+ }
1233
+ .search-input-wrapper {
1234
+ display: flex;
1235
+ gap: 12px;
1236
+ margin-bottom: 24px;
1237
+ }
1238
+ .search-input-wrapper input {
1239
+ flex: 1;
1240
+ padding: 14px 20px;
1241
+ background: var(--bg-tertiary);
1242
+ border: 1px solid var(--border-color);
1243
+ border-radius: 12px;
1244
+ color: var(--text-primary);
1245
+ font-size: 14px;
1246
+ outline: none;
1247
+ transition: all 0.2s;
1248
+ }
1249
+ .search-input-wrapper input:focus {
1250
+ border-color: var(--accent-blue);
1251
+ box-shadow: 0 0 20px rgba(88, 166, 255, 0.1);
1252
+ }
1253
+ .search-results {
1254
+ max-height: 500px;
1255
+ overflow-y: auto;
1256
+ }
1257
+ .search-result {
1258
+ padding: 16px;
1259
+ background: var(--bg-tertiary);
1260
+ border-radius: 12px;
1261
+ margin-bottom: 12px;
1262
+ border-left: 3px solid transparent;
1263
+ transition: all 0.2s;
1264
+ }
1265
+ .search-result:hover {
1266
+ border-left-color: var(--accent-blue);
1267
+ transform: translateX(4px);
1268
+ }
1269
+ .search-result-header {
1270
+ display: flex;
1271
+ justify-content: space-between;
1272
+ align-items: center;
1273
+ margin-bottom: 8px;
1274
+ }
1275
+ .search-result-score {
1276
+ font-size: 12px;
1277
+ padding: 4px 10px;
1278
+ border-radius: 10px;
1279
+ background: rgba(88, 166, 255, 0.2);
1280
+ color: var(--accent-blue);
1281
+ font-weight: 600;
1282
+ }
1283
+ .search-result-content {
1284
+ font-size: 14px;
1285
+ line-height: 1.6;
1286
+ color: var(--text-primary);
1287
+ }
1288
+ /* Loading States */
1289
+ .loading {
1290
+ display: flex;
1291
+ justify-content: center;
1292
+ align-items: center;
1293
+ padding: 60px;
1294
+ }
1295
+ .spinner {
1296
+ width: 40px; height: 40px;
1297
+ border: 3px solid var(--border-color);
1298
+ border-top-color: var(--accent-blue);
1299
+ border-radius: 50%;
1300
+ animation: spin 1s linear infinite;
1301
+ }
1302
+ @keyframes spin { to { transform: rotate(360deg); } }
1303
+ .loading-overlay {
1304
+ position: fixed;
1305
+ top: 0; left: 0; right: 0; bottom: 0;
1306
+ background: rgba(10, 10, 15, 0.8);
1307
+ display: flex;
1308
+ flex-direction: column;
1309
+ justify-content: center;
1310
+ align-items: center;
1311
+ z-index: 1000;
1312
+ opacity: 0;
1313
+ pointer-events: none;
1314
+ transition: opacity 0.3s;
1315
+ }
1316
+ .loading-overlay.show {
1317
+ opacity: 1;
1318
+ pointer-events: auto;
1319
+ }
1320
+ .loading-text {
1321
+ margin-top: 16px;
1322
+ color: var(--text-secondary);
1323
+ font-size: 14px;
1324
+ }
1325
+ /* Toast Notifications */
1326
+ .toast-container {
1327
+ position: fixed;
1328
+ bottom: 24px; right: 24px;
1329
+ z-index: 1000;
1330
+ }
1331
+ .toast {
1332
+ background: var(--bg-card);
1333
+ border: 1px solid var(--border-color);
1334
+ border-radius: 12px;
1335
+ padding: 16px 24px;
1336
+ margin-top: 12px;
1337
+ display: flex;
1338
+ align-items: center;
1339
+ gap: 12px;
1340
+ animation: toastIn 0.3s ease;
1341
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
1342
+ }
1343
+ @keyframes toastIn {
1344
+ from { opacity: 0; transform: translateX(100px); }
1345
+ to { opacity: 1; transform: translateX(0); }
1346
+ }
1347
+ .toast.success { border-left: 4px solid var(--accent-green); }
1348
+ .toast.error { border-left: 4px solid var(--accent-red); }
1349
+ .toast.info { border-left: 4px solid var(--accent-blue); }
1350
+ /* Responsive */
1351
+ @media (max-width: 768px) {
1352
+ .header-content { flex-direction: column; gap: 16px; }
1353
+ .stats-row { grid-template-columns: repeat(2, 1fr); }
1354
+ .agent-grid { grid-template-columns: 1fr; }
1355
+ .tabs { width: 100%; overflow-x: auto; }
1356
+ .category-filter { overflow-x: auto; flex-wrap: nowrap; padding-bottom: 8px; }
1357
+ .pipeline { flex-direction: column; }
1358
+ .pipeline-arrow { transform: rotate(90deg); }
1359
+ }
1360
+ /* Scrollbar */
1361
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
1362
+ ::-webkit-scrollbar-track { background: var(--bg-secondary); }
1363
+ ::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 4px; }
1364
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
1365
+ /* Two Column Layout for Activity + Content */
1366
+ .two-column {
1367
+ display: grid;
1368
+ grid-template-columns: 1fr 350px;
1369
+ gap: 24px;
1370
+ }
1371
+ @media (max-width: 1200px) {
1372
+ .two-column { grid-template-columns: 1fr; }
1373
+ }
1374
+ </style>
1375
+ </head>
1376
+ <body>
1377
+ <div class="bg-animation"></div>
1378
+ <div class="loading-overlay" id="loadingOverlay">
1379
+ <div class="spinner"></div>
1380
+ <div class="loading-text" id="loadingText">Loading project data...</div>
1381
+ </div>
1382
+
1383
+ <header class="header">
1384
+ <div class="header-content">
1385
+ <div class="logo">
1386
+ <div class="logo-icon"><i class="fas fa-brain"></i></div>
1387
+ <div>
1388
+ <div class="logo-text">Claude Memory</div>
1389
+ <div class="logo-subtitle">Unified Control Center v2.1</div>
1390
+ </div>
1391
+ </div>
1392
+ <div class="header-controls">
1393
+ <div class="project-dropdown">
1394
+ <button class="project-btn" onclick="toggleProjectMenu()">
1395
+ <i class="fas fa-folder"></i>
1396
+ <span id="currentProjectName">Select Project</span>
1397
+ <i class="fas fa-chevron-down" style="margin-left: auto;"></i>
1398
+ </button>
1399
+ <div class="project-menu" id="projectMenu"></div>
1400
+ </div>
1401
+ <div class="status-indicator" id="statusIndicator">
1402
+ <span class="status-dot"></span>
1403
+ <span id="statusText">Connected</span>
1404
+ </div>
1405
+ <div class="ws-status disconnected" id="wsStatus" title="Connecting...">
1406
+ <i class="fas fa-bolt"></i>
1407
+ <span>Live</span>
1408
+ </div>
1409
+ </div>
1410
+ </div>
1411
+ </header>
1412
+
1413
+ <main class="main-content">
1414
+ <!-- Stats Row -->
1415
+ <div class="stats-row">
1416
+ <div class="stat-card" style="--card-accent: var(--gradient-1)">
1417
+ <div class="stat-icon" style="background: rgba(88, 166, 255, 0.15); color: var(--accent-blue);"><i class="fas fa-robot"></i></div>
1418
+ <div class="stat-value" id="enabledAgentsCount">0</div>
1419
+ <div class="stat-label">Active Agents</div>
1420
+ <div class="stat-change positive" id="agentsTotalCount">/ 0 total</div>
1421
+ </div>
1422
+ <div class="stat-card" style="--card-accent: var(--gradient-4)">
1423
+ <div class="stat-icon" style="background: rgba(63, 185, 80, 0.15); color: var(--accent-green);"><i class="fas fa-plug"></i></div>
1424
+ <div class="stat-value" id="enabledMcpsCount">0</div>
1425
+ <div class="stat-label">MCP Servers</div>
1426
+ <div class="stat-change positive" id="mcpsTotalCount">/ 0 total</div>
1427
+ </div>
1428
+ <div class="stat-card" style="--card-accent: var(--gradient-2)">
1429
+ <div class="stat-icon" style="background: rgba(163, 113, 247, 0.15); color: var(--accent-purple);"><i class="fas fa-bolt"></i></div>
1430
+ <div class="stat-value" id="enabledHooksCount">0</div>
1431
+ <div class="stat-label">Active Hooks</div>
1432
+ <div class="stat-change positive" id="hooksTotalCount">/ 0 total</div>
1433
+ </div>
1434
+ <div class="stat-card" style="--card-accent: var(--gradient-3)">
1435
+ <div class="stat-icon" style="background: rgba(210, 153, 34, 0.15); color: var(--accent-yellow);"><i class="fas fa-brain"></i></div>
1436
+ <div class="stat-value" id="memoriesCount">0</div>
1437
+ <div class="stat-label">Memories</div>
1438
+ <div class="stat-change positive" id="memoriesChange">+0 today</div>
1439
+ </div>
1440
+ <div class="stat-card" style="--card-accent: linear-gradient(135deg, #39d4d4 0%, #a371f7 100%)">
1441
+ <div class="stat-icon" style="background: rgba(57, 212, 212, 0.15); color: var(--accent-cyan);"><i class="fas fa-code"></i></div>
1442
+ <div class="stat-value" id="patternsCount">0</div>
1443
+ <div class="stat-label">Patterns</div>
1444
+ </div>
1445
+ <div class="stat-card" style="--card-accent: linear-gradient(135deg, #f85149 0%, #ff7b72 100%)">
1446
+ <div class="stat-icon" style="background: rgba(248, 81, 73, 0.15); color: var(--accent-red);"><i class="fas fa-clock"></i></div>
1447
+ <div class="stat-value" id="eventsCount">0</div>
1448
+ <div class="stat-label">Timeline Events</div>
1449
+ </div>
1450
+ </div>
1451
+
1452
+ <!-- Tabs -->
1453
+ <div class="tabs">
1454
+ <div class="tab active" data-tab="agents" onclick="switchTab('agents')">
1455
+ <i class="fas fa-robot"></i> Agents <span class="count" id="agentsTabCount">0</span>
1456
+ </div>
1457
+ <div class="tab" data-tab="mcps" onclick="switchTab('mcps')">
1458
+ <i class="fas fa-plug"></i> MCP Servers <span class="count" id="mcpsTabCount">0</span>
1459
+ </div>
1460
+ <div class="tab" data-tab="hooks" onclick="switchTab('hooks')">
1461
+ <i class="fas fa-bolt"></i> Hooks <span class="count" id="hooksTabCount">0</span>
1462
+ </div>
1463
+ <div class="tab" data-tab="timeline" onclick="switchTab('timeline')">
1464
+ <i class="fas fa-stream"></i> Timeline
1465
+ </div>
1466
+ <div class="tab" data-tab="memories" onclick="switchTab('memories')">
1467
+ <i class="fas fa-brain"></i> Memories
1468
+ </div>
1469
+ <div class="tab" data-tab="patterns" onclick="switchTab('patterns')">
1470
+ <i class="fas fa-code"></i> Patterns
1471
+ </div>
1472
+ <div class="tab" data-tab="search" onclick="switchTab('search')">
1473
+ <i class="fas fa-search"></i> Search
1474
+ </div>
1475
+ <div class="tab" data-tab="activity" onclick="switchTab('activity')">
1476
+ <i class="fas fa-rss"></i> Activity
1477
+ </div>
1478
+ </div>
1479
+
1480
+ <!-- Agents Tab -->
1481
+ <div class="tab-content active" id="agents-tab">
1482
+ <div class="section-header">
1483
+ <div class="search-bar">
1484
+ <i class="fas fa-search"></i>
1485
+ <input type="text" placeholder="Search agents..." id="agentSearch" oninput="filterAgents()">
1486
+ </div>
1487
+ <div class="section-actions">
1488
+ <button class="btn btn-secondary" onclick="enableAllAgents(false)"><i class="fas fa-toggle-off"></i> Disable All</button>
1489
+ <button class="btn btn-primary" onclick="enableAllAgents(true)"><i class="fas fa-toggle-on"></i> Enable All</button>
1490
+ </div>
1491
+ </div>
1492
+ <div class="category-filter" id="categoryFilter"></div>
1493
+ <div class="agent-grid" id="agentGrid"><div class="loading"><div class="spinner"></div></div></div>
1494
+ </div>
1495
+
1496
+ <!-- MCPs Tab -->
1497
+ <div class="tab-content" id="mcps-tab">
1498
+ <div class="section-header">
1499
+ <div class="section-title"><i class="fas fa-plug"></i> MCP Server Configuration</div>
1500
+ </div>
1501
+ <div class="agent-grid" id="mcpGrid"><div class="loading"><div class="spinner"></div></div></div>
1502
+ </div>
1503
+
1504
+ <!-- Hooks Tab -->
1505
+ <div class="tab-content" id="hooks-tab">
1506
+ <div class="section-header">
1507
+ <div class="section-title"><i class="fas fa-bolt"></i> Hook Configuration</div>
1508
+ </div>
1509
+ <div class="agent-grid" id="hookGrid"><div class="loading"><div class="spinner"></div></div></div>
1510
+ </div>
1511
+
1512
+ <!-- Timeline Tab -->
1513
+ <div class="tab-content" id="timeline-tab">
1514
+ <div class="section-header">
1515
+ <div class="section-title"><i class="fas fa-stream"></i> Timeline Events</div>
1516
+ <button class="btn btn-secondary" onclick="loadTimeline()"><i class="fas fa-sync"></i> Refresh</button>
1517
+ </div>
1518
+ <div class="timeline-container" id="timelineContainer"><div class="loading"><div class="spinner"></div></div></div>
1519
+ </div>
1520
+
1521
+ <!-- Memories Tab -->
1522
+ <div class="tab-content" id="memories-tab">
1523
+ <div class="two-column">
1524
+ <div>
1525
+ <div class="section-header">
1526
+ <div class="section-title"><i class="fas fa-brain"></i> Memory Browser</div>
1527
+ <div class="section-actions">
1528
+ <select id="memoryTypeFilter" onchange="loadMemories()" style="padding: 8px 16px; background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 8px; color: var(--text-primary); font-size: 13px;">
1529
+ <option value="">All Types</option>
1530
+ <option value="decision">Decisions</option>
1531
+ <option value="error">Errors</option>
1532
+ <option value="code">Code</option>
1533
+ <option value="preference">Preferences</option>
1534
+ <option value="session">Sessions</option>
1535
+ <option value="chunk">Chunks</option>
1536
+ </select>
1537
+ <button class="btn btn-secondary" onclick="loadMemories()"><i class="fas fa-sync"></i> Refresh</button>
1538
+ </div>
1539
+ </div>
1540
+ <div id="memoriesContainer"><div class="loading"><div class="spinner"></div></div></div>
1541
+ </div>
1542
+ <!-- Embedding Visualizer -->
1543
+ <div class="embedding-visualizer">
1544
+ <h3><i class="fas fa-vector-square"></i> Embedding Pipeline</h3>
1545
+ <div class="embedding-input-preview" id="embeddingInput">Waiting for embedding activity...</div>
1546
+ <div class="pipeline">
1547
+ <div class="pipeline-stage" id="stageText">
1548
+ <div class="pipeline-stage-icon"><i class="fas fa-file-alt"></i></div>
1549
+ <div class="pipeline-stage-label">Text</div>
1550
+ </div>
1551
+ <div class="pipeline-arrow"><i class="fas fa-arrow-right"></i></div>
1552
+ <div class="pipeline-stage" id="stageOllama">
1553
+ <div class="pipeline-stage-icon"><i class="fas fa-microchip"></i></div>
1554
+ <div class="pipeline-stage-label">Ollama</div>
1555
+ </div>
1556
+ <div class="pipeline-arrow"><i class="fas fa-arrow-right"></i></div>
1557
+ <div class="pipeline-stage" id="stageVector">
1558
+ <div class="pipeline-stage-icon"><i class="fas fa-project-diagram"></i></div>
1559
+ <div class="pipeline-stage-label">Vector</div>
1560
+ </div>
1561
+ </div>
1562
+ <div class="embedding-metrics">
1563
+ <div class="embedding-metric">
1564
+ <div class="embedding-metric-value" id="embeddingDimensions">768</div>
1565
+ <div class="embedding-metric-label">Dimensions</div>
1566
+ </div>
1567
+ <div class="embedding-metric">
1568
+ <div class="embedding-metric-value" id="embeddingTime">--</div>
1569
+ <div class="embedding-metric-label">Processing Time</div>
1570
+ </div>
1571
+ </div>
1572
+ <div class="vector-preview" id="vectorPreview"></div>
1573
+ </div>
1574
+ </div>
1575
+ </div>
1576
+
1577
+ <!-- Patterns Tab -->
1578
+ <div class="tab-content" id="patterns-tab">
1579
+ <div class="section-header">
1580
+ <div class="section-title"><i class="fas fa-code"></i> Pattern Library</div>
1581
+ <div class="section-actions">
1582
+ <select id="patternTypeFilter" onchange="loadPatterns()" style="padding: 8px 16px; background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 8px; color: var(--text-primary); font-size: 13px;">
1583
+ <option value="">All Types</option>
1584
+ <option value="bug_fix">Bug Fixes</option>
1585
+ <option value="feature">Features</option>
1586
+ <option value="refactor">Refactors</option>
1587
+ <option value="config">Configurations</option>
1588
+ <option value="performance">Performance</option>
1589
+ </select>
1590
+ <button class="btn btn-secondary" onclick="loadPatterns()"><i class="fas fa-sync"></i> Refresh</button>
1591
+ </div>
1592
+ </div>
1593
+ <div id="patternsContainer"><div class="loading"><div class="spinner"></div></div></div>
1594
+ </div>
1595
+
1596
+ <!-- Search Tab -->
1597
+ <div class="tab-content" id="search-tab">
1598
+ <div class="search-container">
1599
+ <div class="section-title" style="margin-bottom: 20px;"><i class="fas fa-search"></i> Semantic Search</div>
1600
+ <div class="search-input-wrapper">
1601
+ <input type="text" id="semanticSearchInput" placeholder="Search memories semantically..." onkeypress="if(event.key==='Enter')performSearch()">
1602
+ <button class="btn btn-primary" onclick="performSearch()"><i class="fas fa-search"></i> Search</button>
1603
+ </div>
1604
+ <div class="search-results" id="searchResults">
1605
+ <div style="text-align: center; padding: 40px; color: var(--text-secondary);">
1606
+ <i class="fas fa-search" style="font-size: 48px; opacity: 0.3; margin-bottom: 16px;"></i>
1607
+ <div>Enter a query to search your memories</div>
1608
+ </div>
1609
+ </div>
1610
+ </div>
1611
+ </div>
1612
+
1613
+ <!-- Activity Tab -->
1614
+ <div class="tab-content" id="activity-tab">
1615
+ <div class="two-column">
1616
+ <div class="activity-feed" id="activityFeed">
1617
+ <div class="activity-header">
1618
+ <h3><i class="fas fa-rss"></i> Live Activity</h3>
1619
+ <div class="activity-live-dot"></div>
1620
+ </div>
1621
+ <div id="activityItems">
1622
+ <div style="padding: 40px; text-align: center; color: var(--text-secondary);">
1623
+ <i class="fas fa-satellite-dish" style="font-size: 32px; opacity: 0.3; margin-bottom: 12px;"></i>
1624
+ <div>Waiting for activity...</div>
1625
+ </div>
1626
+ </div>
1627
+ </div>
1628
+ <div>
1629
+ <div class="embedding-visualizer">
1630
+ <h3><i class="fas fa-chart-bar"></i> Real-time Stats</h3>
1631
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
1632
+ <div style="background: var(--bg-tertiary); padding: 16px; border-radius: 12px; text-align: center;">
1633
+ <div style="font-size: 24px; font-weight: 700; color: var(--accent-blue);" id="todayMemories">0</div>
1634
+ <div style="font-size: 12px; color: var(--text-secondary);">Memories Today</div>
1635
+ </div>
1636
+ <div style="background: var(--bg-tertiary); padding: 16px; border-radius: 12px; text-align: center;">
1637
+ <div style="font-size: 24px; font-weight: 700; color: var(--accent-green);" id="todaySearches">0</div>
1638
+ <div style="font-size: 12px; color: var(--text-secondary);">Searches Today</div>
1639
+ </div>
1640
+ <div style="background: var(--bg-tertiary); padding: 16px; border-radius: 12px; text-align: center;">
1641
+ <div style="font-size: 24px; font-weight: 700; color: var(--accent-purple);" id="todayPatterns">0</div>
1642
+ <div style="font-size: 12px; color: var(--text-secondary);">Patterns Used</div>
1643
+ </div>
1644
+ <div style="background: var(--bg-tertiary); padding: 16px; border-radius: 12px; text-align: center;">
1645
+ <div style="font-size: 24px; font-weight: 700; color: var(--accent-cyan);" id="avgEmbedTime">--</div>
1646
+ <div style="font-size: 12px; color: var(--text-secondary);">Avg Embed Time</div>
1647
+ </div>
1648
+ </div>
1649
+ </div>
1650
+ </div>
1651
+ </div>
1652
+ </div>
1653
+ </main>
1654
+
1655
+ <div class="toast-container" id="toastContainer"></div>
1656
+
1657
+ <script>
1658
+ // Auto-detect server URL from current page location (works on any host/port)
1659
+ const API_URL = window.location.origin || 'http://localhost:8102';
1660
+ const WS_URL = (window.location.protocol === 'https:' ? 'wss:' : 'ws:') + '//' + (window.location.host || 'localhost:8102') + '/ws';
1661
+ let currentProject = null;
1662
+ let currentMemories = [];
1663
+ let projectConfig = null;
1664
+ let allAgents = [];
1665
+ let allMcps = [];
1666
+ let allHooks = [];
1667
+ let categories = {};
1668
+ let activeCategory = 'all';
1669
+ let ws = null;
1670
+ let wsReconnectAttempts = 0;
1671
+ const WS_MAX_RECONNECT = 5;
1672
+ const WS_RECONNECT_DELAY = 3000;
1673
+ let activityItems = [];
1674
+ let todayStats = { memories: 0, searches: 0, patterns: 0, embedTimes: [] };
1675
+
1676
+ // Initialize WebSocket
1677
+ function initWebSocket() {
1678
+ if (ws && ws.readyState === WebSocket.OPEN) return;
1679
+ try {
1680
+ ws = new WebSocket(WS_URL);
1681
+ ws.onopen = () => {
1682
+ console.log('WebSocket connected');
1683
+ wsReconnectAttempts = 0;
1684
+ updateConnectionStatus(true);
1685
+ if (currentProject) {
1686
+ ws.send(JSON.stringify({ type: 'subscribe', event_types: ['*'], project_path: currentProject }));
1687
+ }
1688
+ };
1689
+ ws.onmessage = (event) => {
1690
+ try {
1691
+ const data = JSON.parse(event.data);
1692
+ handleWebSocketMessage(data);
1693
+ } catch (e) {
1694
+ console.error('Error parsing WebSocket message:', e);
1695
+ }
1696
+ };
1697
+ ws.onclose = () => {
1698
+ console.log('WebSocket disconnected');
1699
+ updateConnectionStatus(false);
1700
+ if (wsReconnectAttempts < WS_MAX_RECONNECT) {
1701
+ wsReconnectAttempts++;
1702
+ setTimeout(initWebSocket, WS_RECONNECT_DELAY);
1703
+ }
1704
+ };
1705
+ ws.onerror = (error) => {
1706
+ console.error('WebSocket error:', error);
1707
+ updateConnectionStatus(false);
1708
+ };
1709
+ } catch (e) {
1710
+ console.error('Failed to initialize WebSocket:', e);
1711
+ }
1712
+ }
1713
+
1714
+ function handleWebSocketMessage(data) {
1715
+ switch (data.type) {
1716
+ case 'connected':
1717
+ console.log('WebSocket: Connected with client ID', data.client_id);
1718
+ break;
1719
+ case 'memory_stored':
1720
+ loadStats();
1721
+ todayStats.memories++;
1722
+ document.getElementById('todayMemories').textContent = todayStats.memories;
1723
+ addActivityItem('store', 'Memory Stored', data.data?.type || 'chunk');
1724
+ showToast(`Memory stored (${data.data?.type || 'chunk'})`, 'success');
1725
+ break;
1726
+ case 'timeline_logged':
1727
+ if (document.getElementById('timeline-tab').classList.contains('active')) {
1728
+ loadTimeline();
1729
+ }
1730
+ const eventsCount = document.getElementById('eventsCount');
1731
+ if (eventsCount) eventsCount.textContent = parseInt(eventsCount.textContent || 0) + 1;
1732
+ addActivityItem('context', 'Timeline Event', data.data?.event_type || 'event');
1733
+ break;
1734
+ case 'anchor_marked':
1735
+ addActivityItem('context', 'Anchor Marked', 'fact verified');
1736
+ showToast('Anchor marked', 'info');
1737
+ break;
1738
+ case 'anchor_conflict':
1739
+ addActivityItem('context', 'Anchor Conflict', 'needs resolution');
1740
+ showToast('Anchor conflict detected!', 'error');
1741
+ break;
1742
+ case 'cleanup_completed':
1743
+ showToast(`Cleanup: ${data.data?.archived || 0} archived, ${data.data?.deleted || 0} deleted`, 'success');
1744
+ loadStats();
1745
+ break;
1746
+ case 'reindex_progress':
1747
+ const pct = Math.round((data.data?.progress / data.data?.total) * 100) || 0;
1748
+ showToast(`Reindexing: ${pct}%`, 'info');
1749
+ break;
1750
+ case 'reindex_completed':
1751
+ showToast('Reindexing completed!', 'success');
1752
+ break;
1753
+ case 'embedding_completed':
1754
+ updateEmbeddingVisualizer(data.data);
1755
+ break;
1756
+ case 'search_performed':
1757
+ todayStats.searches++;
1758
+ document.getElementById('todaySearches').textContent = todayStats.searches;
1759
+ addActivityItem('search', 'Search Performed', data.data?.query?.substring(0, 50) || 'query');
1760
+ break;
1761
+ case 'pong':
1762
+ break;
1763
+ default:
1764
+ console.log('WebSocket event:', data.type, data);
1765
+ }
1766
+ }
1767
+
1768
+ function updateConnectionStatus(connected) {
1769
+ const indicator = document.getElementById('wsStatus');
1770
+ if (indicator) {
1771
+ indicator.className = connected ? 'ws-status connected' : 'ws-status disconnected';
1772
+ indicator.title = connected ? 'Real-time updates active' : 'Connecting...';
1773
+ }
1774
+ }
1775
+
1776
+ function sendHeartbeat() {
1777
+ if (ws && ws.readyState === WebSocket.OPEN) {
1778
+ ws.send(JSON.stringify({ type: 'ping' }));
1779
+ }
1780
+ }
1781
+
1782
+ function addActivityItem(type, title, detail) {
1783
+ const item = { type, title, detail, time: new Date() };
1784
+ activityItems.unshift(item);
1785
+ if (activityItems.length > 50) activityItems.pop();
1786
+ renderActivityFeed();
1787
+ }
1788
+
1789
+ function renderActivityFeed() {
1790
+ const container = document.getElementById('activityItems');
1791
+ if (activityItems.length === 0) {
1792
+ container.innerHTML = `<div style="padding: 40px; text-align: center; color: var(--text-secondary);">
1793
+ <i class="fas fa-satellite-dish" style="font-size: 32px; opacity: 0.3; margin-bottom: 12px;"></i>
1794
+ <div>Waiting for activity...</div>
1795
+ </div>`;
1796
+ return;
1797
+ }
1798
+ container.innerHTML = activityItems.slice(0, 20).map(item => `
1799
+ <div class="activity-item">
1800
+ <div class="activity-icon ${item.type}"><i class="fas fa-${getActivityIcon(item.type)}"></i></div>
1801
+ <div class="activity-content">
1802
+ <div class="activity-title">${escapeHtml(item.title)}</div>
1803
+ <div class="activity-detail">${escapeHtml(item.detail)}</div>
1804
+ </div>
1805
+ <div class="activity-time">${formatRelativeTime(item.time)}</div>
1806
+ </div>
1807
+ `).join('');
1808
+ }
1809
+
1810
+ function getActivityIcon(type) {
1811
+ const icons = { context: 'clock', embed: 'microchip', store: 'save', search: 'search' };
1812
+ return icons[type] || 'circle';
1813
+ }
1814
+
1815
+ function formatRelativeTime(date) {
1816
+ const seconds = Math.floor((new Date() - date) / 1000);
1817
+ if (seconds < 60) return 'just now';
1818
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
1819
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
1820
+ return `${Math.floor(seconds / 86400)}d ago`;
1821
+ }
1822
+
1823
+ function updateEmbeddingVisualizer(data) {
1824
+ if (data?.text) {
1825
+ document.getElementById('embeddingInput').textContent = data.text.substring(0, 200) + (data.text.length > 200 ? '...' : '');
1826
+ }
1827
+ if (data?.processing_time) {
1828
+ const time = data.processing_time;
1829
+ document.getElementById('embeddingTime').textContent = time + 'ms';
1830
+ todayStats.embedTimes.push(time);
1831
+ const avg = Math.round(todayStats.embedTimes.reduce((a, b) => a + b, 0) / todayStats.embedTimes.length);
1832
+ document.getElementById('avgEmbedTime').textContent = avg + 'ms';
1833
+ }
1834
+ if (data?.dimensions) {
1835
+ document.getElementById('embeddingDimensions').textContent = data.dimensions;
1836
+ }
1837
+ // Animate pipeline
1838
+ ['stageText', 'stageOllama', 'stageVector'].forEach((id, i) => {
1839
+ setTimeout(() => {
1840
+ document.getElementById(id).classList.add('active');
1841
+ setTimeout(() => document.getElementById(id).classList.remove('active'), 500);
1842
+ }, i * 300);
1843
+ });
1844
+ // Update vector preview
1845
+ if (data?.vector_sample) {
1846
+ renderVectorPreview(data.vector_sample);
1847
+ } else {
1848
+ renderVectorPreview(Array.from({ length: 100 }, () => Math.random()));
1849
+ }
1850
+ addActivityItem('embed', 'Embedding Generated', `${data?.dimensions || 768} dimensions`);
1851
+ }
1852
+
1853
+ function renderVectorPreview(values) {
1854
+ const container = document.getElementById('vectorPreview');
1855
+ const bars = values.slice(0, 100).map(v => {
1856
+ const height = Math.abs(v) * 100;
1857
+ const color = v >= 0 ? 'var(--accent-cyan)' : 'var(--accent-purple)';
1858
+ return `<div class="vector-bar" style="height: ${Math.max(2, height)}%; background: ${color};"></div>`;
1859
+ });
1860
+ container.innerHTML = bars.join('');
1861
+ }
1862
+
1863
+ // Initialize
1864
+ document.addEventListener('DOMContentLoaded', async () => {
1865
+ await loadProjects();
1866
+ await loadAgentData();
1867
+ await loadStats();
1868
+ initWebSocket();
1869
+ setInterval(sendHeartbeat, 30000);
1870
+ renderVectorPreview(Array.from({ length: 100 }, () => Math.random() * 0.5));
1871
+ });
1872
+
1873
+ // Show/hide loading overlay
1874
+ function showLoading(text = 'Loading...') {
1875
+ document.getElementById('loadingText').textContent = text;
1876
+ document.getElementById('loadingOverlay').classList.add('show');
1877
+ }
1878
+
1879
+ function hideLoading() {
1880
+ document.getElementById('loadingOverlay').classList.remove('show');
1881
+ }
1882
+
1883
+ // Project Management
1884
+ async function loadProjects() {
1885
+ try {
1886
+ const response = await fetch(`${API_URL}/api/projects`);
1887
+ const data = await response.json();
1888
+ const menu = document.getElementById('projectMenu');
1889
+ menu.innerHTML = '';
1890
+
1891
+ // Add "All Projects" option first
1892
+ const allItem = document.createElement('div');
1893
+ allItem.className = 'project-item';
1894
+ allItem.onclick = () => selectProject(null);
1895
+ allItem.innerHTML = `
1896
+ <div class="project-item-icon" style="background: rgba(139, 148, 158, 0.2); color: #8b949e;"><i class="fas fa-globe"></i></div>
1897
+ <div class="project-item-info">
1898
+ <div class="project-item-name">All Projects</div>
1899
+ <div class="project-item-stats">Show all memories across projects</div>
1900
+ </div>
1901
+ `;
1902
+ menu.appendChild(allItem);
1903
+
1904
+ if (!data.success || !data.projects.length) {
1905
+ return;
1906
+ }
1907
+ const colors = ['#58a6ff', '#3fb950', '#a371f7', '#d29922', '#f85149'];
1908
+ data.projects.forEach((project, index) => {
1909
+ const name = project.project_path.split(/[/\\]/).pop();
1910
+ const color = colors[index % colors.length];
1911
+ const item = document.createElement('div');
1912
+ item.className = 'project-item';
1913
+ item.onclick = () => selectProject(project.project_path);
1914
+ item.innerHTML = `
1915
+ <div class="project-item-icon" style="background: ${color}20; color: ${color};"><i class="fas fa-folder"></i></div>
1916
+ <div class="project-item-info">
1917
+ <div class="project-item-name">${name}</div>
1918
+ <div class="project-item-stats">${project.session_count || 0} sessions, ${project.memory_count || 0} memories</div>
1919
+ </div>
1920
+ `;
1921
+ menu.appendChild(item);
1922
+ });
1923
+ const savedProject = localStorage.getItem('currentProject');
1924
+ if (savedProject && data.projects.find(p => p.project_path === savedProject)) {
1925
+ selectProject(savedProject);
1926
+ } else {
1927
+ // Default to "All Projects" for better discovery
1928
+ selectProject(null);
1929
+ }
1930
+ } catch (e) {
1931
+ console.error('Error loading projects:', e);
1932
+ showToast('Failed to load projects', 'error');
1933
+ }
1934
+ }
1935
+
1936
+ function toggleProjectMenu() {
1937
+ document.getElementById('projectMenu').classList.toggle('show');
1938
+ }
1939
+
1940
+ async function selectProject(projectPath) {
1941
+ showLoading('Loading project data...');
1942
+ currentProject = projectPath;
1943
+ if (projectPath) {
1944
+ localStorage.setItem('currentProject', projectPath);
1945
+ } else {
1946
+ localStorage.removeItem('currentProject');
1947
+ }
1948
+ const name = projectPath ? projectPath.split(/[/\\]/).pop() : 'All Projects';
1949
+ document.getElementById('currentProjectName').textContent = name;
1950
+ document.getElementById('projectMenu').classList.remove('show');
1951
+ document.querySelectorAll('.project-item').forEach(item => item.classList.remove('active'));
1952
+ event?.target?.closest('.project-item')?.classList.add('active');
1953
+
1954
+ // Resubscribe WebSocket
1955
+ if (ws && ws.readyState === WebSocket.OPEN) {
1956
+ ws.send(JSON.stringify({ type: 'subscribe', event_types: ['*'], project_path: projectPath || '*' }));
1957
+ }
1958
+
1959
+ // Hot reload all data for the project
1960
+ try {
1961
+ await Promise.all([
1962
+ loadProjectConfig(),
1963
+ loadStats(),
1964
+ loadMemoriesIfActive(),
1965
+ loadPatternsIfActive(),
1966
+ loadTimelineIfActive()
1967
+ ]);
1968
+ renderAgents();
1969
+ renderMcps();
1970
+ renderHooks();
1971
+ showToast(`Loaded: ${name}`, 'success');
1972
+ } catch (e) {
1973
+ console.error('Error loading project data:', e);
1974
+ showToast('Failed to load project data', 'error');
1975
+ }
1976
+ hideLoading();
1977
+ }
1978
+
1979
+ async function loadMemoriesIfActive() {
1980
+ if (document.getElementById('memories-tab').classList.contains('active')) {
1981
+ await loadMemories();
1982
+ }
1983
+ }
1984
+
1985
+ async function loadPatternsIfActive() {
1986
+ if (document.getElementById('patterns-tab').classList.contains('active')) {
1987
+ await loadPatterns();
1988
+ }
1989
+ }
1990
+
1991
+ async function loadTimelineIfActive() {
1992
+ if (document.getElementById('timeline-tab').classList.contains('active')) {
1993
+ await loadTimeline();
1994
+ }
1995
+ }
1996
+
1997
+ async function loadProjectConfig() {
1998
+ if (!currentProject) return;
1999
+ try {
2000
+ const response = await fetch(`${API_URL}/api/project/${encodeURIComponent(currentProject)}/config`);
2001
+ projectConfig = await response.json();
2002
+ if (projectConfig.success) updateStats(projectConfig.stats);
2003
+ } catch (e) {
2004
+ console.error('Error loading project config:', e);
2005
+ }
2006
+ }
2007
+
2008
+ async function loadAgentData() {
2009
+ try {
2010
+ const [agentsRes, mcpsRes, hooksRes] = await Promise.all([
2011
+ fetch(`${API_URL}/api/agents`),
2012
+ fetch(`${API_URL}/api/mcps`),
2013
+ fetch(`${API_URL}/api/hooks`)
2014
+ ]);
2015
+ const agentsData = await agentsRes.json();
2016
+ const mcpsData = await mcpsRes.json();
2017
+ const hooksData = await hooksRes.json();
2018
+ allAgents = agentsData.agents || [];
2019
+ categories = agentsData.categories || {};
2020
+ allMcps = mcpsData.mcps || [];
2021
+ allHooks = hooksData.hooks || [];
2022
+ document.getElementById('agentsTabCount').textContent = allAgents.length;
2023
+ document.getElementById('mcpsTabCount').textContent = allMcps.length;
2024
+ document.getElementById('hooksTabCount').textContent = allHooks.length;
2025
+ renderCategoryFilter();
2026
+ if (projectConfig) {
2027
+ renderAgents();
2028
+ renderMcps();
2029
+ renderHooks();
2030
+ }
2031
+ } catch (e) {
2032
+ console.error('Error loading agent data:', e);
2033
+ }
2034
+ }
2035
+
2036
+ async function loadStats() {
2037
+ try {
2038
+ const response = await fetch(`${API_URL}/api/stats`);
2039
+ const stats = await response.json();
2040
+ document.getElementById('memoriesCount').textContent = stats.total_memories || 0;
2041
+ document.getElementById('eventsCount').textContent = stats.total_timeline_events || 0;
2042
+ document.getElementById('patternsCount').textContent = stats.total_patterns || 0;
2043
+ } catch (e) {
2044
+ console.error('Error loading stats:', e);
2045
+ }
2046
+ }
2047
+
2048
+ function updateStats(stats) {
2049
+ if (!stats) return;
2050
+ document.getElementById('enabledAgentsCount').textContent = stats.enabled_agents;
2051
+ document.getElementById('agentsTotalCount').textContent = `/ ${stats.total_agents} total`;
2052
+ document.getElementById('enabledMcpsCount').textContent = stats.enabled_mcps;
2053
+ document.getElementById('mcpsTotalCount').textContent = `/ ${stats.total_mcps} total`;
2054
+ document.getElementById('enabledHooksCount').textContent = stats.enabled_hooks;
2055
+ document.getElementById('hooksTotalCount').textContent = `/ ${stats.total_hooks} total`;
2056
+ }
2057
+
2058
+ // Rendering
2059
+ function renderCategoryFilter() {
2060
+ const container = document.getElementById('categoryFilter');
2061
+ container.innerHTML = `<div class="category-chip active" data-category="all" onclick="filterByCategory('all')"><i class="fas fa-th"></i> All</div>`;
2062
+ Object.entries(categories).forEach(([key, cat]) => {
2063
+ const chip = document.createElement('div');
2064
+ chip.className = 'category-chip';
2065
+ chip.dataset.category = key;
2066
+ chip.style.setProperty('--chip-color', cat.color);
2067
+ chip.onclick = () => filterByCategory(key);
2068
+ chip.innerHTML = `<i class="fas fa-${getIconForCategory(cat.icon)}"></i> ${cat.name}`;
2069
+ container.appendChild(chip);
2070
+ });
2071
+ }
2072
+
2073
+ function getIconForCategory(icon) {
2074
+ const iconMap = { 'code': 'code', 'shield-check': 'shield-alt', 'palette': 'palette', 'lightbulb': 'lightbulb', 'megaphone': 'bullhorn', 'settings': 'cog', 'chart-bar': 'chart-bar', 'cube': 'cube', 'search': 'search' };
2075
+ return iconMap[icon] || 'circle';
2076
+ }
2077
+
2078
+ function renderAgents() {
2079
+ const grid = document.getElementById('agentGrid');
2080
+ const searchTerm = document.getElementById('agentSearch').value.toLowerCase();
2081
+ let filteredAgents = allAgents;
2082
+ if (activeCategory !== 'all') filteredAgents = filteredAgents.filter(a => a.category === activeCategory);
2083
+ if (searchTerm) filteredAgents = filteredAgents.filter(a => a.name.toLowerCase().includes(searchTerm) || a.description.toLowerCase().includes(searchTerm) || a.tags.some(t => t.includes(searchTerm)));
2084
+ if (!filteredAgents.length) {
2085
+ grid.innerHTML = '<div style="grid-column: 1/-1; text-align: center; padding: 60px; color: var(--text-secondary);">No agents found</div>';
2086
+ return;
2087
+ }
2088
+ grid.innerHTML = filteredAgents.map(agent => {
2089
+ const config = projectConfig?.agents?.[agent.id] || { enabled: agent.default_enabled };
2090
+ const category = categories[agent.category] || {};
2091
+ const isEnabled = config.enabled;
2092
+ return `
2093
+ <div class="agent-card ${isEnabled ? '' : 'disabled'}">
2094
+ <div class="agent-category-label" style="background: ${category.color || '#58a6ff'}"></div>
2095
+ <div class="agent-card-header">
2096
+ <div class="agent-icon" style="background: ${category.color}20; color: ${category.color}"><i class="fas fa-${getIconForCategory(category.icon)}"></i></div>
2097
+ <div class="agent-toggle ${isEnabled ? 'active' : ''}" onclick="toggleAgent('${agent.id}', ${!isEnabled})"></div>
2098
+ </div>
2099
+ <div class="agent-name">${agent.name}</div>
2100
+ <div class="agent-description">${agent.description}</div>
2101
+ <div class="agent-tags">${agent.tags.slice(0, 4).map(tag => `<span class="agent-tag">${tag}</span>`).join('')}</div>
2102
+ </div>
2103
+ `;
2104
+ }).join('');
2105
+ }
2106
+
2107
+ function renderMcps() {
2108
+ const grid = document.getElementById('mcpGrid');
2109
+ grid.innerHTML = allMcps.map(mcp => {
2110
+ const config = projectConfig?.mcps?.[mcp.id] || { enabled: mcp.default_enabled };
2111
+ const isEnabled = config.enabled;
2112
+ return `
2113
+ <div class="config-card">
2114
+ <div class="config-icon" style="background: ${mcp.color}20; color: ${mcp.color}"><i class="fas fa-${getIconForMcp(mcp.icon)}"></i></div>
2115
+ <div class="config-info">
2116
+ <div class="config-name">${mcp.name}</div>
2117
+ <div class="config-desc">${mcp.description}</div>
2118
+ </div>
2119
+ <div class="agent-toggle ${isEnabled ? 'active' : ''}" onclick="toggleMcp('${mcp.id}', ${!isEnabled})"></div>
2120
+ </div>
2121
+ `;
2122
+ }).join('');
2123
+ }
2124
+
2125
+ function renderHooks() {
2126
+ const grid = document.getElementById('hookGrid');
2127
+ grid.innerHTML = allHooks.map(hook => {
2128
+ const config = projectConfig?.hooks?.[hook.id] || { enabled: hook.default_enabled };
2129
+ const isEnabled = config.enabled;
2130
+ return `
2131
+ <div class="config-card">
2132
+ <div class="config-icon" style="background: ${hook.color}20; color: ${hook.color}"><i class="fas fa-${getIconForMcp(hook.icon)}"></i></div>
2133
+ <div class="config-info">
2134
+ <div class="config-name">${hook.name}</div>
2135
+ <div class="config-desc">${hook.description}</div>
2136
+ <div class="config-trigger"><i class="fas fa-bolt"></i> ${hook.trigger}</div>
2137
+ </div>
2138
+ <div class="agent-toggle ${isEnabled ? 'active' : ''}" onclick="toggleHook('${hook.id}', ${!isEnabled})"></div>
2139
+ </div>
2140
+ `;
2141
+ }).join('');
2142
+ }
2143
+
2144
+ function getIconForMcp(icon) {
2145
+ const iconMap = { 'brain': 'brain', 'book': 'book', 'folder': 'folder', 'github': 'code-branch', 'database': 'database', 'globe': 'globe', 'anchor': 'anchor', 'shield': 'shield-alt', 'check-circle': 'check-circle', 'file-text': 'file-alt', 'message-circle': 'comment' };
2146
+ return iconMap[icon] || 'plug';
2147
+ }
2148
+
2149
+ // Toggle actions
2150
+ async function toggleAgent(agentId, enabled) {
2151
+ if (!currentProject) return;
2152
+ try {
2153
+ const response = await fetch(`${API_URL}/api/project/${encodeURIComponent(currentProject)}/agent/${agentId}`, {
2154
+ method: 'POST',
2155
+ headers: { 'Content-Type': 'application/json' },
2156
+ body: JSON.stringify({ enabled })
2157
+ });
2158
+ const result = await response.json();
2159
+ if (result.success) {
2160
+ await loadProjectConfig();
2161
+ renderAgents();
2162
+ showToast(`${agentId} ${enabled ? 'enabled' : 'disabled'}`, 'success');
2163
+ }
2164
+ } catch (e) {
2165
+ showToast('Failed to update agent', 'error');
2166
+ }
2167
+ }
2168
+
2169
+ async function toggleMcp(mcpId, enabled) {
2170
+ if (!currentProject) return;
2171
+ try {
2172
+ const response = await fetch(`${API_URL}/api/project/${encodeURIComponent(currentProject)}/mcp/${mcpId}`, {
2173
+ method: 'POST',
2174
+ headers: { 'Content-Type': 'application/json' },
2175
+ body: JSON.stringify({ enabled })
2176
+ });
2177
+ const result = await response.json();
2178
+ if (result.success) {
2179
+ await loadProjectConfig();
2180
+ renderMcps();
2181
+ showToast(`${mcpId} ${enabled ? 'enabled' : 'disabled'}`, 'success');
2182
+ }
2183
+ } catch (e) {
2184
+ showToast('Failed to update MCP', 'error');
2185
+ }
2186
+ }
2187
+
2188
+ async function toggleHook(hookId, enabled) {
2189
+ if (!currentProject) return;
2190
+ try {
2191
+ const response = await fetch(`${API_URL}/api/project/${encodeURIComponent(currentProject)}/hook/${hookId}`, {
2192
+ method: 'POST',
2193
+ headers: { 'Content-Type': 'application/json' },
2194
+ body: JSON.stringify({ enabled })
2195
+ });
2196
+ const result = await response.json();
2197
+ if (result.success) {
2198
+ await loadProjectConfig();
2199
+ renderHooks();
2200
+ showToast(`${hookId} ${enabled ? 'enabled' : 'disabled'}`, 'success');
2201
+ }
2202
+ } catch (e) {
2203
+ showToast('Failed to update hook', 'error');
2204
+ }
2205
+ }
2206
+
2207
+ async function enableAllAgents(enabled) {
2208
+ if (!currentProject) return;
2209
+ const updates = {};
2210
+ allAgents.forEach(agent => { updates[agent.id] = enabled; });
2211
+ try {
2212
+ const response = await fetch(`${API_URL}/api/project/${encodeURIComponent(currentProject)}/agents/bulk`, {
2213
+ method: 'POST',
2214
+ headers: { 'Content-Type': 'application/json' },
2215
+ body: JSON.stringify({ updates })
2216
+ });
2217
+ const result = await response.json();
2218
+ if (result.success) {
2219
+ await loadProjectConfig();
2220
+ renderAgents();
2221
+ showToast(`All agents ${enabled ? 'enabled' : 'disabled'}`, 'success');
2222
+ }
2223
+ } catch (e) {
2224
+ showToast('Failed to update agents', 'error');
2225
+ }
2226
+ }
2227
+
2228
+ // Filtering
2229
+ function filterByCategory(category) {
2230
+ activeCategory = category;
2231
+ document.querySelectorAll('.category-chip').forEach(chip => {
2232
+ chip.classList.toggle('active', chip.dataset.category === category);
2233
+ });
2234
+ renderAgents();
2235
+ }
2236
+
2237
+ function filterAgents() { renderAgents(); }
2238
+
2239
+ // Tabs
2240
+ function switchTab(tabId) {
2241
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
2242
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
2243
+ document.querySelector(`.tab[data-tab="${tabId}"]`).classList.add('active');
2244
+ document.getElementById(`${tabId}-tab`).classList.add('active');
2245
+ if (tabId === 'timeline') loadTimeline();
2246
+ if (tabId === 'memories') loadMemories();
2247
+ if (tabId === 'patterns') loadPatterns();
2248
+ }
2249
+
2250
+ // Timeline
2251
+ let timelineFilter = 'all';
2252
+ let timelineTimeRange = 'all';
2253
+
2254
+ async function loadTimeline() {
2255
+ const container = document.getElementById('timelineContainer');
2256
+ container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
2257
+ try {
2258
+ const sessionRes = await fetch(`${API_URL}/api/sessions/${encodeURIComponent(currentProject)}`);
2259
+ const sessionData = await sessionRes.json();
2260
+ if (!sessionData.sessions?.length) {
2261
+ container.innerHTML = `<div class="timeline-empty"><i class="fas fa-stream"></i><div>No timeline events yet</div><div style="font-size: 12px; margin-top: 8px;">Events will appear here as you work</div></div>`;
2262
+ return;
2263
+ }
2264
+ const sessionId = sessionData.sessions[0].session_id;
2265
+ const response = await fetch(`${API_URL}/a2a`, {
2266
+ method: 'POST',
2267
+ headers: { 'Content-Type': 'application/json' },
2268
+ body: JSON.stringify({
2269
+ jsonrpc: '2.0',
2270
+ id: Date.now(),
2271
+ method: 'tasks/send',
2272
+ params: { metadata: { skill_id: 'timeline_get', params: { session_id: sessionId, limit: 50 } } }
2273
+ })
2274
+ });
2275
+ const data = await response.json();
2276
+ const result = JSON.parse(data.result?.artifacts?.[0]?.parts?.[0]?.text || '{}');
2277
+ if (!result.events?.length) {
2278
+ container.innerHTML = `<div class="timeline-empty"><i class="fas fa-stream"></i><div>No timeline events yet</div></div>`;
2279
+ return;
2280
+ }
2281
+ const chains = buildCausalChains(result.events);
2282
+ const filterHtml = `
2283
+ <div class="timeline-filters">
2284
+ <div class="timeline-filter-chip ${timelineFilter === 'all' ? 'active' : ''}" onclick="filterTimeline('all')"><i class="fas fa-layer-group"></i> All</div>
2285
+ <div class="timeline-filter-chip ${timelineFilter === 'decision' ? 'active' : ''}" onclick="filterTimeline('decision')"><i class="fas fa-lightbulb"></i> Decisions</div>
2286
+ <div class="timeline-filter-chip ${timelineFilter === 'observation' ? 'active' : ''}" onclick="filterTimeline('observation')"><i class="fas fa-eye"></i> Observations</div>
2287
+ <div class="timeline-filter-chip ${timelineFilter === 'anchor' ? 'active' : ''}" onclick="filterTimeline('anchor')"><i class="fas fa-anchor"></i> Anchors</div>
2288
+ <div class="timeline-filter-chip ${timelineFilter === 'outcome' ? 'active' : ''}" onclick="filterTimeline('outcome')"><i class="fas fa-check-circle"></i> Outcomes</div>
2289
+ <div class="timeline-filter-divider"></div>
2290
+ <div class="timeline-time-filter ${timelineTimeRange === 'hour' ? 'active' : ''}" onclick="filterTimeRange('hour')"><i class="fas fa-clock"></i> Last Hour</div>
2291
+ <div class="timeline-time-filter ${timelineTimeRange === 'today' ? 'active' : ''}" onclick="filterTimeRange('today')"><i class="fas fa-calendar-day"></i> Today</div>
2292
+ <div class="timeline-time-filter ${timelineTimeRange === 'week' ? 'active' : ''}" onclick="filterTimeRange('week')"><i class="fas fa-calendar-week"></i> This Week</div>
2293
+ <div class="timeline-time-filter ${timelineTimeRange === 'all' ? 'active' : ''}" onclick="filterTimeRange('all')"><i class="fas fa-infinity"></i> All Time</div>
2294
+ </div>
2295
+ `;
2296
+ const chainsHtml = chains.map(chain => renderChain(chain)).join('');
2297
+ container.innerHTML = filterHtml + chainsHtml;
2298
+ } catch (e) {
2299
+ console.error('Error loading timeline:', e);
2300
+ container.innerHTML = `<div class="timeline-empty"><i class="fas fa-exclamation-triangle"></i><div>Failed to load timeline</div></div>`;
2301
+ }
2302
+ }
2303
+
2304
+ function buildCausalChains(events) {
2305
+ const sorted = [...events].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
2306
+ const chains = [];
2307
+ let currentChain = null;
2308
+ const chronological = [...sorted].reverse();
2309
+ chronological.forEach(event => {
2310
+ if (event.event_type === 'user_request') {
2311
+ currentChain = { root: event, children: [] };
2312
+ chains.unshift(currentChain);
2313
+ } else if (currentChain) {
2314
+ currentChain.children.push(event);
2315
+ } else {
2316
+ chains.unshift({ root: null, children: [event] });
2317
+ }
2318
+ });
2319
+ chains.forEach(chain => { chain.children.reverse(); });
2320
+ return chains;
2321
+ }
2322
+
2323
+ function renderChain(chain) {
2324
+ let children = chain.children;
2325
+ if (timelineFilter !== 'all') {
2326
+ children = children.filter(e => e.event_type === timelineFilter);
2327
+ if (children.length === 0 && chain.root?.event_type !== timelineFilter) return '';
2328
+ }
2329
+ if (timelineTimeRange !== 'all') {
2330
+ const now = new Date();
2331
+ let cutoff;
2332
+ switch (timelineTimeRange) {
2333
+ case 'hour': cutoff = new Date(now - 60 * 60 * 1000); break;
2334
+ case 'today': cutoff = new Date(now.setHours(0, 0, 0, 0)); break;
2335
+ case 'week': cutoff = new Date(now - 7 * 24 * 60 * 60 * 1000); break;
2336
+ }
2337
+ if (chain.root && new Date(chain.root.created_at) < cutoff) return '';
2338
+ }
2339
+ const chainId = chain.root?.id || Math.random().toString(36).substr(2, 9);
2340
+ if (chain.root) {
2341
+ return `
2342
+ <div class="timeline-chain" id="chain-${chainId}">
2343
+ <div class="timeline-request" onclick="toggleChain('${chainId}')">
2344
+ <div class="timeline-request-header">
2345
+ <button class="timeline-collapse-btn" title="Collapse/Expand"><i class="fas fa-chevron-down"></i></button>
2346
+ <div class="timeline-request-icon"><i class="fas fa-user"></i></div>
2347
+ <div class="timeline-request-title">${escapeHtml(chain.root.summary)}</div>
2348
+ ${children.length > 0 ? `<span class="timeline-children-count">${children.length} events</span>` : ''}
2349
+ <div class="timeline-request-time">${formatTime(chain.root.created_at)}</div>
2350
+ </div>
2351
+ ${chain.root.details ? `<div class="timeline-request-goal">${getGoalFromDetails(chain.root.details)}</div>` : ''}
2352
+ </div>
2353
+ ${children.length > 0 ? `<div class="timeline-children">${children.map((event, idx) => renderEvent(event, idx === children.length - 1)).join('')}</div>` : ''}
2354
+ </div>
2355
+ `;
2356
+ } else {
2357
+ return `<div class="timeline-chain" style="border-left-color: var(--border-color);"><div class="timeline-children" style="padding-top: 8px;">${children.map((event, idx) => renderEvent(event, idx === children.length - 1)).join('')}</div></div>`;
2358
+ }
2359
+ }
2360
+
2361
+ function toggleChain(chainId) {
2362
+ const chain = document.getElementById('chain-' + chainId);
2363
+ if (chain) chain.classList.toggle('collapsed');
2364
+ }
2365
+
2366
+ function renderEvent(event, isLast) {
2367
+ const icons = { 'decision': 'lightbulb', 'observation': 'eye', 'action': 'play', 'outcome': 'check-circle', 'anchor': 'anchor', 'error': 'exclamation-circle' };
2368
+ const details = parseDetails(event.details);
2369
+ const confidence = event.confidence || details?.confidence;
2370
+ const outcome = event.status || details?.outcome?.status;
2371
+ const filePath = details?.file_path || extractFilePath(event.summary);
2372
+ let eventClass = 'timeline-event';
2373
+ if (event.is_anchor || event.event_type === 'anchor') eventClass += ' anchor-event';
2374
+ if (event.event_type === 'outcome') {
2375
+ eventClass += ' outcome-event';
2376
+ if (outcome === 'failed') eventClass += ' failed';
2377
+ }
2378
+ return `
2379
+ <div class="${eventClass}">
2380
+ <div class="timeline-connector" ${isLast ? 'style="height: 50%;"' : ''}></div>
2381
+ <div class="timeline-dot dot-${event.event_type}"><i class="fas fa-${icons[event.event_type] || 'circle'}"></i></div>
2382
+ <div class="timeline-content">
2383
+ <div class="timeline-type-badge badge-${event.event_type}"><i class="fas fa-${icons[event.event_type] || 'circle'}"></i> ${event.event_type.replace('_', ' ')}</div>
2384
+ <div class="timeline-summary ${event.summary.includes('...') ? 'truncated' : ''}" onclick="toggleSummary(this)" data-full="${escapeHtml(details?.tool?.old_string || details?.command || event.summary)}" title="${event.summary.includes('...') ? 'Click to expand' : ''}">${escapeHtml(event.summary)}</div>
2385
+ ${filePath ? `<div class="timeline-file-link" onclick="copyFilePath(event, '${escapeHtml(filePath)}')" title="Click to copy path"><i class="fas fa-file-code"></i><span>${escapeHtml(filePath.split(/[/\\]/).pop())}</span></div>` : ''}
2386
+ ${details?.reasoning ? `<div class="timeline-details"><strong>Reasoning:</strong> ${escapeHtml(details.reasoning)}</div>` : ''}
2387
+ <div class="timeline-meta">
2388
+ <span class="timeline-time">${formatTime(event.created_at)}</span>
2389
+ ${confidence ? `<span class="timeline-confidence ${confidence > 0.7 ? 'high' : ''}">${Math.round(confidence * 100)}% confidence</span>` : ''}
2390
+ ${event.is_anchor ? `<span class="timeline-anchor-badge"><i class="fas fa-anchor"></i> Verified Fact</span>` : ''}
2391
+ ${outcome === 'success' ? `<span class="timeline-outcome-badge success"><i class="fas fa-check"></i> Success</span>` : ''}
2392
+ ${outcome === 'failed' ? `<span class="timeline-outcome-badge failed"><i class="fas fa-times"></i> Failed</span>` : ''}
2393
+ </div>
2394
+ </div>
2395
+ </div>
2396
+ `;
2397
+ }
2398
+
2399
+ function extractFilePath(summary) {
2400
+ const patterns = [/(?:in|from|to)\s+([^\s]+\.\w{2,4})/i, /([A-Za-z]:[\\\/][^\s]+\.\w{2,4})/, /([^\s]+\.\w{2,4})$/];
2401
+ for (const pattern of patterns) {
2402
+ const match = summary.match(pattern);
2403
+ if (match) return match[1];
2404
+ }
2405
+ return null;
2406
+ }
2407
+
2408
+ function copyFilePath(e, path) {
2409
+ e.stopPropagation();
2410
+ navigator.clipboard.writeText(path).then(() => {
2411
+ const el = e.target.closest('.timeline-file-link');
2412
+ if (el) {
2413
+ el.classList.add('copied');
2414
+ const span = el.querySelector('span');
2415
+ const originalText = span.textContent;
2416
+ span.textContent = 'Copied!';
2417
+ setTimeout(() => {
2418
+ el.classList.remove('copied');
2419
+ span.textContent = originalText;
2420
+ }, 1500);
2421
+ }
2422
+ showToast('Path copied to clipboard', 'success');
2423
+ });
2424
+ }
2425
+
2426
+ function toggleSummary(el) {
2427
+ if (!el.classList.contains('truncated') && !el.classList.contains('expanded')) return;
2428
+ el.classList.toggle('expanded');
2429
+ if (el.classList.contains('expanded')) {
2430
+ const fullText = el.dataset.full || el.textContent;
2431
+ el.dataset.short = el.textContent;
2432
+ el.textContent = fullText;
2433
+ } else {
2434
+ el.textContent = el.dataset.short || el.textContent;
2435
+ }
2436
+ }
2437
+
2438
+ function parseDetails(details) {
2439
+ if (!details) return null;
2440
+ try { return typeof details === 'string' ? JSON.parse(details) : details; } catch { return null; }
2441
+ }
2442
+
2443
+ function getGoalFromDetails(details) {
2444
+ const parsed = parseDetails(details);
2445
+ return parsed?.goal || parsed?.intent || '';
2446
+ }
2447
+
2448
+ function formatTime(timestamp) { return new Date(timestamp).toLocaleString(); }
2449
+
2450
+ function escapeHtml(text) {
2451
+ if (!text) return '';
2452
+ const div = document.createElement('div');
2453
+ div.textContent = text;
2454
+ return div.innerHTML;
2455
+ }
2456
+
2457
+ function filterTimeline(filter) { timelineFilter = filter; loadTimeline(); }
2458
+ function filterTimeRange(range) { timelineTimeRange = range; loadTimeline(); }
2459
+
2460
+ // Memories
2461
+ async function loadMemories() {
2462
+ const container = document.getElementById('memoriesContainer');
2463
+ container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
2464
+ try {
2465
+ const typeFilter = document.getElementById('memoryTypeFilter').value;
2466
+ // Use direct API endpoint - show all memories, optionally filter by type
2467
+ let url = `${API_URL}/api/memories?limit=50`;
2468
+ if (typeFilter) url += `&memory_type=${typeFilter}`;
2469
+ // Optionally filter by project if one is selected
2470
+ if (currentProject) url += `&project_path=${encodeURIComponent(currentProject)}`;
2471
+
2472
+ const response = await fetch(url);
2473
+ const data = await response.json();
2474
+
2475
+ if (!data.memories?.length) {
2476
+ const projectName = currentProject ? currentProject.split(/[/\\]/).pop() : 'any project';
2477
+ container.innerHTML = `<div style="text-align: center; padding: 60px; color: var(--text-secondary);"><i class="fas fa-brain" style="font-size: 48px; opacity: 0.3; margin-bottom: 16px;"></i><div>No memories found for ${projectName}</div><div style="font-size: 12px; margin-top: 8px;">Select "All Projects" to see all memories</div></div>`;
2478
+ return;
2479
+ }
2480
+ container.innerHTML = renderMemoryCards(data.memories);
2481
+ } catch (e) {
2482
+ console.error('Error loading memories:', e);
2483
+ container.innerHTML = `<div style="text-align: center; padding: 60px; color: var(--text-secondary);"><i class="fas fa-exclamation-triangle" style="font-size: 48px; opacity: 0.3; margin-bottom: 16px;"></i><div>Failed to load memories</div></div>`;
2484
+ }
2485
+ }
2486
+
2487
+ function renderMemoryCards(memories) {
2488
+ currentMemories = memories; // Store for modal access
2489
+ return memories.map((memory, index) => `
2490
+ <div class="memory-card" onclick="showMemoryDetail(${index})" title="Click to view full content">
2491
+ <div class="memory-header">
2492
+ <span class="memory-type-badge memory-type-${memory.type}">${memory.type}</span>
2493
+ <span style="font-size: 12px; color: var(--text-muted);">${formatTime(memory.created_at)}</span>
2494
+ </div>
2495
+ <div class="memory-content">${escapeHtml((memory.content || '').substring(0, 300))}${(memory.content || '').length > 300 ? '...' : ''}</div>
2496
+ <div class="memory-meta">
2497
+ ${memory.importance ? `<span><i class="fas fa-star"></i> Importance: ${memory.importance}</span>` : ''}
2498
+ ${memory.project_path ? `<span><i class="fas fa-folder"></i> ${memory.project_path.split(/[/\\]/).pop()}</span>` : ''}
2499
+ </div>
2500
+ </div>
2501
+ `).join('');
2502
+ }
2503
+
2504
+ // Patterns
2505
+ async function loadPatterns() {
2506
+ const container = document.getElementById('patternsContainer');
2507
+ container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
2508
+ try {
2509
+ const typeFilter = document.getElementById('patternTypeFilter').value;
2510
+ // Use direct API endpoint
2511
+ let url = `${API_URL}/api/patterns?limit=30`;
2512
+ if (typeFilter) url += `&problem_type=${typeFilter}`;
2513
+
2514
+ const response = await fetch(url);
2515
+ const data = await response.json();
2516
+
2517
+ if (!data.patterns?.length) {
2518
+ container.innerHTML = `<div style="text-align: center; padding: 60px; color: var(--text-secondary);"><i class="fas fa-code" style="font-size: 48px; opacity: 0.3; margin-bottom: 16px;"></i><div>No patterns stored yet</div><div style="font-size: 12px; margin-top: 8px;">Patterns are created when you solve problems successfully</div></div>`;
2519
+ return;
2520
+ }
2521
+ container.innerHTML = data.patterns.map(pattern => `
2522
+ <div class="pattern-card">
2523
+ <div class="pattern-header">
2524
+ <span class="pattern-name">${escapeHtml(pattern.name || 'Unnamed Pattern')}</span>
2525
+ <span class="pattern-success-rate ${(pattern.success_count || 0) > 2 ? 'high' : 'medium'}">${pattern.success_count || 0} uses</span>
2526
+ </div>
2527
+ <div class="pattern-solution">${escapeHtml((pattern.solution || '').substring(0, 400))}${(pattern.solution || '').length > 400 ? '...' : ''}</div>
2528
+ <div class="pattern-tags">
2529
+ ${pattern.problem_type ? `<span class="pattern-tag">${pattern.problem_type}</span>` : ''}
2530
+ ${(pattern.tech_context ? JSON.parse(pattern.tech_context) : []).slice(0, 3).map(t => `<span class="pattern-tag">${t}</span>`).join('')}
2531
+ </div>
2532
+ </div>
2533
+ `).join('');
2534
+ } catch (e) {
2535
+ console.error('Error loading patterns:', e);
2536
+ container.innerHTML = `<div style="text-align: center; padding: 60px; color: var(--text-secondary);"><i class="fas fa-exclamation-triangle" style="font-size: 48px; opacity: 0.3; margin-bottom: 16px;"></i><div>Failed to load patterns</div></div>`;
2537
+ }
2538
+ }
2539
+
2540
+ // Semantic Search
2541
+ async function performSearch() {
2542
+ const query = document.getElementById('semanticSearchInput').value.trim();
2543
+ if (!query) return;
2544
+ const container = document.getElementById('searchResults');
2545
+ container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
2546
+ try {
2547
+ // Use direct API endpoint for search
2548
+ let url = `${API_URL}/api/search?query=${encodeURIComponent(query)}&limit=20`;
2549
+ if (currentProject) url += `&project_path=${encodeURIComponent(currentProject)}`;
2550
+
2551
+ const response = await fetch(url);
2552
+ const data = await response.json();
2553
+
2554
+ if (!data.results?.length) {
2555
+ container.innerHTML = `<div style="text-align: center; padding: 40px; color: var(--text-secondary);"><i class="fas fa-search" style="font-size: 48px; opacity: 0.3; margin-bottom: 16px;"></i><div>No results found for "${escapeHtml(query)}"</div></div>`;
2556
+ return;
2557
+ }
2558
+ container.innerHTML = data.results.map(result => `
2559
+ <div class="search-result">
2560
+ <div class="search-result-header">
2561
+ <span class="memory-type-badge memory-type-${result.type || 'chunk'}">${result.type || 'memory'}</span>
2562
+ <span class="search-result-score">${Math.round((result.similarity || 0.8) * 100)}% match</span>
2563
+ </div>
2564
+ <div class="search-result-content">${escapeHtml(result.content || '')}</div>
2565
+ </div>
2566
+ `).join('');
2567
+ addActivityItem('search', 'Search Performed', query.substring(0, 50));
2568
+ todayStats.searches++;
2569
+ document.getElementById('todaySearches').textContent = todayStats.searches;
2570
+ } catch (e) {
2571
+ console.error('Error performing search:', e);
2572
+ container.innerHTML = `<div style="text-align: center; padding: 40px; color: var(--text-secondary);"><i class="fas fa-exclamation-triangle" style="font-size: 48px; opacity: 0.3; margin-bottom: 16px;"></i><div>Search failed</div></div>`;
2573
+ }
2574
+ }
2575
+
2576
+ // Toast
2577
+ function showToast(message, type = 'success') {
2578
+ const container = document.getElementById('toastContainer');
2579
+ const toast = document.createElement('div');
2580
+ toast.className = `toast ${type}`;
2581
+ const icons = { success: 'check-circle', error: 'exclamation-circle', info: 'info-circle' };
2582
+ const colors = { success: 'var(--accent-green)', error: 'var(--accent-red)', info: 'var(--accent-blue)' };
2583
+ toast.innerHTML = `<i class="fas fa-${icons[type]}" style="color: ${colors[type]}"></i><span>${message}</span>`;
2584
+ container.appendChild(toast);
2585
+ setTimeout(() => {
2586
+ toast.style.animation = 'toastIn 0.3s ease reverse';
2587
+ setTimeout(() => toast.remove(), 300);
2588
+ }, 3000);
2589
+ }
2590
+
2591
+ // Close dropdown when clicking outside
2592
+ document.addEventListener('click', (e) => {
2593
+ if (!e.target.closest('.project-dropdown')) {
2594
+ document.getElementById('projectMenu').classList.remove('show');
2595
+ }
2596
+ });
2597
+
2598
+ // Health check
2599
+ async function checkHealth() {
2600
+ try {
2601
+ const response = await fetch(`${API_URL}/health`);
2602
+ const data = await response.json();
2603
+ const indicator = document.getElementById('statusIndicator');
2604
+ const text = document.getElementById('statusText');
2605
+ if (data.status === 'healthy') {
2606
+ indicator.style.borderColor = 'rgba(63, 185, 80, 0.3)';
2607
+ indicator.style.background = 'rgba(63, 185, 80, 0.1)';
2608
+ indicator.style.color = 'var(--accent-green)';
2609
+ text.textContent = 'Connected';
2610
+ } else {
2611
+ throw new Error('Unhealthy');
2612
+ }
2613
+ } catch (e) {
2614
+ const indicator = document.getElementById('statusIndicator');
2615
+ const text = document.getElementById('statusText');
2616
+ indicator.style.borderColor = 'rgba(248, 81, 73, 0.3)';
2617
+ indicator.style.background = 'rgba(248, 81, 73, 0.1)';
2618
+ indicator.style.color = 'var(--accent-red)';
2619
+ text.textContent = 'Disconnected';
2620
+ }
2621
+ }
2622
+
2623
+ setInterval(checkHealth, 10000);
2624
+ checkHealth();
2625
+
2626
+ // Memory Detail Modal
2627
+ function showMemoryDetail(index) {
2628
+ const memory = currentMemories[index];
2629
+ if (!memory) return;
2630
+
2631
+ const modal = document.getElementById('memoryModal');
2632
+ document.getElementById('modalMemoryType').innerHTML = `<span class="memory-type-badge memory-type-${memory.type}">${memory.type}</span>`;
2633
+ document.getElementById('modalMemoryContent').textContent = memory.content || 'No content';
2634
+ document.getElementById('modalMemoryDate').textContent = formatTime(memory.created_at);
2635
+ document.getElementById('modalMemoryImportance').textContent = memory.importance || 'N/A';
2636
+ document.getElementById('modalMemoryProject').textContent = memory.project_path ? memory.project_path.split(/[/\\]/).pop() : 'None';
2637
+ document.getElementById('modalMemoryTags').textContent = memory.tags ? JSON.parse(memory.tags).join(', ') : 'None';
2638
+
2639
+ modal.classList.add('show');
2640
+ }
2641
+
2642
+ function closeMemoryModal() {
2643
+ document.getElementById('memoryModal').classList.remove('show');
2644
+ }
2645
+
2646
+ // Close modal on overlay click
2647
+ document.getElementById('memoryModal')?.addEventListener('click', (e) => {
2648
+ if (e.target.classList.contains('modal-overlay')) closeMemoryModal();
2649
+ });
2650
+
2651
+ // Close modal on Escape key
2652
+ document.addEventListener('keydown', (e) => {
2653
+ if (e.key === 'Escape') closeMemoryModal();
2654
+ });
2655
+ </script>
2656
+
2657
+ <!-- Memory Detail Modal -->
2658
+ <div class="modal-overlay" id="memoryModal" onclick="if(event.target === this) closeMemoryModal()">
2659
+ <div class="modal-content">
2660
+ <div class="modal-header">
2661
+ <h3><i class="fas fa-brain"></i> Memory Details</h3>
2662
+ <button class="modal-close" onclick="closeMemoryModal()">&times;</button>
2663
+ </div>
2664
+ <div class="modal-body">
2665
+ <div id="modalMemoryType" style="margin-bottom: 16px;"></div>
2666
+ <pre id="modalMemoryContent"></pre>
2667
+ <div class="modal-meta">
2668
+ <div class="modal-meta-item">
2669
+ <label>Created</label>
2670
+ <span id="modalMemoryDate"></span>
2671
+ </div>
2672
+ <div class="modal-meta-item">
2673
+ <label>Importance</label>
2674
+ <span id="modalMemoryImportance"></span>
2675
+ </div>
2676
+ <div class="modal-meta-item">
2677
+ <label>Project</label>
2678
+ <span id="modalMemoryProject"></span>
2679
+ </div>
2680
+ <div class="modal-meta-item">
2681
+ <label>Tags</label>
2682
+ <span id="modalMemoryTags"></span>
2683
+ </div>
2684
+ </div>
2685
+ </div>
2686
+ </div>
2687
+ </div>
2688
+ </body>
2689
+ </html>