superlocalmemory 2.6.0 → 2.7.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.
- package/CHANGELOG.md +167 -1803
- package/README.md +212 -397
- package/bin/slm +179 -3
- package/bin/superlocalmemoryv2:learning +4 -0
- package/bin/superlocalmemoryv2:patterns +4 -0
- package/docs/ACCESSIBILITY.md +291 -0
- package/docs/ARCHITECTURE.md +12 -6
- package/docs/FRAMEWORK-INTEGRATIONS.md +300 -0
- package/docs/MCP-MANUAL-SETUP.md +14 -4
- package/install.sh +99 -3
- package/mcp_server.py +291 -1
- package/package.json +2 -1
- package/requirements-learning.txt +12 -0
- package/scripts/verify-v27.sh +233 -0
- package/skills/slm-show-patterns/SKILL.md +224 -0
- package/src/learning/__init__.py +201 -0
- package/src/learning/adaptive_ranker.py +826 -0
- package/src/learning/cross_project_aggregator.py +866 -0
- package/src/learning/engagement_tracker.py +638 -0
- package/src/learning/feature_extractor.py +461 -0
- package/src/learning/feedback_collector.py +690 -0
- package/src/learning/learning_db.py +842 -0
- package/src/learning/project_context_manager.py +582 -0
- package/src/learning/source_quality_scorer.py +685 -0
- package/src/learning/synthetic_bootstrap.py +1047 -0
- package/src/learning/tests/__init__.py +0 -0
- package/src/learning/tests/test_adaptive_ranker.py +328 -0
- package/src/learning/tests/test_aggregator.py +309 -0
- package/src/learning/tests/test_feedback_collector.py +295 -0
- package/src/learning/tests/test_learning_db.py +606 -0
- package/src/learning/tests/test_project_context.py +296 -0
- package/src/learning/tests/test_source_quality.py +355 -0
- package/src/learning/tests/test_synthetic_bootstrap.py +433 -0
- package/src/learning/tests/test_workflow_miner.py +322 -0
- package/src/learning/workflow_pattern_miner.py +665 -0
- package/ui/index.html +346 -13
- package/ui/js/clusters.js +90 -1
- package/ui/js/graph-core.js +445 -0
- package/ui/js/graph-cytoscape-monolithic-backup.js +1168 -0
- package/ui/js/graph-cytoscape.js +1168 -0
- package/ui/js/graph-d3-backup.js +32 -0
- package/ui/js/graph-filters.js +220 -0
- package/ui/js/graph-interactions.js +354 -0
- package/ui/js/graph-ui.js +214 -0
- package/ui/js/memories.js +52 -0
- package/ui/js/modal.js +104 -1
package/ui/index.html
CHANGED
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
|
|
21
21
|
body {
|
|
22
22
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
23
|
+
overflow-y: scroll; /* Always show scrollbar to prevent layout shift */
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
html {
|
|
27
|
+
scroll-behavior: smooth;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
[data-bs-theme="light"] body,
|
|
@@ -70,12 +75,35 @@
|
|
|
70
75
|
margin: 0;
|
|
71
76
|
}
|
|
72
77
|
|
|
78
|
+
.tab-content {
|
|
79
|
+
min-height: 700px;
|
|
80
|
+
overflow: visible;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.tab-pane {
|
|
84
|
+
overflow: visible;
|
|
85
|
+
}
|
|
86
|
+
|
|
73
87
|
#graph-container {
|
|
74
88
|
background: var(--bs-body-bg);
|
|
75
89
|
border-radius: 10px;
|
|
76
90
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
77
|
-
|
|
91
|
+
height: 600px !important;
|
|
92
|
+
max-height: 600px;
|
|
93
|
+
width: 100% !important;
|
|
78
94
|
position: relative;
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
/* Mobile touch support - prevent default gestures */
|
|
97
|
+
touch-action: none;
|
|
98
|
+
-webkit-user-select: none;
|
|
99
|
+
-moz-user-select: none;
|
|
100
|
+
-ms-user-select: none;
|
|
101
|
+
user-select: none;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#graph-container canvas {
|
|
105
|
+
max-width: 100% !important;
|
|
106
|
+
max-height: 100% !important;
|
|
79
107
|
}
|
|
80
108
|
|
|
81
109
|
.node {
|
|
@@ -363,6 +391,244 @@
|
|
|
363
391
|
[data-bs-theme="dark"] footer a {
|
|
364
392
|
color: #8fa4ff;
|
|
365
393
|
}
|
|
394
|
+
|
|
395
|
+
/* ========================================
|
|
396
|
+
MOBILE & TABLET RESPONSIVE STYLES
|
|
397
|
+
======================================== */
|
|
398
|
+
|
|
399
|
+
/* Tablet (768px and below) */
|
|
400
|
+
@media (max-width: 768px) {
|
|
401
|
+
.navbar-brand {
|
|
402
|
+
font-size: 1rem;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
#navbar-subtitle {
|
|
406
|
+
display: none !important;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.stat-card {
|
|
410
|
+
margin-bottom: 12px;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.stat-value {
|
|
414
|
+
font-size: 1.4rem;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.stat-icon {
|
|
418
|
+
font-size: 1.5rem;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* Graph container - smaller on tablet */
|
|
422
|
+
#graph-container {
|
|
423
|
+
height: 450px !important;
|
|
424
|
+
max-height: 450px;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/* Stack controls vertically on tablet */
|
|
428
|
+
.tab-pane .row > .col-md-6,
|
|
429
|
+
.tab-pane .row > .col-md-8,
|
|
430
|
+
.tab-pane .row > .col-md-4 {
|
|
431
|
+
margin-bottom: 10px;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/* Smaller buttons on mobile */
|
|
435
|
+
.btn-sm {
|
|
436
|
+
font-size: 0.8rem;
|
|
437
|
+
padding: 4px 8px;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/* Memory table - hide less important columns */
|
|
441
|
+
.memory-table th:nth-child(4),
|
|
442
|
+
.memory-table td:nth-child(4),
|
|
443
|
+
.memory-table th:nth-child(5),
|
|
444
|
+
.memory-table td:nth-child(5) {
|
|
445
|
+
display: none;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.memory-content {
|
|
449
|
+
max-width: 200px;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/* Mobile (480px and below) */
|
|
454
|
+
@media (max-width: 480px) {
|
|
455
|
+
body {
|
|
456
|
+
font-size: 0.9rem;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.navbar-brand {
|
|
460
|
+
font-size: 0.9rem;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.container-fluid {
|
|
464
|
+
padding-left: 10px;
|
|
465
|
+
padding-right: 10px;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/* Stat cards - 2 per row on mobile */
|
|
469
|
+
.col-6.mb-3 {
|
|
470
|
+
padding-left: 5px;
|
|
471
|
+
padding-right: 5px;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.stat-card {
|
|
475
|
+
padding: 12px 8px !important;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.stat-value {
|
|
479
|
+
font-size: 1.2rem;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.stat-icon {
|
|
483
|
+
font-size: 1.2rem;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.stat-card small {
|
|
487
|
+
font-size: 0.7rem;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/* Graph container - even smaller on mobile */
|
|
491
|
+
#graph-container {
|
|
492
|
+
height: 350px !important;
|
|
493
|
+
max-height: 350px;
|
|
494
|
+
border-radius: 6px;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/* Tabs - smaller text */
|
|
498
|
+
.nav-tabs .nav-link {
|
|
499
|
+
font-size: 0.8rem;
|
|
500
|
+
padding: 8px 10px;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.nav-tabs .nav-link i {
|
|
504
|
+
display: none;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/* Search controls - stack vertically */
|
|
508
|
+
#memories-pane .row > div {
|
|
509
|
+
margin-bottom: 8px;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/* Memory table - show only essentials */
|
|
513
|
+
.memory-table {
|
|
514
|
+
font-size: 0.8rem;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.memory-table th:nth-child(3),
|
|
518
|
+
.memory-table td:nth-child(3),
|
|
519
|
+
.memory-table th:nth-child(4),
|
|
520
|
+
.memory-table td:nth-child(4),
|
|
521
|
+
.memory-table th:nth-child(5),
|
|
522
|
+
.memory-table td:nth-child(5) {
|
|
523
|
+
display: none;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.memory-content {
|
|
527
|
+
max-width: 150px;
|
|
528
|
+
font-size: 0.75rem;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/* Modal - full screen on mobile */
|
|
532
|
+
.modal-dialog {
|
|
533
|
+
margin: 0;
|
|
534
|
+
max-width: 100%;
|
|
535
|
+
height: 100vh;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.modal-content {
|
|
539
|
+
height: 100%;
|
|
540
|
+
border-radius: 0;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/* Dropdown controls */
|
|
544
|
+
.form-select-sm {
|
|
545
|
+
font-size: 0.8rem;
|
|
546
|
+
padding: 4px 8px;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/* Profile select - smaller */
|
|
550
|
+
.profile-select {
|
|
551
|
+
min-width: 90px;
|
|
552
|
+
font-size: 0.75rem;
|
|
553
|
+
padding: 2px 24px 2px 8px;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/* Theme toggle - smaller */
|
|
557
|
+
.theme-toggle {
|
|
558
|
+
font-size: 0.8rem;
|
|
559
|
+
padding: 3px 10px;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/* Footer - smaller text */
|
|
563
|
+
footer {
|
|
564
|
+
font-size: 0.75rem;
|
|
565
|
+
padding: 15px;
|
|
566
|
+
margin-top: 20px;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/* Graph controls - stack vertically on mobile */
|
|
570
|
+
#graph-pane .card .row > div {
|
|
571
|
+
margin-bottom: 10px;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/* Hide "Show All Memories" button text on mobile, icon only */
|
|
575
|
+
#graph-status-filtered .btn span {
|
|
576
|
+
display: none;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
#graph-status-filtered .btn i::after {
|
|
580
|
+
content: ' All';
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/* Landscape mobile (small height) */
|
|
585
|
+
@media (max-height: 500px) and (orientation: landscape) {
|
|
586
|
+
#graph-container {
|
|
587
|
+
height: 250px !important;
|
|
588
|
+
max-height: 250px;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.stat-card {
|
|
592
|
+
padding: 8px !important;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.stat-value {
|
|
596
|
+
font-size: 1rem;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.navbar {
|
|
600
|
+
padding: 5px 0;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/* Touch feedback for interactive elements */
|
|
605
|
+
@media (hover: none) and (pointer: coarse) {
|
|
606
|
+
/* This targets touch devices only */
|
|
607
|
+
.btn:active,
|
|
608
|
+
.nav-link:active,
|
|
609
|
+
.sortable:active {
|
|
610
|
+
opacity: 0.7;
|
|
611
|
+
transform: scale(0.98);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/* Larger tap targets on touch devices */
|
|
615
|
+
.btn {
|
|
616
|
+
min-height: 44px;
|
|
617
|
+
min-width: 44px;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.nav-tabs .nav-link {
|
|
621
|
+
min-height: 44px;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/* Prevent accidental double-tap zoom on buttons */
|
|
625
|
+
.btn,
|
|
626
|
+
.nav-link,
|
|
627
|
+
.form-control,
|
|
628
|
+
.form-select {
|
|
629
|
+
touch-action: manipulation;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
366
632
|
</style>
|
|
367
633
|
</head>
|
|
368
634
|
<body>
|
|
@@ -383,8 +649,8 @@
|
|
|
383
649
|
</button>
|
|
384
650
|
</div>
|
|
385
651
|
<span class="text-white-50 d-none d-md-inline" id="navbar-subtitle">Knowledge Graph Explorer</span>
|
|
386
|
-
<button class="theme-toggle" id="theme-toggle" onclick="toggleDarkMode()"
|
|
387
|
-
<i class="bi bi-sun-fill" id="theme-icon"></i>
|
|
652
|
+
<button class="theme-toggle" id="theme-toggle" onclick="toggleDarkMode()" aria-label="Toggle dark mode">
|
|
653
|
+
<i class="bi bi-sun-fill" id="theme-icon" aria-hidden="true"></i>
|
|
388
654
|
</button>
|
|
389
655
|
</div>
|
|
390
656
|
</div>
|
|
@@ -475,24 +741,77 @@
|
|
|
475
741
|
<!-- Graph Visualization -->
|
|
476
742
|
<div class="tab-pane fade show active" id="graph-pane">
|
|
477
743
|
<div class="card p-3 mb-3">
|
|
478
|
-
<div class="row align-items-center">
|
|
744
|
+
<div class="row align-items-center mb-2">
|
|
479
745
|
<div class="col-md-8">
|
|
480
|
-
<h5 class="mb-0">
|
|
481
|
-
<small class="text-muted">
|
|
746
|
+
<h5 class="mb-0">Interactive Knowledge Graph</h5>
|
|
747
|
+
<small class="text-muted">Zoom, pan, click nodes to explore • Powered by Cytoscape.js</small>
|
|
748
|
+
<div id="graph-stats" class="mt-2"></div>
|
|
482
749
|
</div>
|
|
483
750
|
<div class="col-md-4 text-end">
|
|
484
|
-
<button class="btn btn-sm btn-outline-primary" onclick="loadGraph()">
|
|
751
|
+
<button class="btn btn-sm btn-outline-primary" onclick="loadGraph()" aria-label="Refresh graph data">
|
|
485
752
|
<i class="bi bi-arrow-clockwise"></i> Refresh
|
|
486
753
|
</button>
|
|
487
|
-
<select class="form-select form-select-sm d-inline-block w-auto" id="graph-max-nodes" onchange="loadGraph()">
|
|
488
|
-
<option value="50">50 nodes</option>
|
|
489
|
-
<option value="100"
|
|
754
|
+
<select class="form-select form-select-sm d-inline-block w-auto" id="graph-max-nodes" onchange="loadGraph()" aria-label="Select maximum number of nodes to display">
|
|
755
|
+
<option value="50" selected>50 nodes</option>
|
|
756
|
+
<option value="100">100 nodes</option>
|
|
490
757
|
<option value="200">200 nodes</option>
|
|
758
|
+
<option value="500">500 nodes</option>
|
|
491
759
|
</select>
|
|
492
760
|
</div>
|
|
493
761
|
</div>
|
|
762
|
+
<div class="row align-items-center">
|
|
763
|
+
<div class="col-md-6">
|
|
764
|
+
<label class="form-label mb-1 small" for="graph-layout-selector">Layout Algorithm:</label>
|
|
765
|
+
<select class="form-select form-select-sm" id="graph-layout-selector" onchange="changeGraphLayout(this.value)" aria-label="Select graph layout algorithm">
|
|
766
|
+
<option value="fcose" selected>Force-Directed (Fast)</option>
|
|
767
|
+
<option value="cose">Force-Directed (Classic)</option>
|
|
768
|
+
<option value="circle">Circular</option>
|
|
769
|
+
<option value="grid">Grid</option>
|
|
770
|
+
<option value="breadthfirst">Hierarchical</option>
|
|
771
|
+
<option value="concentric">Concentric (by Importance)</option>
|
|
772
|
+
</select>
|
|
773
|
+
</div>
|
|
774
|
+
<div class="col-md-6">
|
|
775
|
+
<!-- Clear status indicator - shows what user is viewing -->
|
|
776
|
+
<div id="graph-status-container">
|
|
777
|
+
<!-- When viewing FULL graph (default state) -->
|
|
778
|
+
<div id="graph-status-full" style="display:block;" role="status" aria-live="polite">
|
|
779
|
+
<div class="d-flex align-items-center">
|
|
780
|
+
<span class="text-muted small">
|
|
781
|
+
<i class="bi bi-diagram-3"></i> <span id="graph-status-full-text">Showing all memories</span>
|
|
782
|
+
</span>
|
|
783
|
+
<button class="btn btn-sm btn-outline-secondary ms-2" onclick="loadGraph()" aria-label="Refresh current graph view">
|
|
784
|
+
<i class="bi bi-arrow-clockwise"></i> Refresh
|
|
785
|
+
</button>
|
|
786
|
+
</div>
|
|
787
|
+
</div>
|
|
788
|
+
|
|
789
|
+
<!-- When viewing FILTERED by cluster (show BIG clear button) -->
|
|
790
|
+
<div id="graph-status-filtered" style="display:none;" role="status" aria-live="polite">
|
|
791
|
+
<div class="alert alert-info py-2 px-3 mb-0 d-flex align-items-center justify-content-between">
|
|
792
|
+
<span>
|
|
793
|
+
<i class="bi bi-funnel-fill"></i>
|
|
794
|
+
<strong id="graph-filter-description">Viewing Cluster X</strong>
|
|
795
|
+
<span class="small text-muted ms-2" id="graph-filter-count">(X memories)</span>
|
|
796
|
+
</span>
|
|
797
|
+
<button class="btn btn-primary btn-sm ms-3" onclick="clearGraphFilters()" aria-label="Clear filter and show all memories">
|
|
798
|
+
<i class="bi bi-grid-3x3"></i> Show All Memories
|
|
799
|
+
</button>
|
|
800
|
+
</div>
|
|
801
|
+
</div>
|
|
802
|
+
</div>
|
|
803
|
+
</div>
|
|
804
|
+
</div>
|
|
805
|
+
</div>
|
|
806
|
+
<div id="graph-container"
|
|
807
|
+
role="application"
|
|
808
|
+
aria-label="Interactive knowledge graph - use Tab to navigate nodes, Enter to view details, Arrow keys to move between adjacent nodes, Escape to clear filters"
|
|
809
|
+
aria-describedby="graph-stats"
|
|
810
|
+
style="width:100%; height:600px; border:1px solid #dee2e6; border-radius:8px; background:#f8f9fa;">
|
|
494
811
|
</div>
|
|
495
|
-
|
|
812
|
+
|
|
813
|
+
<!-- Skip link for keyboard users -->
|
|
814
|
+
<a href="#memories-pane" class="visually-hidden-focusable">Skip to Memories list</a>
|
|
496
815
|
</div>
|
|
497
816
|
|
|
498
817
|
<!-- Memories List -->
|
|
@@ -826,12 +1145,26 @@
|
|
|
826
1145
|
|
|
827
1146
|
<!-- Bootstrap JS -->
|
|
828
1147
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
829
|
-
<!-- D3.js -->
|
|
1148
|
+
<!-- D3.js (kept for backward compatibility) -->
|
|
830
1149
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
831
1150
|
|
|
1151
|
+
<!-- Cytoscape.js + Extensions (v2.6.5 — interactive graph) -->
|
|
1152
|
+
<script src="https://unpkg.com/cytoscape@3.30.4/dist/cytoscape.min.js"></script>
|
|
1153
|
+
<script src="https://unpkg.com/layout-base@2.0.1/layout-base.js"></script>
|
|
1154
|
+
<script src="https://unpkg.com/cose-base@2.2.0/cose-base.js"></script>
|
|
1155
|
+
<script src="https://unpkg.com/cytoscape-fcose@2.2.0/cytoscape-fcose.js"></script>
|
|
1156
|
+
<link href="https://unpkg.com/cytoscape-navigator@1.3.3/cytoscape.js-navigator.css" rel="stylesheet" type="text/css" />
|
|
1157
|
+
<script src="https://unpkg.com/cytoscape-navigator@1.3.3/cytoscape-navigator.js"></script>
|
|
1158
|
+
|
|
832
1159
|
<!-- Modular JS (v2.5 — split from monolith app.js) -->
|
|
833
1160
|
<script src="static/js/core.js"></script>
|
|
834
|
-
|
|
1161
|
+
|
|
1162
|
+
<!-- Graph Visualization Scripts (v2.6.5 - Modular) -->
|
|
1163
|
+
<script src="static/js/graph-core.js"></script>
|
|
1164
|
+
<script src="static/js/graph-filters.js"></script>
|
|
1165
|
+
<script src="static/js/graph-ui.js"></script>
|
|
1166
|
+
<script src="static/js/graph-interactions.js"></script>
|
|
1167
|
+
|
|
835
1168
|
<script src="static/js/memories.js"></script>
|
|
836
1169
|
<script src="static/js/search.js"></script>
|
|
837
1170
|
<script src="static/js/modal.js"></script>
|
package/ui/js/clusters.js
CHANGED
|
@@ -30,7 +30,9 @@ function renderClusters(clusters) {
|
|
|
30
30
|
|
|
31
31
|
var card = document.createElement('div');
|
|
32
32
|
card.className = 'card cluster-card';
|
|
33
|
-
card.style.
|
|
33
|
+
card.style.cssText = 'border-color:' + color + '; cursor:pointer;';
|
|
34
|
+
card.setAttribute('data-cluster-id', cluster.cluster_id);
|
|
35
|
+
card.title = 'Click to filter graph to this cluster';
|
|
34
36
|
|
|
35
37
|
var body = document.createElement('div');
|
|
36
38
|
body.className = 'card-body';
|
|
@@ -76,5 +78,92 @@ function renderClusters(clusters) {
|
|
|
76
78
|
|
|
77
79
|
card.appendChild(body);
|
|
78
80
|
container.appendChild(card);
|
|
81
|
+
|
|
82
|
+
// v2.6.5: Click card → filter graph to this cluster
|
|
83
|
+
card.addEventListener('click', function(e) {
|
|
84
|
+
// Don't trigger if clicking on badge or entity
|
|
85
|
+
if (e.target.classList.contains('entity-badge') || e.target.classList.contains('badge')) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const clusterId = parseInt(card.getAttribute('data-cluster-id'));
|
|
90
|
+
filterGraphToCluster(clusterId);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// v2.6.5: Click entity badge → filter graph by entity
|
|
94
|
+
if (cluster.top_entities && cluster.top_entities.length > 0) {
|
|
95
|
+
const entityBadges = body.querySelectorAll('.entity-badge');
|
|
96
|
+
entityBadges.forEach(function(badge) {
|
|
97
|
+
badge.style.cursor = 'pointer';
|
|
98
|
+
badge.title = 'Click to show memories with this entity';
|
|
99
|
+
badge.addEventListener('click', function(e) {
|
|
100
|
+
e.stopPropagation(); // Don't trigger card click
|
|
101
|
+
const entityText = badge.textContent.split(' (')[0]; // Extract entity name
|
|
102
|
+
filterGraphByEntity(entityText);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// v2.6.5: Click "X memories" badge → show list in sidebar (future feature)
|
|
108
|
+
countBadge.style.cursor = 'pointer';
|
|
109
|
+
countBadge.title = 'Click to view memories in this cluster';
|
|
110
|
+
countBadge.addEventListener('click', function(e) {
|
|
111
|
+
e.stopPropagation(); // Don't trigger card click
|
|
112
|
+
showClusterMemories(cluster.cluster_id);
|
|
113
|
+
});
|
|
79
114
|
});
|
|
80
115
|
}
|
|
116
|
+
|
|
117
|
+
// v2.6.5: Filter graph to a specific cluster
|
|
118
|
+
function filterGraphToCluster(clusterId) {
|
|
119
|
+
// Switch to Graph tab
|
|
120
|
+
const graphTab = document.querySelector('a[href="#graph"]');
|
|
121
|
+
if (graphTab) {
|
|
122
|
+
graphTab.click();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Apply filter after a delay (for tab to load)
|
|
126
|
+
setTimeout(function() {
|
|
127
|
+
if (typeof filterState !== 'undefined' && typeof filterByCluster === 'function' && typeof renderGraph === 'function') {
|
|
128
|
+
filterState.cluster_id = clusterId;
|
|
129
|
+
const filtered = filterByCluster(originalGraphData, clusterId);
|
|
130
|
+
renderGraph(filtered);
|
|
131
|
+
|
|
132
|
+
// Update URL
|
|
133
|
+
const url = new URL(window.location);
|
|
134
|
+
url.searchParams.set('cluster_id', clusterId);
|
|
135
|
+
window.history.replaceState({}, '', url);
|
|
136
|
+
}
|
|
137
|
+
}, 300);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// v2.6.5: Filter graph by entity
|
|
141
|
+
function filterGraphByEntity(entity) {
|
|
142
|
+
// Switch to Graph tab
|
|
143
|
+
const graphTab = document.querySelector('a[href="#graph"]');
|
|
144
|
+
if (graphTab) {
|
|
145
|
+
graphTab.click();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Apply filter after a delay
|
|
149
|
+
setTimeout(function() {
|
|
150
|
+
if (typeof filterState !== 'undefined' && typeof filterByEntity === 'function' && typeof renderGraph === 'function') {
|
|
151
|
+
filterState.entity = entity;
|
|
152
|
+
const filtered = filterByEntity(originalGraphData, entity);
|
|
153
|
+
renderGraph(filtered);
|
|
154
|
+
}
|
|
155
|
+
}, 300);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// v2.6.5: Show memories in a cluster (future: sidebar list)
|
|
159
|
+
function showClusterMemories(clusterId) {
|
|
160
|
+
// For now, just filter Memories tab
|
|
161
|
+
const memoriesTab = document.querySelector('a[href="#memories"]');
|
|
162
|
+
if (memoriesTab) {
|
|
163
|
+
memoriesTab.click();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// TODO: Implement sidebar memory list view
|
|
167
|
+
console.log('Show memories for cluster', clusterId);
|
|
168
|
+
showToast('Filtering memories for cluster ' + clusterId);
|
|
169
|
+
}
|