@aman_asmuei/amem 0.22.1 → 0.24.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/README.md +251 -123
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/dashboard.js +436 -129
- package/dist/dashboard.js.map +1 -1
- package/dist/hooks.js +20 -18
- package/dist/hooks.js.map +1 -1
- package/dist/index.js +29 -10
- package/dist/index.js.map +1 -1
- package/dist/tools/admin.js +0 -2
- package/dist/tools/admin.js.map +1 -1
- package/dist/tools/log.js +1 -1
- package/dist/tools/log.js.map +1 -1
- package/package.json +3 -2
package/dist/dashboard.js
CHANGED
|
@@ -74,11 +74,39 @@ a{color:var(--decision);text-decoration:none}
|
|
|
74
74
|
.mem-card .mem-meta .tag{background:var(--card);border:1px solid var(--border);border-radius:4px;padding:1px 6px}
|
|
75
75
|
|
|
76
76
|
/* knowledge graph */
|
|
77
|
-
|
|
77
|
+
.graph-container{position:relative;overflow:hidden;border-radius:6px;background:var(--bg);height:600px}
|
|
78
|
+
#graph-svg{width:100%;height:100%;cursor:grab}
|
|
78
79
|
#graph-svg:active{cursor:grabbing}
|
|
79
|
-
#graph-svg circle{cursor:pointer;transition:r 0.15s}
|
|
80
|
-
#graph-svg circle:hover{
|
|
80
|
+
#graph-svg .node-circle{cursor:pointer;transition:opacity 0.3s,r 0.15s}
|
|
81
|
+
#graph-svg .node-circle:hover{filter:brightness(1.3)}
|
|
82
|
+
#graph-svg .edge-line{transition:opacity 0.3s}
|
|
83
|
+
#graph-svg .node-label{transition:opacity 0.3s;pointer-events:none}
|
|
84
|
+
#graph-svg .edge-label{transition:opacity 0.3s;pointer-events:none}
|
|
85
|
+
.graph-dimmed{opacity:0.12!important}
|
|
81
86
|
.graph-tooltip{position:absolute;background:var(--card);border:1px solid var(--border);border-radius:6px;padding:10px 14px;font-size:0.8rem;max-width:300px;pointer-events:none;z-index:10;box-shadow:0 4px 12px rgba(0,0,0,0.4)}
|
|
87
|
+
.graph-controls{display:flex;gap:8px;margin-bottom:10px;flex-wrap:wrap;align-items:center}
|
|
88
|
+
.graph-controls input,.graph-controls select{background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:6px;padding:6px 10px;font-size:0.8rem;outline:none}
|
|
89
|
+
.graph-controls input:focus,.graph-controls select:focus{border-color:var(--decision)}
|
|
90
|
+
.graph-controls input{flex:1;min-width:140px}
|
|
91
|
+
.graph-controls button{background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:6px;padding:6px 12px;font-size:0.8rem;cursor:pointer}
|
|
92
|
+
.graph-controls button:hover{border-color:var(--decision);color:var(--decision)}
|
|
93
|
+
.graph-controls button.active{background:var(--decision);color:#fff;border-color:var(--decision)}
|
|
94
|
+
.graph-legend{display:flex;gap:14px;flex-wrap:wrap;padding:8px 0}
|
|
95
|
+
.graph-legend-item{display:flex;align-items:center;gap:5px;font-size:0.75rem;color:var(--muted);cursor:pointer}
|
|
96
|
+
.graph-legend-item:hover{color:var(--text)}
|
|
97
|
+
.graph-legend-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}
|
|
98
|
+
.graph-stats{display:flex;gap:14px;font-size:0.75rem;color:var(--muted);padding:4px 0}
|
|
99
|
+
.graph-stats b{color:var(--text)}
|
|
100
|
+
.graph-detail{position:absolute;top:10px;right:10px;width:280px;background:var(--card);border:1px solid var(--border);border-radius:var(--radius);padding:16px;z-index:20;box-shadow:0 8px 24px rgba(0,0,0,0.5);max-height:calc(100% - 20px);overflow-y:auto;display:none}
|
|
101
|
+
.graph-detail h3{font-size:0.9rem;margin-bottom:8px;display:flex;align-items:center;gap:8px}
|
|
102
|
+
.graph-detail .close{margin-left:auto;cursor:pointer;color:var(--muted);font-size:1rem;line-height:1}
|
|
103
|
+
.graph-detail .close:hover{color:var(--text)}
|
|
104
|
+
.graph-detail .detail-content{font-size:0.85rem;line-height:1.6;margin-bottom:10px;word-break:break-word}
|
|
105
|
+
.graph-detail .detail-meta{font-size:0.75rem;color:var(--muted);display:flex;flex-direction:column;gap:4px}
|
|
106
|
+
.graph-detail .detail-relations{margin-top:10px;border-top:1px solid var(--border);padding-top:10px}
|
|
107
|
+
.graph-detail .relation-item{display:flex;align-items:center;gap:6px;font-size:0.8rem;padding:4px 0;cursor:pointer;color:var(--text)}
|
|
108
|
+
.graph-detail .relation-item:hover{color:var(--decision)}
|
|
109
|
+
.graph-detail .relation-arrow{color:var(--muted);font-size:0.7rem}
|
|
82
110
|
|
|
83
111
|
/* memory actions */
|
|
84
112
|
.mem-actions{display:flex;gap:6px;margin-top:6px}
|
|
@@ -193,10 +221,29 @@ a{color:var(--decision);text-decoration:none}
|
|
|
193
221
|
</div>
|
|
194
222
|
|
|
195
223
|
<!-- Knowledge graph -->
|
|
196
|
-
<div class="card" id="graph-card">
|
|
224
|
+
<div class="card full" id="graph-card">
|
|
197
225
|
<h2>Knowledge Graph</h2>
|
|
198
|
-
<div
|
|
199
|
-
|
|
226
|
+
<div class="graph-controls">
|
|
227
|
+
<input type="text" id="graph-search" placeholder="Search nodes..."/>
|
|
228
|
+
<select id="graph-type-filter">
|
|
229
|
+
<option value="">All types</option>
|
|
230
|
+
<option value="correction">correction</option>
|
|
231
|
+
<option value="decision">decision</option>
|
|
232
|
+
<option value="pattern">pattern</option>
|
|
233
|
+
<option value="preference">preference</option>
|
|
234
|
+
<option value="topology">topology</option>
|
|
235
|
+
<option value="fact">fact</option>
|
|
236
|
+
</select>
|
|
237
|
+
<button id="graph-reset" title="Reset view">Reset</button>
|
|
238
|
+
<button id="graph-fit" title="Fit all nodes">Fit All</button>
|
|
239
|
+
</div>
|
|
240
|
+
<div class="graph-stats" id="graph-stats"></div>
|
|
241
|
+
<div class="graph-legend" id="graph-legend"></div>
|
|
242
|
+
<div class="graph-container" id="graph-container">
|
|
243
|
+
<div id="graph-tooltip" class="graph-tooltip" style="display:none"></div>
|
|
244
|
+
<svg id="graph-svg"></svg>
|
|
245
|
+
<div class="graph-detail" id="graph-detail"></div>
|
|
246
|
+
</div>
|
|
200
247
|
</div>
|
|
201
248
|
|
|
202
249
|
<!-- Reminders -->
|
|
@@ -342,80 +389,412 @@ a{color:var(--decision);text-decoration:none}
|
|
|
342
389
|
renderMemories(list);
|
|
343
390
|
}
|
|
344
391
|
|
|
345
|
-
// -- Knowledge Graph (
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
392
|
+
// -- Knowledge Graph (enhanced force-directed with zoom/pan/focus) --
|
|
393
|
+
var graphNodes=[], graphEdges=[], graphNodeMap={}, graphAllNodes=[], graphAllEdges=[];
|
|
394
|
+
var graphZoom={x:0,y:0,scale:1};
|
|
395
|
+
var graphFocusId=null;
|
|
396
|
+
var graphDragNode=null, graphDragBg=false, graphDragStart={x:0,y:0}, graphPanStart={x:0,y:0};
|
|
397
|
+
|
|
398
|
+
function computeNodeDegrees(){
|
|
399
|
+
for(var i=0;i<graphAllNodes.length;i++) graphAllNodes[i].degree=0;
|
|
400
|
+
for(var i=0;i<graphAllEdges.length;i++){
|
|
401
|
+
var a=graphNodeMap[graphAllEdges[i].from];
|
|
402
|
+
var b=graphNodeMap[graphAllEdges[i].to];
|
|
403
|
+
if(a) a.degree=(a.degree||0)+1;
|
|
404
|
+
if(b) b.degree=(b.degree||0)+1;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function nodeRadius(n){return Math.max(6,Math.min(20,6+(n.degree||0)*2))}
|
|
409
|
+
|
|
410
|
+
function getNeighborIds(nodeId){
|
|
411
|
+
var ids={};ids[nodeId]=true;
|
|
412
|
+
for(var i=0;i<graphAllEdges.length;i++){
|
|
413
|
+
if(graphAllEdges[i].from===nodeId) ids[graphAllEdges[i].to]=true;
|
|
414
|
+
if(graphAllEdges[i].to===nodeId) ids[graphAllEdges[i].from]=true;
|
|
415
|
+
}
|
|
416
|
+
return ids;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function getNodeEdges(nodeId){
|
|
420
|
+
var result=[];
|
|
421
|
+
for(var i=0;i<graphAllEdges.length;i++){
|
|
422
|
+
if(graphAllEdges[i].from===nodeId||graphAllEdges[i].to===nodeId) result.push(graphAllEdges[i]);
|
|
423
|
+
}
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
351
426
|
|
|
352
|
-
|
|
427
|
+
function forceLayout(nodes,edges,W,H){
|
|
353
428
|
var i,j;
|
|
429
|
+
var nodeMap={};
|
|
354
430
|
for(i=0;i<nodes.length;i++){
|
|
355
|
-
|
|
356
|
-
nodes[i].
|
|
431
|
+
// Use stable seeded positions based on node id hash
|
|
432
|
+
var h=0;for(var c=0;c<nodes[i].id.length;c++){h=((h<<5)-h)+nodes[i].id.charCodeAt(c);h|=0}
|
|
433
|
+
nodes[i].x=W*0.15+Math.abs(h%1000)/1000*W*0.7;
|
|
434
|
+
nodes[i].y=H*0.15+Math.abs((h*31)%1000)/1000*H*0.7;
|
|
357
435
|
nodes[i].vx=0;nodes[i].vy=0;
|
|
436
|
+
nodeMap[nodes[i].id]=nodes[i];
|
|
358
437
|
}
|
|
359
|
-
var nodeMap={};
|
|
360
|
-
for(i=0;i<nodes.length;i++) nodeMap[nodes[i].id]=nodes[i];
|
|
361
438
|
|
|
362
|
-
|
|
363
|
-
var
|
|
364
|
-
|
|
365
|
-
|
|
439
|
+
var REPULSION=5000,SPRING=0.04,DAMPING=0.82,DT=1;
|
|
440
|
+
var ITERS=nodes.length>200?60:nodes.length>50?100:150;
|
|
441
|
+
|
|
442
|
+
for(var iter=0;iter<ITERS;iter++){
|
|
443
|
+
// Barnes-Hut approximation for large graphs: skip distant pairs
|
|
366
444
|
for(i=0;i<nodes.length;i++){
|
|
367
445
|
for(j=i+1;j<nodes.length;j++){
|
|
368
|
-
var dx=nodes[j].x-nodes[i].x,
|
|
446
|
+
var dx=nodes[j].x-nodes[i].x,dy=nodes[j].y-nodes[i].y;
|
|
369
447
|
var dist2=dx*dx+dy*dy+1;
|
|
448
|
+
if(dist2>400000) continue; // skip very distant pairs
|
|
370
449
|
var f=REPULSION/dist2;
|
|
371
450
|
var d=Math.sqrt(dist2);
|
|
372
|
-
var fx=f*dx/d,
|
|
451
|
+
var fx=f*dx/d,fy=f*dy/d;
|
|
373
452
|
nodes[i].vx-=fx;nodes[i].vy-=fy;
|
|
374
453
|
nodes[j].vx+=fx;nodes[j].vy+=fy;
|
|
375
454
|
}
|
|
376
455
|
}
|
|
377
|
-
// attraction along edges
|
|
378
456
|
for(i=0;i<edges.length;i++){
|
|
379
457
|
var a=nodeMap[edges[i].from],b=nodeMap[edges[i].to];
|
|
380
458
|
if(!a||!b) continue;
|
|
381
459
|
var edx=b.x-a.x,edy=b.y-a.y;
|
|
382
|
-
var
|
|
460
|
+
var strength=edges[i].strength||0.8;
|
|
461
|
+
var efx=SPRING*strength*edx,efy=SPRING*strength*edy;
|
|
383
462
|
a.vx+=efx;a.vy+=efy;b.vx-=efx;b.vy-=efy;
|
|
384
463
|
}
|
|
385
|
-
// center gravity
|
|
386
|
-
for(i=0;i<nodes.length;i++){
|
|
387
|
-
nodes[i].vx+=(W/2-nodes[i].x)*0.001;
|
|
388
|
-
nodes[i].vy+=(H/2-nodes[i].y)*0.001;
|
|
389
|
-
}
|
|
390
|
-
// integrate
|
|
391
464
|
for(i=0;i<nodes.length;i++){
|
|
465
|
+
nodes[i].vx+=(W/2-nodes[i].x)*0.002;
|
|
466
|
+
nodes[i].vy+=(H/2-nodes[i].y)*0.002;
|
|
392
467
|
nodes[i].vx*=DAMPING;nodes[i].vy*=DAMPING;
|
|
393
468
|
nodes[i].x+=nodes[i].vx*DT;nodes[i].y+=nodes[i].vy*DT;
|
|
394
|
-
nodes[i].x=Math.max(
|
|
395
|
-
nodes[i].y=Math.max(
|
|
469
|
+
nodes[i].x=Math.max(40,Math.min(W-40,nodes[i].x));
|
|
470
|
+
nodes[i].y=Math.max(40,Math.min(H-40,nodes[i].y));
|
|
396
471
|
}
|
|
397
472
|
}
|
|
473
|
+
return nodeMap;
|
|
474
|
+
}
|
|
398
475
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
476
|
+
function renderGraphSvg(){
|
|
477
|
+
var svg=$('graph-svg');
|
|
478
|
+
if(!svg) return;
|
|
479
|
+
var focusNeighbors=graphFocusId?getNeighborIds(graphFocusId):null;
|
|
403
480
|
|
|
404
|
-
|
|
405
|
-
var html='';
|
|
406
|
-
|
|
407
|
-
|
|
481
|
+
var defs='<defs><marker id="arrow" viewBox="0 0 10 6" refX="10" refY="3" markerWidth="8" markerHeight="6" orient="auto-start-reverse"><path d="M0,0 L10,3 L0,6z" fill="#58a6ff" fill-opacity="0.6"/></marker></defs>';
|
|
482
|
+
var html=defs+'<g id="graph-pan" transform="translate('+graphZoom.x+','+graphZoom.y+') scale('+graphZoom.scale+')">';
|
|
483
|
+
|
|
484
|
+
// Edges
|
|
485
|
+
for(var i=0;i<graphEdges.length;i++){
|
|
486
|
+
var e=graphEdges[i];
|
|
487
|
+
var ea=graphNodeMap[e.from],eb=graphNodeMap[e.to];
|
|
408
488
|
if(!ea||!eb) continue;
|
|
409
|
-
var
|
|
410
|
-
|
|
411
|
-
|
|
489
|
+
var dim=focusNeighbors&&(!focusNeighbors[e.from]||!focusNeighbors[e.to]);
|
|
490
|
+
var cls='edge-line'+(dim?' graph-dimmed':'');
|
|
491
|
+
var sw=1.5+(e.strength||0.8)*2;
|
|
492
|
+
var opacity=dim?0.1:0.5;
|
|
493
|
+
// Shorten line to avoid overlapping node circles
|
|
494
|
+
var edx=eb.x-ea.x,edy=eb.y-ea.y;
|
|
495
|
+
var elen=Math.sqrt(edx*edx+edy*edy)||1;
|
|
496
|
+
var rA=nodeRadius(ea),rB=nodeRadius(eb);
|
|
497
|
+
var x1=ea.x+edx/elen*(rA+2),y1=ea.y+edy/elen*(rA+2);
|
|
498
|
+
var x2=eb.x-edx/elen*(rB+2),y2=eb.y-edy/elen*(rB+2);
|
|
499
|
+
html+='<line class="'+cls+'" x1="'+x1+'" y1="'+y1+'" x2="'+x2+'" y2="'+y2+'" stroke="#58a6ff" stroke-opacity="'+opacity+'" stroke-width="'+sw+'" marker-end="url(#arrow)"/>';
|
|
500
|
+
if(e.type){
|
|
501
|
+
var mx=(ea.x+eb.x)/2,my=(ea.y+eb.y)/2;
|
|
502
|
+
html+='<text class="edge-label'+(dim?' graph-dimmed':'')+'" x="'+mx+'" y="'+(my-6)+'" fill="#c9d1d9" font-size="9" text-anchor="middle" font-weight="600" opacity="'+(dim?0.1:0.8)+'">'+esc(e.type)+'</text>';
|
|
503
|
+
}
|
|
412
504
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
505
|
+
|
|
506
|
+
// Nodes
|
|
507
|
+
for(var i=0;i<graphNodes.length;i++){
|
|
508
|
+
var n=graphNodes[i];
|
|
509
|
+
var color=TYPE_COLORS[n.type]||'#8b949e';
|
|
510
|
+
var r=nodeRadius(n);
|
|
511
|
+
var dim=focusNeighbors&&!focusNeighbors[n.id];
|
|
512
|
+
var cls='node-circle'+(dim?' graph-dimmed':'');
|
|
513
|
+
var focused=graphFocusId===n.id;
|
|
514
|
+
var sw=focused?3:2;
|
|
515
|
+
var stroke=focused?'#fff':'#0d1117';
|
|
516
|
+
html+='<circle class="'+cls+'" data-nid="'+esc(n.id)+'" cx="'+n.x+'" cy="'+n.y+'" r="'+r+'" fill="'+color+'" stroke="'+stroke+'" stroke-width="'+sw+'"/>';
|
|
517
|
+
var labelOpacity=dim?0.1:(r>8?1:0.7);
|
|
518
|
+
html+='<text class="node-label'+(dim?' graph-dimmed':'')+'" x="'+n.x+'" y="'+(n.y+r+14)+'" fill="'+color+'" font-size="'+(r>12?'11':'10')+'" text-anchor="middle" font-family="-apple-system,sans-serif" opacity="'+labelOpacity+'">'+esc(n.label.slice(0,28))+'</text>';
|
|
417
519
|
}
|
|
418
|
-
|
|
520
|
+
|
|
521
|
+
html+='</g>';
|
|
522
|
+
setHTML(svg,html);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function renderGraphLegend(){
|
|
526
|
+
var el=$('graph-legend');
|
|
527
|
+
if(!el) return;
|
|
528
|
+
var types=['correction','decision','pattern','preference','topology','fact'];
|
|
529
|
+
setHTML(el, types.map(function(t){
|
|
530
|
+
return '<div class="graph-legend-item" data-type="'+t+'"><div class="graph-legend-dot" style="background:'+(TYPE_COLORS[t]||'#8b949e')+'"></div>'+t+'</div>';
|
|
531
|
+
}).join(''));
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function renderGraphStats(){
|
|
535
|
+
var el=$('graph-stats');
|
|
536
|
+
if(!el) return;
|
|
537
|
+
var connected=0;
|
|
538
|
+
var connectedSet={};
|
|
539
|
+
for(var i=0;i<graphAllEdges.length;i++){
|
|
540
|
+
connectedSet[graphAllEdges[i].from]=true;
|
|
541
|
+
connectedSet[graphAllEdges[i].to]=true;
|
|
542
|
+
}
|
|
543
|
+
connected=Object.keys(connectedSet).length;
|
|
544
|
+
setHTML(el,'<b>'+graphNodes.length+'</b> nodes · <b>'+graphEdges.length+'</b> edges · <b>'+connected+'</b> connected');
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function showNodeDetail(nodeId){
|
|
548
|
+
var panel=$('graph-detail');
|
|
549
|
+
if(!panel) return;
|
|
550
|
+
var n=graphNodeMap[nodeId];
|
|
551
|
+
if(!n){panel.style.display='none';return}
|
|
552
|
+
|
|
553
|
+
var color=TYPE_COLORS[n.type]||'#8b949e';
|
|
554
|
+
var edges=getNodeEdges(nodeId);
|
|
555
|
+
var html='<h3><span class="type-badge" style="background:'+color+'">'+esc(n.type)+'</span><code class="mono" style="font-size:0.7rem;color:var(--muted)">'+esc(n.id.slice(0,8))+'</code><span class="close" id="detail-close">×</span></h3>';
|
|
556
|
+
html+='<div class="detail-content">'+esc(n.fullContent||n.label)+'</div>';
|
|
557
|
+
html+='<div class="detail-meta">';
|
|
558
|
+
html+='<span>Tier: '+(n.tier||'archival')+'</span>';
|
|
559
|
+
html+='<span>Connections: '+(n.degree||0)+'</span>';
|
|
560
|
+
html+='</div>';
|
|
561
|
+
|
|
562
|
+
if(edges.length){
|
|
563
|
+
html+='<div class="detail-relations"><div style="font-size:0.75rem;color:var(--muted);margin-bottom:6px;font-weight:600">RELATIONS</div>';
|
|
564
|
+
for(var i=0;i<edges.length;i++){
|
|
565
|
+
var e=edges[i];
|
|
566
|
+
var otherId=e.from===nodeId?e.to:e.from;
|
|
567
|
+
var other=graphNodeMap[otherId];
|
|
568
|
+
var otherLabel=other?(other.label.slice(0,30)):otherId.slice(0,8);
|
|
569
|
+
var dir=e.from===nodeId?'→':'←';
|
|
570
|
+
html+='<div class="relation-item" data-nid="'+esc(otherId)+'">';
|
|
571
|
+
html+='<span class="relation-arrow">'+dir+'</span>';
|
|
572
|
+
html+='<span style="color:var(--muted);font-size:0.7rem">'+esc(e.type||'related')+'</span> ';
|
|
573
|
+
html+=esc(otherLabel);
|
|
574
|
+
html+='</div>';
|
|
575
|
+
}
|
|
576
|
+
html+='</div>';
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
setHTML(panel,html);
|
|
580
|
+
panel.style.display='block';
|
|
581
|
+
|
|
582
|
+
// Close button
|
|
583
|
+
var closeBtn=$('detail-close');
|
|
584
|
+
if(closeBtn) closeBtn.addEventListener('click',function(){
|
|
585
|
+
panel.style.display='none';
|
|
586
|
+
graphFocusId=null;
|
|
587
|
+
renderGraphSvg();
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
// Click relation to navigate
|
|
591
|
+
var items=panel.querySelectorAll('.relation-item[data-nid]');
|
|
592
|
+
for(var i=0;i<items.length;i++){
|
|
593
|
+
items[i].addEventListener('click',function(){
|
|
594
|
+
var nid=this.dataset.nid;
|
|
595
|
+
if(nid){
|
|
596
|
+
graphFocusId=nid;
|
|
597
|
+
renderGraphSvg();
|
|
598
|
+
showNodeDetail(nid);
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function filterGraphNodes(){
|
|
605
|
+
var search=($('graph-search')||{}).value||'';
|
|
606
|
+
var typeFilter=($('graph-type-filter')||{}).value||'';
|
|
607
|
+
search=search.toLowerCase();
|
|
608
|
+
|
|
609
|
+
if(!search&&!typeFilter){
|
|
610
|
+
graphNodes=graphAllNodes;
|
|
611
|
+
graphEdges=graphAllEdges;
|
|
612
|
+
} else {
|
|
613
|
+
var visibleIds={};
|
|
614
|
+
graphNodes=graphAllNodes.filter(function(n){
|
|
615
|
+
var matchSearch=!search||n.label.toLowerCase().indexOf(search)!==-1||(n.fullContent||'').toLowerCase().indexOf(search)!==-1;
|
|
616
|
+
var matchType=!typeFilter||n.type===typeFilter;
|
|
617
|
+
if(matchSearch&&matchType){visibleIds[n.id]=true;return true}
|
|
618
|
+
return false;
|
|
619
|
+
});
|
|
620
|
+
graphEdges=graphAllEdges.filter(function(e){return visibleIds[e.from]&&visibleIds[e.to]});
|
|
621
|
+
}
|
|
622
|
+
renderGraphSvg();
|
|
623
|
+
renderGraphStats();
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function renderGraph(data){
|
|
627
|
+
var svg=$('graph-svg');
|
|
628
|
+
var container=$('graph-container');
|
|
629
|
+
if(!svg||!container) return;
|
|
630
|
+
var W=container.clientWidth||900,H=container.clientHeight||600;
|
|
631
|
+
svg.setAttribute('viewBox','0 0 '+W+' '+H);
|
|
632
|
+
|
|
633
|
+
graphAllNodes=data.nodes;graphAllEdges=data.edges;
|
|
634
|
+
graphNodes=data.nodes;graphEdges=data.edges;
|
|
635
|
+
graphZoom={x:0,y:0,scale:1};
|
|
636
|
+
graphFocusId=null;
|
|
637
|
+
|
|
638
|
+
if(!data.nodes.length){
|
|
639
|
+
setHTML(svg,'<text x="'+W/2+'" y="'+H/2+'" fill="#8b949e" text-anchor="middle" font-size="14">No graph data — use memory_relate to connect memories</text>');
|
|
640
|
+
renderGraphStats();
|
|
641
|
+
renderGraphLegend();
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
graphNodeMap=forceLayout(data.nodes,data.edges,W,H);
|
|
646
|
+
computeNodeDegrees();
|
|
647
|
+
renderGraphSvg();
|
|
648
|
+
renderGraphStats();
|
|
649
|
+
renderGraphLegend();
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function setupGraphInteraction(){
|
|
653
|
+
var svg=$('graph-svg');
|
|
654
|
+
var container=$('graph-container');
|
|
655
|
+
var tooltip=$('graph-tooltip');
|
|
656
|
+
if(!svg||!container) return;
|
|
657
|
+
|
|
658
|
+
// Zoom with mouse wheel
|
|
659
|
+
container.addEventListener('wheel',function(e){
|
|
660
|
+
e.preventDefault();
|
|
661
|
+
var rect=container.getBoundingClientRect();
|
|
662
|
+
var mx=e.clientX-rect.left;
|
|
663
|
+
var my=e.clientY-rect.top;
|
|
664
|
+
var delta=e.deltaY>0?0.9:1.1;
|
|
665
|
+
var newScale=Math.max(0.2,Math.min(5,graphZoom.scale*delta));
|
|
666
|
+
// Zoom toward mouse position
|
|
667
|
+
graphZoom.x=mx-(mx-graphZoom.x)*newScale/graphZoom.scale;
|
|
668
|
+
graphZoom.y=my-(my-graphZoom.y)*newScale/graphZoom.scale;
|
|
669
|
+
graphZoom.scale=newScale;
|
|
670
|
+
renderGraphSvg();
|
|
671
|
+
},{passive:false});
|
|
672
|
+
|
|
673
|
+
// Pan with mouse drag on background
|
|
674
|
+
svg.addEventListener('mousedown',function(e){
|
|
675
|
+
if(e.target.tagName==='circle'){
|
|
676
|
+
// Node drag
|
|
677
|
+
var nid=e.target.dataset.nid;
|
|
678
|
+
if(nid) graphDragNode=graphNodeMap[nid];
|
|
679
|
+
if(graphDragNode){
|
|
680
|
+
var rect=container.getBoundingClientRect();
|
|
681
|
+
graphDragStart.x=(e.clientX-rect.left-graphZoom.x)/graphZoom.scale-graphDragNode.x;
|
|
682
|
+
graphDragStart.y=(e.clientY-rect.top-graphZoom.y)/graphZoom.scale-graphDragNode.y;
|
|
683
|
+
}
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
// Background pan
|
|
687
|
+
graphDragBg=true;
|
|
688
|
+
graphPanStart.x=e.clientX-graphZoom.x;
|
|
689
|
+
graphPanStart.y=e.clientY-graphZoom.y;
|
|
690
|
+
svg.style.cursor='grabbing';
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
svg.addEventListener('mousemove',function(e){
|
|
694
|
+
if(graphDragNode){
|
|
695
|
+
var rect=container.getBoundingClientRect();
|
|
696
|
+
graphDragNode.x=(e.clientX-rect.left-graphZoom.x)/graphZoom.scale-graphDragStart.x;
|
|
697
|
+
graphDragNode.y=(e.clientY-rect.top-graphZoom.y)/graphZoom.scale-graphDragStart.y;
|
|
698
|
+
renderGraphSvg();
|
|
699
|
+
} else if(graphDragBg){
|
|
700
|
+
graphZoom.x=e.clientX-graphPanStart.x;
|
|
701
|
+
graphZoom.y=e.clientY-graphPanStart.y;
|
|
702
|
+
renderGraphSvg();
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
document.addEventListener('mouseup',function(){
|
|
707
|
+
graphDragNode=null;
|
|
708
|
+
graphDragBg=false;
|
|
709
|
+
svg.style.cursor='grab';
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
// Click node to focus
|
|
713
|
+
svg.addEventListener('click',function(e){
|
|
714
|
+
var circle=e.target;
|
|
715
|
+
if(circle.tagName!=='circle') {
|
|
716
|
+
// Click background to unfocus
|
|
717
|
+
if(graphFocusId){graphFocusId=null;renderGraphSvg();$('graph-detail').style.display='none'}
|
|
718
|
+
if(tooltip) tooltip.style.display='none';
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
var nid=circle.dataset.nid;
|
|
722
|
+
if(!nid) return;
|
|
723
|
+
|
|
724
|
+
graphFocusId=graphFocusId===nid?null:nid;
|
|
725
|
+
renderGraphSvg();
|
|
726
|
+
if(graphFocusId) showNodeDetail(nid);
|
|
727
|
+
else $('graph-detail').style.display='none';
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
// Hover tooltip
|
|
731
|
+
svg.addEventListener('mouseover',function(e){
|
|
732
|
+
if(e.target.tagName!=='circle'||!tooltip) return;
|
|
733
|
+
var nid=e.target.dataset.nid;
|
|
734
|
+
var n=graphNodeMap[nid];
|
|
735
|
+
if(!n) return;
|
|
736
|
+
var rect=container.getBoundingClientRect();
|
|
737
|
+
tooltip.style.display='block';
|
|
738
|
+
tooltip.style.left=(e.clientX-rect.left+15)+'px';
|
|
739
|
+
tooltip.style.top=(e.clientY-rect.top-10)+'px';
|
|
740
|
+
var color=TYPE_COLORS[n.type]||'#8b949e';
|
|
741
|
+
setHTML(tooltip,
|
|
742
|
+
'<div style="margin-bottom:4px"><span class="type-badge" style="background:'+color+'">'+esc(n.type)+'</span> <code class="mono">'+esc(n.id.slice(0,8))+'</code></div>'+
|
|
743
|
+
'<div style="font-size:0.85rem;margin-bottom:4px">'+esc((n.fullContent||n.label).slice(0,120))+'</div>'+
|
|
744
|
+
'<div style="font-size:0.75rem;color:var(--muted)">Connections: '+(n.degree||0)+' · Tier: '+(n.tier||'archival')+'</div>'
|
|
745
|
+
);
|
|
746
|
+
});
|
|
747
|
+
svg.addEventListener('mouseout',function(e){
|
|
748
|
+
if(e.target.tagName==='circle'&&tooltip) tooltip.style.display='none';
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
// Graph search/filter
|
|
752
|
+
var gsDebounce;
|
|
753
|
+
var gs=$('graph-search');
|
|
754
|
+
if(gs) gs.addEventListener('input',function(){clearTimeout(gsDebounce);gsDebounce=setTimeout(filterGraphNodes,300)});
|
|
755
|
+
var gf=$('graph-type-filter');
|
|
756
|
+
if(gf) gf.addEventListener('change',filterGraphNodes);
|
|
757
|
+
|
|
758
|
+
// Reset button
|
|
759
|
+
var resetBtn=$('graph-reset');
|
|
760
|
+
if(resetBtn) resetBtn.addEventListener('click',function(){
|
|
761
|
+
graphZoom={x:0,y:0,scale:1};
|
|
762
|
+
graphFocusId=null;
|
|
763
|
+
if(gs) gs.value='';
|
|
764
|
+
if(gf) gf.value='';
|
|
765
|
+
graphNodes=graphAllNodes;graphEdges=graphAllEdges;
|
|
766
|
+
$('graph-detail').style.display='none';
|
|
767
|
+
renderGraphSvg();renderGraphStats();
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
// Fit all button
|
|
771
|
+
var fitBtn=$('graph-fit');
|
|
772
|
+
if(fitBtn) fitBtn.addEventListener('click',function(){
|
|
773
|
+
if(!graphNodes.length) return;
|
|
774
|
+
var minX=Infinity,maxX=-Infinity,minY=Infinity,maxY=-Infinity;
|
|
775
|
+
for(var i=0;i<graphNodes.length;i++){
|
|
776
|
+
minX=Math.min(minX,graphNodes[i].x);maxX=Math.max(maxX,graphNodes[i].x);
|
|
777
|
+
minY=Math.min(minY,graphNodes[i].y);maxY=Math.max(maxY,graphNodes[i].y);
|
|
778
|
+
}
|
|
779
|
+
var container=$('graph-container');
|
|
780
|
+
var cw=container.clientWidth,ch=container.clientHeight;
|
|
781
|
+
var gw=maxX-minX+80,gh=maxY-minY+80;
|
|
782
|
+
var scale=Math.min(cw/gw,ch/gh,2);
|
|
783
|
+
graphZoom.scale=scale;
|
|
784
|
+
graphZoom.x=cw/2-((minX+maxX)/2)*scale;
|
|
785
|
+
graphZoom.y=ch/2-((minY+maxY)/2)*scale;
|
|
786
|
+
renderGraphSvg();
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
// Legend click to filter
|
|
790
|
+
var legend=$('graph-legend');
|
|
791
|
+
if(legend) legend.addEventListener('click',function(e){
|
|
792
|
+
var item=e.target.closest('.graph-legend-item');
|
|
793
|
+
if(!item) return;
|
|
794
|
+
var type=item.dataset.type;
|
|
795
|
+
var gf=$('graph-type-filter');
|
|
796
|
+
if(gf){gf.value=gf.value===type?'':type;filterGraphNodes()}
|
|
797
|
+
});
|
|
419
798
|
}
|
|
420
799
|
|
|
421
800
|
// -- Reminders --
|
|
@@ -459,22 +838,22 @@ a{color:var(--decision);text-decoration:none}
|
|
|
459
838
|
renderStats(s);
|
|
460
839
|
renderTypeBars(s.byType);
|
|
461
840
|
renderConfBars(s.confidence);
|
|
462
|
-
}).catch(function(){});
|
|
841
|
+
}).catch(function(e){ console.error('[amem] Dashboard fetch error:', e); });
|
|
463
842
|
|
|
464
843
|
fetchJSON('/api/memories?limit=200').then(function(m){
|
|
465
844
|
allMemories=m;
|
|
466
845
|
filterMemories();
|
|
467
846
|
renderTimeline(allMemories);
|
|
468
|
-
}).catch(function(){});
|
|
847
|
+
}).catch(function(e){ console.error('[amem] Dashboard fetch error:', e); });
|
|
469
848
|
|
|
470
|
-
fetchJSON('/api/graph').then(renderGraph).catch(function(){});
|
|
471
|
-
fetchJSON('/api/reminders').then(renderReminders).catch(function(){});
|
|
472
|
-
fetchJSON('/api/log?limit=30').then(renderLog).catch(function(){});
|
|
473
|
-
fetchJSON('/api/summaries?limit=10').then(renderSummaries).catch(function(){});
|
|
849
|
+
fetchJSON('/api/graph').then(renderGraph).catch(function(e){ console.error('[amem] Dashboard fetch error:', e); });
|
|
850
|
+
fetchJSON('/api/reminders').then(renderReminders).catch(function(e){ console.error('[amem] Dashboard fetch error:', e); });
|
|
851
|
+
fetchJSON('/api/log?limit=30').then(renderLog).catch(function(e){ console.error('[amem] Dashboard fetch error:', e); });
|
|
852
|
+
fetchJSON('/api/summaries?limit=10').then(renderSummaries).catch(function(e){ console.error('[amem] Dashboard fetch error:', e); });
|
|
474
853
|
fetchJSON('/api/copilot-preview').then(function(d){
|
|
475
854
|
var el=$('copilot-preview');
|
|
476
855
|
if(el) el.textContent=d.markdown||'No memories to export.';
|
|
477
|
-
}).catch(function(){});
|
|
856
|
+
}).catch(function(e){ console.error('[amem] Dashboard fetch error:', e); });
|
|
478
857
|
}
|
|
479
858
|
|
|
480
859
|
window.copyCopilotPreview=function(){
|
|
@@ -597,80 +976,8 @@ a{color:var(--decision);text-decoration:none}
|
|
|
597
976
|
});
|
|
598
977
|
};
|
|
599
978
|
|
|
600
|
-
// -- Interactive Graph (drag, click-to-inspect) --
|
|
601
|
-
var dragNode=null, dragOffset={x:0,y:0}, graphNodes=[], graphNodeMap={}, graphEdges=[];
|
|
602
|
-
|
|
603
|
-
function makeGraphInteractive(){
|
|
604
|
-
var svg=$('graph-svg');
|
|
605
|
-
var tooltip=$('graph-tooltip');
|
|
606
|
-
if(!svg||!tooltip) return;
|
|
607
|
-
|
|
608
|
-
svg.addEventListener('mousedown',function(e){
|
|
609
|
-
var circle=e.target;
|
|
610
|
-
if(circle.tagName!=='circle') return;
|
|
611
|
-
var nid=circle.dataset.nid;
|
|
612
|
-
if(!nid) return;
|
|
613
|
-
dragNode=graphNodeMap[nid];
|
|
614
|
-
if(!dragNode) return;
|
|
615
|
-
var rect=svg.getBoundingClientRect();
|
|
616
|
-
dragOffset.x=e.clientX-rect.left-dragNode.x;
|
|
617
|
-
dragOffset.y=e.clientY-rect.top-dragNode.y;
|
|
618
|
-
e.preventDefault();
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
svg.addEventListener('mousemove',function(e){
|
|
622
|
-
if(!dragNode) return;
|
|
623
|
-
var rect=svg.getBoundingClientRect();
|
|
624
|
-
dragNode.x=e.clientX-rect.left-dragOffset.x;
|
|
625
|
-
dragNode.y=e.clientY-rect.top-dragOffset.y;
|
|
626
|
-
redrawGraph();
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
document.addEventListener('mouseup',function(){dragNode=null});
|
|
630
|
-
|
|
631
|
-
svg.addEventListener('click',function(e){
|
|
632
|
-
var circle=e.target;
|
|
633
|
-
if(circle.tagName!=='circle') return;
|
|
634
|
-
var nid=circle.dataset.nid;
|
|
635
|
-
if(!nid) return;
|
|
636
|
-
var n=graphNodeMap[nid];
|
|
637
|
-
if(!n) return;
|
|
638
|
-
var rect=svg.getBoundingClientRect();
|
|
639
|
-
tooltip.style.display='block';
|
|
640
|
-
tooltip.style.left=(e.clientX-rect.left+15)+'px';
|
|
641
|
-
tooltip.style.top=(e.clientY-rect.top-10)+'px';
|
|
642
|
-
setHTML(tooltip,
|
|
643
|
-
'<div style="margin-bottom:4px"><span class="type-badge" style="background:'+(TYPE_COLORS[n.type]||'#8b949e')+'">'+esc(n.type)+'</span> <code class="mono">'+esc(n.id.slice(0,8))+'</code></div>'+
|
|
644
|
-
'<div style="font-size:0.85rem;margin-bottom:6px">'+esc(n.fullContent||n.label)+'</div>'+
|
|
645
|
-
'<div style="font-size:0.75rem;color:var(--muted)">Tier: '+(n.tier||'archival')+'</div>'
|
|
646
|
-
);
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
document.addEventListener('click',function(e){
|
|
650
|
-
if(e.target.tagName!=='circle'&&!tooltip.contains(e.target)) tooltip.style.display='none';
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function redrawGraph(){
|
|
655
|
-
var svg=$('graph-svg');
|
|
656
|
-
var html='';
|
|
657
|
-
for(var i=0;i<graphEdges.length;i++){
|
|
658
|
-
var ea=graphNodeMap[graphEdges[i].from],eb=graphNodeMap[graphEdges[i].to];
|
|
659
|
-
if(!ea||!eb) continue;
|
|
660
|
-
var mx=(ea.x+eb.x)/2,my=(ea.y+eb.y)/2;
|
|
661
|
-
html+='<line x1="'+ea.x+'" y1="'+ea.y+'" x2="'+eb.x+'" y2="'+eb.y+'" stroke="#30363d" stroke-width="'+(1+graphEdges[i].strength*2)+'"/>';
|
|
662
|
-
if(graphEdges[i].type) html+='<text x="'+mx+'" y="'+(my-4)+'" fill="#8b949e" font-size="9" text-anchor="middle">'+esc(graphEdges[i].type)+'</text>';
|
|
663
|
-
}
|
|
664
|
-
for(var i=0;i<graphNodes.length;i++){
|
|
665
|
-
var color=TYPE_COLORS[graphNodes[i].type]||'#8b949e';
|
|
666
|
-
html+='<circle data-nid="'+esc(graphNodes[i].id)+'" cx="'+graphNodes[i].x+'" cy="'+graphNodes[i].y+'" r="8" fill="'+color+'" stroke="#0d1117" stroke-width="2"/>';
|
|
667
|
-
html+='<text x="'+graphNodes[i].x+'" y="'+(graphNodes[i].y+20)+'" fill="'+color+'" font-size="10" text-anchor="middle" font-family="-apple-system,sans-serif">'+esc(graphNodes[i].label.slice(0,24))+'</text>';
|
|
668
|
-
}
|
|
669
|
-
setHTML(svg,html);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
979
|
// -- Init --
|
|
673
|
-
|
|
980
|
+
setupGraphInteraction();
|
|
674
981
|
loadAll();
|
|
675
982
|
setInterval(loadAll,30000);
|
|
676
983
|
})();
|