@trlc/super-memory 1.0.0 → 1.0.1

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.
@@ -0,0 +1,851 @@
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>Super Memory Dashboard</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --bg-dark: #0A0A0A;
11
+ --bg-card: #1F1F1F;
12
+ --bg-hover: #2A2A2A;
13
+ --accent-red: #DC2626;
14
+ --accent-red-hover: #B91C1C;
15
+ --text-primary: #FFFFFF;
16
+ --text-secondary: #A1A1AA;
17
+ --text-muted: #71717A;
18
+ --border-color: #333333;
19
+ --category-gotcha: #EF4444;
20
+ --category-problem: #F59E0B;
21
+ --category-decision: #8B5CF6;
22
+ --category-discovery: #10B981;
23
+ }
24
+
25
+ * {
26
+ margin: 0;
27
+ padding: 0;
28
+ box-sizing: border-box;
29
+ }
30
+
31
+ body {
32
+ font-family: 'Inter', sans-serif;
33
+ background: var(--bg-dark);
34
+ color: var(--text-primary);
35
+ min-height: 100vh;
36
+ line-height: 1.6;
37
+ }
38
+
39
+ /* Header */
40
+ .header {
41
+ background: var(--bg-card);
42
+ border-bottom: 1px solid var(--border-color);
43
+ padding: 1rem 2rem;
44
+ display: flex;
45
+ justify-content: space-between;
46
+ align-items: center;
47
+ }
48
+
49
+ .logo {
50
+ display: flex;
51
+ align-items: center;
52
+ gap: 0.75rem;
53
+ }
54
+
55
+ .logo-icon {
56
+ font-size: 1.5rem;
57
+ }
58
+
59
+ .logo-text {
60
+ font-weight: 700;
61
+ font-size: 1.25rem;
62
+ }
63
+
64
+ .header-actions {
65
+ display: flex;
66
+ gap: 1rem;
67
+ }
68
+
69
+ .action-bar {
70
+ background: var(--bg-card);
71
+ border: 1px solid var(--border-color);
72
+ border-radius: 0.75rem;
73
+ padding: 1rem;
74
+ margin-bottom: 2rem;
75
+ display: flex;
76
+ gap: 1rem;
77
+ align-items: center;
78
+ }
79
+
80
+ .btn {
81
+ padding: 0.5rem 1rem;
82
+ border-radius: 0.5rem;
83
+ border: none;
84
+ cursor: pointer;
85
+ font-size: 0.875rem;
86
+ font-weight: 500;
87
+ transition: all 0.2s;
88
+ display: flex;
89
+ align-items: center;
90
+ gap: 0.5rem;
91
+ }
92
+
93
+ .btn-secondary {
94
+ background: var(--bg-hover);
95
+ color: var(--text-primary);
96
+ border: 1px solid var(--border-color);
97
+ }
98
+
99
+ .btn-secondary:hover {
100
+ background: var(--border-color);
101
+ }
102
+
103
+ .btn-primary {
104
+ background: var(--accent-red);
105
+ color: white;
106
+ }
107
+
108
+ .btn-primary:hover {
109
+ background: var(--accent-red-hover);
110
+ }
111
+
112
+ /* Main Layout */
113
+ .container {
114
+ max-width: 1400px;
115
+ margin: 0 auto;
116
+ padding: 2rem;
117
+ }
118
+
119
+ .grid {
120
+ display: grid;
121
+ grid-template-columns: 280px 1fr;
122
+ gap: 2rem;
123
+ }
124
+
125
+ /* Stats Cards */
126
+ .stats-section {
127
+ margin-bottom: 2rem;
128
+ }
129
+
130
+ .stats-grid {
131
+ display: grid;
132
+ grid-template-columns: repeat(5, 1fr);
133
+ gap: 1rem;
134
+ margin-bottom: 2rem;
135
+ }
136
+
137
+ .stat-card {
138
+ background: var(--bg-card);
139
+ border: 1px solid var(--border-color);
140
+ border-radius: 0.75rem;
141
+ padding: 1.25rem;
142
+ transition: transform 0.2s;
143
+ }
144
+
145
+ .stat-card:hover {
146
+ transform: translateY(-2px);
147
+ }
148
+
149
+ .stat-icon {
150
+ font-size: 1.5rem;
151
+ margin-bottom: 0.5rem;
152
+ }
153
+
154
+ .stat-value {
155
+ font-size: 1.75rem;
156
+ font-weight: 700;
157
+ margin-bottom: 0.25rem;
158
+ }
159
+
160
+ .stat-label {
161
+ font-size: 0.75rem;
162
+ color: var(--text-muted);
163
+ text-transform: uppercase;
164
+ letter-spacing: 0.05em;
165
+ }
166
+
167
+ /* Search */
168
+ .search-section {
169
+ margin-bottom: 2rem;
170
+ }
171
+
172
+ .search-input {
173
+ width: 100%;
174
+ padding: 0.875rem 1rem;
175
+ background: var(--bg-card);
176
+ border: 1px solid var(--border-color);
177
+ border-radius: 0.5rem;
178
+ color: var(--text-primary);
179
+ font-size: 0.9375rem;
180
+ font-family: 'Inter', sans-serif;
181
+ }
182
+
183
+ .search-input:focus {
184
+ outline: none;
185
+ border-color: var(--accent-red);
186
+ }
187
+
188
+ .search-input::placeholder {
189
+ color: var(--text-muted);
190
+ }
191
+
192
+ /* Sidebar */
193
+ .sidebar {
194
+ background: var(--bg-card);
195
+ border: 1px solid var(--border-color);
196
+ border-radius: 0.75rem;
197
+ padding: 1.5rem;
198
+ height: fit-content;
199
+ }
200
+
201
+ .sidebar-title {
202
+ font-size: 0.75rem;
203
+ font-weight: 600;
204
+ color: var(--text-muted);
205
+ text-transform: uppercase;
206
+ letter-spacing: 0.05em;
207
+ margin-bottom: 1rem;
208
+ }
209
+
210
+ .category-list {
211
+ list-style: none;
212
+ }
213
+
214
+ .category-item {
215
+ display: flex;
216
+ align-items: center;
217
+ gap: 0.75rem;
218
+ padding: 0.625rem;
219
+ border-radius: 0.5rem;
220
+ cursor: pointer;
221
+ transition: background 0.2s;
222
+ }
223
+
224
+ .category-item:hover {
225
+ background: var(--bg-hover);
226
+ }
227
+
228
+ .category-item.active {
229
+ background: var(--bg-hover);
230
+ }
231
+
232
+ .category-dot {
233
+ width: 8px;
234
+ height: 8px;
235
+ border-radius: 50%;
236
+ }
237
+
238
+ .category-dot.gotcha { background: var(--category-gotcha); }
239
+ .category-dot.problem { background: var(--category-problem); }
240
+ .category-dot.decision { background: var(--category-decision); }
241
+ .category-dot.discovery { background: var(--category-discovery); }
242
+
243
+ .category-name {
244
+ flex: 1;
245
+ font-size: 0.875rem;
246
+ }
247
+
248
+ .category-count {
249
+ font-size: 0.75rem;
250
+ color: var(--text-muted);
251
+ font-family: 'JetBrains Mono', monospace;
252
+ }
253
+
254
+ /* Memory List */
255
+ .memories-section {
256
+ background: var(--bg-card);
257
+ border: 1px solid var(--border-color);
258
+ border-radius: 0.75rem;
259
+ padding: 1.5rem;
260
+ }
261
+
262
+ .memories-header {
263
+ display: flex;
264
+ justify-content: space-between;
265
+ align-items: center;
266
+ margin-bottom: 1.5rem;
267
+ }
268
+
269
+ .memories-title {
270
+ font-size: 1.125rem;
271
+ font-weight: 600;
272
+ }
273
+
274
+ .memories-count {
275
+ font-size: 0.875rem;
276
+ color: var(--text-muted);
277
+ }
278
+
279
+ .memory-list {
280
+ display: flex;
281
+ flex-direction: column;
282
+ gap: 0.75rem;
283
+ }
284
+
285
+ .memory-item {
286
+ background: var(--bg-dark);
287
+ border: 1px solid var(--border-color);
288
+ border-radius: 0.5rem;
289
+ padding: 1rem;
290
+ cursor: pointer;
291
+ transition: all 0.2s;
292
+ }
293
+
294
+ .memory-item:hover {
295
+ border-color: var(--accent-red);
296
+ }
297
+
298
+ .memory-header {
299
+ display: flex;
300
+ justify-content: space-between;
301
+ align-items: flex-start;
302
+ margin-bottom: 0.5rem;
303
+ }
304
+
305
+ .memory-category {
306
+ display: flex;
307
+ align-items: center;
308
+ gap: 0.375rem;
309
+ font-size: 0.75rem;
310
+ font-weight: 500;
311
+ padding: 0.25rem 0.5rem;
312
+ border-radius: 0.25rem;
313
+ background: rgba(255,255,255,0.05);
314
+ }
315
+
316
+ .memory-date {
317
+ font-size: 0.75rem;
318
+ color: var(--text-muted);
319
+ font-family: 'JetBrains Mono', monospace;
320
+ }
321
+
322
+ .memory-content {
323
+ font-size: 0.875rem;
324
+ color: var(--text-secondary);
325
+ line-height: 1.5;
326
+ display: -webkit-box;
327
+ -webkit-line-clamp: 2;
328
+ -webkit-box-orient: vertical;
329
+ overflow: hidden;
330
+ }
331
+
332
+ .memory-layer {
333
+ margin-top: 0.75rem;
334
+ font-size: 0.75rem;
335
+ color: var(--text-muted);
336
+ }
337
+
338
+ /* Modal */
339
+ .modal-overlay {
340
+ display: none;
341
+ position: fixed;
342
+ top: 0;
343
+ left: 0;
344
+ right: 0;
345
+ bottom: 0;
346
+ background: rgba(0,0,0,0.8);
347
+ z-index: 100;
348
+ align-items: center;
349
+ justify-content: center;
350
+ }
351
+
352
+ .modal-overlay.active {
353
+ display: flex;
354
+ }
355
+
356
+ .modal {
357
+ background: var(--bg-card);
358
+ border: 1px solid var(--border-color);
359
+ border-radius: 0.75rem;
360
+ width: 90%;
361
+ max-width: 700px;
362
+ max-height: 90vh;
363
+ overflow-y: auto;
364
+ }
365
+
366
+ .modal-header {
367
+ display: flex;
368
+ justify-content: space-between;
369
+ align-items: center;
370
+ padding: 1.25rem;
371
+ border-bottom: 1px solid var(--border-color);
372
+ }
373
+
374
+ .modal-title {
375
+ font-size: 1rem;
376
+ font-weight: 600;
377
+ }
378
+
379
+ .modal-close {
380
+ background: none;
381
+ border: none;
382
+ color: var(--text-muted);
383
+ font-size: 1.5rem;
384
+ cursor: pointer;
385
+ }
386
+
387
+ .modal-body {
388
+ padding: 1.5rem;
389
+ }
390
+
391
+ .modal-content {
392
+ white-space: pre-wrap;
393
+ font-size: 0.9375rem;
394
+ line-height: 1.7;
395
+ color: var(--text-secondary);
396
+ }
397
+
398
+ /* Loading */
399
+ .loading {
400
+ text-align: center;
401
+ padding: 3rem;
402
+ color: var(--text-muted);
403
+ }
404
+
405
+ .spinner {
406
+ display: inline-block;
407
+ width: 24px;
408
+ height: 24px;
409
+ border: 2px solid var(--border-color);
410
+ border-top-color: var(--accent-red);
411
+ border-radius: 50%;
412
+ animation: spin 1s linear infinite;
413
+ margin-right: 0.75rem;
414
+ }
415
+
416
+ @keyframes spin {
417
+ to { transform: rotate(360deg); }
418
+ }
419
+
420
+ /* Empty State */
421
+ .empty-state {
422
+ text-align: center;
423
+ padding: 4rem 2rem;
424
+ color: var(--text-muted);
425
+ }
426
+
427
+ .empty-icon {
428
+ font-size: 3rem;
429
+ margin-bottom: 1rem;
430
+ }
431
+
432
+ /* Responsive */
433
+ @media (max-width: 1024px) {
434
+ .grid {
435
+ grid-template-columns: 1fr;
436
+ }
437
+
438
+ .stats-grid {
439
+ grid-template-columns: repeat(3, 1fr);
440
+ }
441
+
442
+ .sidebar {
443
+ order: 2;
444
+ }
445
+ }
446
+
447
+ @media (max-width: 640px) {
448
+ .header {
449
+ flex-direction: column;
450
+ gap: 1rem;
451
+ text-align: center;
452
+ }
453
+
454
+ .stats-grid {
455
+ grid-template-columns: repeat(2, 1fr);
456
+ }
457
+
458
+ .container {
459
+ padding: 1rem;
460
+ }
461
+ }
462
+ </style>
463
+ </head>
464
+ <body>
465
+ <header class="header">
466
+ <div class="logo">
467
+ <span class="logo-icon">🦞</span>
468
+ <span class="logo-text">Super Memory</span>
469
+ </div>
470
+ <div class="header-actions">
471
+ <button class="btn btn-secondary" onclick="refreshData()">
472
+ 🔄 Refresh
473
+ </button>
474
+ <button class="btn btn-secondary" onclick="exportData()">
475
+ 📥 Export
476
+ </button>
477
+ </div>
478
+ </header>
479
+
480
+ <main class="container">
481
+ <!-- Action Bar -->
482
+ <section class="action-bar">
483
+ <button class="btn btn-primary" onclick="runIndexUpdate()">
484
+ 🗂️ Update Index
485
+ </button>
486
+ <button class="btn btn-secondary" onclick="promptFlush()">
487
+ 💾 Flush Context
488
+ </button>
489
+ <button class="btn btn-secondary" onclick="runMaintenance()">
490
+ 🧹 Maintenance
491
+ </button>
492
+ </section>
493
+
494
+ <!-- Stats Section -->
495
+ <section class="stats-section">
496
+ <div class="stats-grid" id="stats-grid">
497
+ <div class="stat-card">
498
+ <div class="spinner"></div>
499
+ <div class="stat-label">Loading...</div>
500
+ </div>
501
+ </div>
502
+ </section>
503
+
504
+ <!-- Search Section -->
505
+ <section class="search-section">
506
+ <input
507
+ type="text"
508
+ class="search-input"
509
+ id="search-input"
510
+ placeholder="🔍 Search your memories..."
511
+ onkeyup="handleSearch(event)"
512
+ >
513
+ </section>
514
+
515
+ <div class="grid">
516
+ <!-- Sidebar -->
517
+ <aside class="sidebar">
518
+ <h3 class="sidebar-title">Categories</h3>
519
+ <ul class="category-list" id="category-list">
520
+ <li class="category-item active" onclick="filterCategory('all')">
521
+ <span class="category-dot" style="background: #666;"></span>
522
+ <span class="category-name">All</span>
523
+ <span class="category-count" id="count-all">-</span>
524
+ </li>
525
+ <li class="category-item" onclick="filterCategory('gotcha')">
526
+ <span class="category-dot gotcha"></span>
527
+ <span class="category-name">Gotcha</span>
528
+ <span class="category-count" id="count-gotcha">-</span>
529
+ </li>
530
+ <li class="category-item" onclick="filterCategory('problem')">
531
+ <span class="category-dot problem"></span>
532
+ <span class="category-name">Problem-Fix</span>
533
+ <span class="category-count" id="count-problem">-</span>
534
+ </li>
535
+ <li class="category-item" onclick="filterCategory('decision')">
536
+ <span class="category-dot decision"></span>
537
+ <span class="category-name">Decision</span>
538
+ <span class="category-count" id="count-decision">-</span>
539
+ </li>
540
+ <li class="category-item" onclick="filterCategory('discovery')">
541
+ <span class="category-dot discovery"></span>
542
+ <span class="category-name">Discovery</span>
543
+ <span class="category-count" id="count-discovery">-</span>
544
+ </li>
545
+ </ul>
546
+ </aside>
547
+
548
+ <!-- Memories List -->
549
+ <section class="memories-section">
550
+ <div class="memories-header">
551
+ <h2 class="memories-title">Recent Memories</h2>
552
+ <span class="memories-count" id="memories-count">Loading...</span>
553
+ </div>
554
+ <div class="memory-list" id="memory-list">
555
+ <div class="loading">
556
+ <div class="spinner"></div>
557
+ Loading memories...
558
+ </div>
559
+ </div>
560
+ </section>
561
+ </div>
562
+ </main>
563
+
564
+ <!-- Modal -->
565
+ <div class="modal-overlay" id="modal" onclick="closeModal(event)">
566
+ <div class="modal" onclick="event.stopPropagation()">
567
+ <div class="modal-header">
568
+ <h3 class="modal-title" id="modal-title">Memory Details</h3>
569
+ <button class="modal-close" onclick="closeModal()">×</button>
570
+ </div>
571
+ <div class="modal-body">
572
+ <div class="modal-content" id="modal-content"></div>
573
+ <div style="margin-top: 1.5rem; display: flex; gap: 0.5rem; border-top: 1px solid var(--border-color); padding-top: 1rem;">
574
+ <button class="btn btn-secondary" id="modal-categorize-btn" style="padding: 0.25rem 0.5rem; font-size: 0.75rem;">
575
+ 🧠 Auto-Categorize
576
+ </button>
577
+ </div>
578
+ </div>
579
+ </div>
580
+ </div>
581
+
582
+ <script>
583
+ // State
584
+ let currentCategory = 'all';
585
+ let memories = [];
586
+
587
+ // Actions
588
+ async function runIndexUpdate() {
589
+ if (!confirm('Update progressive MEMORY_INDEX.md? This will optimize token usage by 70%.')) return;
590
+ try {
591
+ const response = await fetch('/api/index-update', { method: 'POST' });
592
+ const result = await response.json();
593
+ if (result.success) alert('✅ Index updated successfully!');
594
+ else alert('❌ Failed: ' + result.error);
595
+ refreshData();
596
+ } catch (error) { alert('Error: ' + error.message); }
597
+ }
598
+
599
+ async function promptFlush() {
600
+ const context = prompt('Enter critical context to flush (protected from session compaction):');
601
+ if (!context) return;
602
+ try {
603
+ const response = await fetch('/api/flush', {
604
+ method: 'POST',
605
+ headers: { 'Content-Type': 'application/json' },
606
+ body: JSON.stringify({ content: context })
607
+ });
608
+ const result = await response.json();
609
+ if (result.success) alert('✅ Context flushed!');
610
+ else alert('❌ Failed: ' + result.error);
611
+ refreshData();
612
+ } catch (error) { alert('Error: ' + error.message); }
613
+ }
614
+
615
+ async function runMaintenance() {
616
+ try {
617
+ const response = await fetch('/api/maintenance');
618
+ const result = await response.json();
619
+ if (result.success && result.suggestions.length > 0) {
620
+ let msg = '🧹 Maintenance Suggestions:\n\n';
621
+ result.suggestions.forEach(s => msg += `• [${s.priority.toUpperCase()}] ${s.message}\n 👉 Action: ${s.action}\n\n`);
622
+ alert(msg);
623
+ } else {
624
+ alert('✅ System healthy. No maintenance needed.');
625
+ }
626
+ } catch (error) { alert('Error: ' + error.message); }
627
+ }
628
+
629
+ async function runCategorize(id) {
630
+ try {
631
+ const response = await fetch(`/api/categorize/${id}`, { method: 'POST' });
632
+ const result = await response.json();
633
+ if (result.success) {
634
+ alert('✅ Re-categorized as: ' + result.newCategory.toUpperCase());
635
+ refreshData();
636
+ } else {
637
+ alert('❌ Failed: ' + result.error);
638
+ }
639
+ } catch (error) { alert('Error: ' + error.message); }
640
+ }
641
+
642
+ // Initialize
643
+ document.addEventListener('DOMContentLoaded', () => {
644
+ loadStats();
645
+ loadMemories();
646
+ });
647
+
648
+ // Load Stats
649
+ async function loadStats() {
650
+ try {
651
+ const response = await fetch('/api/stats');
652
+ const stats = await response.json();
653
+ renderStats(stats);
654
+ } catch (error) {
655
+ console.error('Failed to load stats:', error);
656
+ }
657
+ }
658
+
659
+ // Render Stats
660
+ function renderStats(stats) {
661
+ const grid = document.getElementById('stats-grid');
662
+ grid.innerHTML = `
663
+ <div class="stat-card">
664
+ <div class="stat-icon">💭</div>
665
+ <div class="stat-value">${stats.layers.session?.count || 0}</div>
666
+ <div class="stat-label">Session Context</div>
667
+ </div>
668
+ <div class="stat-card">
669
+ <div class="stat-icon">📅</div>
670
+ <div class="stat-value">${stats.layers.daily?.count || 0}</div>
671
+ <div class="stat-label">Daily Logs</div>
672
+ </div>
673
+ <div class="stat-card">
674
+ <div class="stat-icon">🧠</div>
675
+ <div class="stat-value">${stats.layers.longterm?.exists ? '✓' : '✗'}</div>
676
+ <div class="stat-label">Long-term Memory</div>
677
+ </div>
678
+ <div class="stat-card">
679
+ <div class="stat-icon">📚</div>
680
+ <div class="stat-value">${stats.layers.index?.exists ? '✓' : '✗'}</div>
681
+ <div class="stat-label">Memory Index</div>
682
+ </div>
683
+ <div class="stat-card">
684
+ <div class="stat-icon">📝</div>
685
+ <div class="stat-value">${stats.layers.logs?.count || 0}</div>
686
+ <div class="stat-label">Action Logs</div>
687
+ </div>
688
+ `;
689
+
690
+ // Update category counts
691
+ document.getElementById('count-gotcha').textContent = stats.categories?.gotcha || 0;
692
+ document.getElementById('count-problem').textContent = stats.categories?.problem || 0;
693
+ document.getElementById('count-decision').textContent = stats.categories?.decision || 0;
694
+ document.getElementById('count-discovery').textContent = stats.categories?.discovery || 0;
695
+ document.getElementById('count-all').textContent = stats.totalEntries || 0;
696
+ }
697
+
698
+ // Load Memories
699
+ async function loadMemories() {
700
+ try {
701
+ const response = await fetch('/api/entries');
702
+ const data = await response.json();
703
+ memories = data.entries || [];
704
+ renderMemories(memories);
705
+ } catch (error) {
706
+ console.error('Failed to load memories:', error);
707
+ document.getElementById('memory-list').innerHTML = `
708
+ <div class="empty-state">
709
+ <div class="empty-icon">❌</div>
710
+ <p>Failed to load memories</p>
711
+ </div>
712
+ `;
713
+ }
714
+ }
715
+
716
+ // Render Memories
717
+ function renderMemories(entries) {
718
+ const list = document.getElementById('memory-list');
719
+ document.getElementById('memories-count').textContent = `${entries.length} memories`;
720
+
721
+ if (entries.length === 0) {
722
+ list.innerHTML = `
723
+ <div class="empty-state">
724
+ <div class="empty-icon">📝</div>
725
+ <p>No memories found</p>
726
+ <p style="font-size: 0.875rem; margin-top: 0.5rem;">
727
+ Use <code>super-memory save "your memory"</code> to add one
728
+ </p>
729
+ </div>
730
+ `;
731
+ return;
732
+ }
733
+
734
+ list.innerHTML = entries.map(entry => `
735
+ <div class="memory-item" onclick="openModal('${entry.id}')">
736
+ <div class="memory-header">
737
+ <span class="memory-category">
738
+ ${getCategoryEmoji(entry.category)} ${entry.category}
739
+ </span>
740
+ <span class="memory-date">${formatDate(entry.date)}</span>
741
+ </div>
742
+ <div class="memory-content">${escapeHtml(entry.preview || entry.content)}</div>
743
+ <div class="memory-layer">Layer ${entry.layer} • ${entry.source || 'Unknown'}</div>
744
+ </div>
745
+ `).join('');
746
+ }
747
+
748
+ // Search
749
+ async function handleSearch(event) {
750
+ if (event.key === 'Enter') {
751
+ const query = event.target.value;
752
+ if (!query) {
753
+ renderMemories(memories);
754
+ return;
755
+ }
756
+
757
+ try {
758
+ const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
759
+ const results = await response.json();
760
+ renderMemories(results);
761
+ } catch (error) {
762
+ console.error('Search failed:', error);
763
+ }
764
+ }
765
+ }
766
+
767
+ // Filter Category
768
+ function filterCategory(category) {
769
+ currentCategory = category;
770
+
771
+ // Update UI
772
+ document.querySelectorAll('.category-item').forEach(item => {
773
+ item.classList.remove('active');
774
+ });
775
+ event.currentTarget.classList.add('active');
776
+
777
+ // Filter memories
778
+ if (category === 'all') {
779
+ renderMemories(memories);
780
+ } else {
781
+ const filtered = memories.filter(m => m.category === category);
782
+ renderMemories(filtered);
783
+ }
784
+ }
785
+
786
+ // Open Modal
787
+ async function openModal(id) {
788
+ try {
789
+ const response = await fetch(`/api/memory/${id}`);
790
+ const memory = await response.json();
791
+
792
+ document.getElementById('modal-title').textContent =
793
+ `${getCategoryEmoji(memory.category)} ${memory.category}`;
794
+ document.getElementById('modal-content').textContent = memory.content;
795
+
796
+ const catBtn = document.getElementById('modal-categorize-btn');
797
+ catBtn.onclick = () => runCategorize(id);
798
+
799
+ document.getElementById('modal').classList.add('active');
800
+ } catch (error) {
801
+ console.error('Failed to load memory:', error);
802
+ }
803
+ }
804
+
805
+ // Close Modal
806
+ function closeModal(event) {
807
+ if (!event || event.target === document.getElementById('modal')) {
808
+ document.getElementById('modal').classList.remove('active');
809
+ }
810
+ }
811
+
812
+ // Export
813
+ function exportData() {
814
+ window.location.href = '/api/export';
815
+ }
816
+
817
+ // Refresh
818
+ function refreshData() {
819
+ loadStats();
820
+ loadMemories();
821
+ }
822
+
823
+ // Helpers
824
+ function getCategoryEmoji(category) {
825
+ const emojis = {
826
+ gotcha: '🔴',
827
+ problem: '🟡',
828
+ decision: '🟤',
829
+ discovery: '🟣'
830
+ };
831
+ return emojis[category] || '⚪';
832
+ }
833
+
834
+ function formatDate(dateStr) {
835
+ if (!dateStr) return 'Unknown';
836
+ const date = new Date(dateStr);
837
+ return date.toLocaleDateString('en-US', {
838
+ year: 'numeric',
839
+ month: 'short',
840
+ day: 'numeric'
841
+ });
842
+ }
843
+
844
+ function escapeHtml(text) {
845
+ const div = document.createElement('div');
846
+ div.textContent = text;
847
+ return div.innerHTML;
848
+ }
849
+ </script>
850
+ </body>
851
+ </html>