memorix 0.6.5 → 0.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 +27 -0
- package/README.md +22 -6
- package/dist/cli/index.js +544 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/app.js +91 -21
- package/dist/dashboard/static/style.css +13 -0
- package/dist/index.js +544 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
|
@@ -57,6 +57,10 @@ const i18n = {
|
|
|
57
57
|
concepts: 'Concepts',
|
|
58
58
|
files: 'Files Modified',
|
|
59
59
|
clickToExpand: 'Click to expand',
|
|
60
|
+
vectorSearch: 'Vector Search',
|
|
61
|
+
fulltextOnly: 'Fulltext Only',
|
|
62
|
+
enabled: 'Enabled',
|
|
63
|
+
typeDistribution: 'Type Distribution',
|
|
60
64
|
|
|
61
65
|
// Retention
|
|
62
66
|
memoryRetention: 'Memory Retention',
|
|
@@ -132,6 +136,10 @@ const i18n = {
|
|
|
132
136
|
concepts: '概念',
|
|
133
137
|
files: '相关文件',
|
|
134
138
|
clickToExpand: '点击展开',
|
|
139
|
+
vectorSearch: '向量搜索',
|
|
140
|
+
fulltextOnly: '仅全文搜索',
|
|
141
|
+
enabled: '已启用',
|
|
142
|
+
typeDistribution: '类型分布',
|
|
135
143
|
|
|
136
144
|
// Retention
|
|
137
145
|
memoryRetention: '记忆衰减',
|
|
@@ -398,6 +406,11 @@ async function loadDashboard() {
|
|
|
398
406
|
<div class="stat-label">${t('nextId')}</div>
|
|
399
407
|
<div class="stat-value">#${stats.nextId}</div>
|
|
400
408
|
</div>
|
|
409
|
+
<div class="stat-card" data-accent="${stats.embedding?.enabled ? 'cyan' : 'amber'}">
|
|
410
|
+
<div class="stat-label">${t('vectorSearch')}</div>
|
|
411
|
+
<div class="stat-value" style="font-size: 18px;">${stats.embedding?.enabled ? '✓ ' + t('enabled') : t('fulltextOnly')}</div>
|
|
412
|
+
${stats.embedding?.provider ? `<div style="font-size: 10px; color: var(--text-muted); margin-top: 4px; font-family: var(--font-mono);">${stats.embedding.provider} (${stats.embedding.dimensions}d)</div>` : ''}
|
|
413
|
+
</div>
|
|
401
414
|
</div>
|
|
402
415
|
|
|
403
416
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
|
|
@@ -406,16 +419,23 @@ async function loadDashboard() {
|
|
|
406
419
|
<span class="panel-title">${t('observationTypes')}</span>
|
|
407
420
|
</div>
|
|
408
421
|
<div class="panel-body">
|
|
409
|
-
${typeEntries.length > 0 ?
|
|
410
|
-
<div style="display: flex;
|
|
411
|
-
<
|
|
412
|
-
<
|
|
413
|
-
|
|
414
|
-
|
|
422
|
+
${typeEntries.length > 0 ? `
|
|
423
|
+
<div style="display: flex; gap: 20px; align-items: flex-start;">
|
|
424
|
+
<canvas id="type-pie-chart" width="140" height="140" style="flex-shrink: 0;"></canvas>
|
|
425
|
+
<div style="flex: 1;">
|
|
426
|
+
${typeEntries.map(([type, count]) => `
|
|
427
|
+
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
|
|
428
|
+
<span style="width: 18px; text-align: center; font-size: 13px;">${typeIcons[type] || '❓'}</span>
|
|
429
|
+
<span style="width: 110px; font-size: 11px; color: var(--text-secondary);">${type}</span>
|
|
430
|
+
<div style="flex: 1; height: 5px; background: rgba(128,128,128,0.1); border-radius: 3px; overflow: hidden;">
|
|
431
|
+
<div style="width: ${(count / maxTypeCount) * 100}%; height: 100%; background: var(--type-${type}, var(--accent-cyan)); border-radius: 3px;"></div>
|
|
432
|
+
</div>
|
|
433
|
+
<span style="font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); min-width: 22px; text-align: right;">${count}</span>
|
|
434
|
+
</div>
|
|
435
|
+
`).join('')}
|
|
415
436
|
</div>
|
|
416
|
-
<span style="font-family: var(--font-mono); font-size: 12px; color: var(--text-muted); min-width: 24px; text-align: right;">${count}</span>
|
|
417
437
|
</div>
|
|
418
|
-
`
|
|
438
|
+
` : `<p style="color: var(--text-muted); font-size: 13px;">${t('noObservationsYet')}</p>`}
|
|
419
439
|
</div>
|
|
420
440
|
</div>
|
|
421
441
|
|
|
@@ -442,6 +462,55 @@ async function loadDashboard() {
|
|
|
442
462
|
</div>
|
|
443
463
|
</div>
|
|
444
464
|
`;
|
|
465
|
+
|
|
466
|
+
// Render pie chart if data exists
|
|
467
|
+
if (typeEntries.length > 0) {
|
|
468
|
+
requestAnimationFrame(() => renderPieChart('type-pie-chart', typeEntries, typeIcons));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** Draw a mini donut chart on a canvas */
|
|
473
|
+
function renderPieChart(canvasId, entries, icons) {
|
|
474
|
+
const canvas = document.getElementById(canvasId);
|
|
475
|
+
if (!canvas) return;
|
|
476
|
+
const ctx = canvas.getContext('2d');
|
|
477
|
+
const dpr = window.devicePixelRatio || 1;
|
|
478
|
+
const size = 140;
|
|
479
|
+
canvas.width = size * dpr;
|
|
480
|
+
canvas.height = size * dpr;
|
|
481
|
+
canvas.style.width = size + 'px';
|
|
482
|
+
canvas.style.height = size + 'px';
|
|
483
|
+
ctx.scale(dpr, dpr);
|
|
484
|
+
|
|
485
|
+
const cx = size / 2, cy = size / 2, r = 54, inner = 34;
|
|
486
|
+
const total = entries.reduce((s, e) => s + e[1], 0);
|
|
487
|
+
const colors = [
|
|
488
|
+
'#06b6d4', '#a855f7', '#f59e0b', '#22c55e',
|
|
489
|
+
'#3b82f6', '#ef4444', '#ec4899', '#f97316', '#6366f1',
|
|
490
|
+
];
|
|
491
|
+
|
|
492
|
+
let angle = -Math.PI / 2;
|
|
493
|
+
entries.forEach(([type, count], i) => {
|
|
494
|
+
const slice = (count / total) * Math.PI * 2;
|
|
495
|
+
ctx.beginPath();
|
|
496
|
+
ctx.moveTo(cx + inner * Math.cos(angle), cy + inner * Math.sin(angle));
|
|
497
|
+
ctx.arc(cx, cy, r, angle, angle + slice);
|
|
498
|
+
ctx.arc(cx, cy, inner, angle + slice, angle, true);
|
|
499
|
+
ctx.closePath();
|
|
500
|
+
ctx.fillStyle = colors[i % colors.length];
|
|
501
|
+
ctx.fill();
|
|
502
|
+
angle += slice;
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Center text
|
|
506
|
+
ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--text-primary').trim() || '#fff';
|
|
507
|
+
ctx.font = 'bold 20px system-ui';
|
|
508
|
+
ctx.textAlign = 'center';
|
|
509
|
+
ctx.textBaseline = 'middle';
|
|
510
|
+
ctx.fillText(total, cx, cy - 6);
|
|
511
|
+
ctx.font = '10px system-ui';
|
|
512
|
+
ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--text-muted').trim() || '#888';
|
|
513
|
+
ctx.fillText('total', cx, cy + 10);
|
|
445
514
|
}
|
|
446
515
|
|
|
447
516
|
// ============================================================
|
|
@@ -720,13 +789,13 @@ function renderGraph(graph) {
|
|
|
720
789
|
legend.className = 'graph-legend';
|
|
721
790
|
legend.style.cssText = `
|
|
722
791
|
position: absolute; top: 12px; right: 12px; z-index: 10;
|
|
723
|
-
background: var(--
|
|
792
|
+
background: var(--bg-card);
|
|
724
793
|
backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
|
|
725
|
-
border: 1px solid var(--
|
|
794
|
+
border: 1px solid var(--border-medium);
|
|
726
795
|
border-radius: 12px; padding: 12px 14px; min-width: 140px;
|
|
727
796
|
font-family: 'Inter', sans-serif; font-size: 11px;
|
|
728
|
-
color: var(--text-secondary
|
|
729
|
-
box-shadow: 0 4px 24px rgba(0,0,0,0.
|
|
797
|
+
color: var(--text-secondary);
|
|
798
|
+
box-shadow: 0 4px 24px rgba(0,0,0,0.12);
|
|
730
799
|
transition: opacity 0.3s;
|
|
731
800
|
`;
|
|
732
801
|
|
|
@@ -736,7 +805,7 @@ function renderGraph(graph) {
|
|
|
736
805
|
|
|
737
806
|
// Title
|
|
738
807
|
const title = document.createElement('div');
|
|
739
|
-
title.style.cssText = 'font-weight: 600; font-size: 11px; margin-bottom: 8px; color: var(--text-primary
|
|
808
|
+
title.style.cssText = 'font-weight: 600; font-size: 11px; margin-bottom: 8px; color: var(--text-primary); letter-spacing: 0.5px; text-transform: uppercase;';
|
|
740
809
|
title.textContent = t('legend') || 'Legend';
|
|
741
810
|
legend.appendChild(title);
|
|
742
811
|
|
|
@@ -764,7 +833,7 @@ function renderGraph(graph) {
|
|
|
764
833
|
|
|
765
834
|
// Hover to highlight same-type nodes
|
|
766
835
|
row.addEventListener('mouseenter', () => {
|
|
767
|
-
row.style.background = '
|
|
836
|
+
row.style.background = 'var(--bg-card-hover)';
|
|
768
837
|
nodes.forEach(n => { n._dimmed = n.type !== type; });
|
|
769
838
|
draw();
|
|
770
839
|
});
|
|
@@ -779,7 +848,7 @@ function renderGraph(graph) {
|
|
|
779
848
|
|
|
780
849
|
// Stats footer
|
|
781
850
|
const stats = document.createElement('div');
|
|
782
|
-
stats.style.cssText = 'margin-top: 8px; padding-top: 8px; border-top: 1px solid
|
|
851
|
+
stats.style.cssText = 'margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border-subtle); font-size: 10px; opacity: 0.5;';
|
|
783
852
|
stats.textContent = `${nodes.length} nodes · ${edges.length} edges`;
|
|
784
853
|
legend.appendChild(stats);
|
|
785
854
|
|
|
@@ -1067,6 +1136,7 @@ function renderObsList() {
|
|
|
1067
1136
|
list.innerHTML = filtered.map(obs => {
|
|
1068
1137
|
const isLow = isLowQualityObs(obs.title || '');
|
|
1069
1138
|
const isSelected = selectedIds.has(obs.id);
|
|
1139
|
+
const hl = (text) => obsFilter ? escapeHtml(text).replace(new RegExp(`(${escapeHtml(obsFilter).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'), '<mark>$1</mark>') : escapeHtml(text);
|
|
1070
1140
|
return `
|
|
1071
1141
|
<div class="obs-card${isLow ? ' low-quality' : ''}" data-obs-id="${obs.id}">
|
|
1072
1142
|
<div class="obs-card-header" onclick="toggleObsDetail(${obs.id})">
|
|
@@ -1076,19 +1146,19 @@ function renderObsList() {
|
|
|
1076
1146
|
${typeIcons[obs.type] || '❓'} ${obs.type || 'unknown'}
|
|
1077
1147
|
</span>
|
|
1078
1148
|
${isLow ? '<span class="low-quality-badge">low quality</span>' : ''}
|
|
1079
|
-
<span class="obs-card-title">${
|
|
1149
|
+
<span class="obs-card-title">${hl(obs.title || t('untitled'))}</span>
|
|
1080
1150
|
<span class="obs-expand-icon">▼</span>
|
|
1081
1151
|
</div>
|
|
1082
1152
|
<div class="obs-card-meta">
|
|
1083
|
-
<span>📁 ${
|
|
1153
|
+
<span>📁 ${hl(obs.entityName || 'unknown')}</span>
|
|
1084
1154
|
${obs.createdAt ? `<span>🕐 ${formatTime(obs.createdAt)}</span>` : ''}
|
|
1085
1155
|
${obs.accessCount ? `<span>👁 ${obs.accessCount}</span>` : ''}
|
|
1086
1156
|
</div>
|
|
1087
1157
|
<div class="obs-detail" id="obs-detail-${obs.id}" style="display:none;">
|
|
1088
|
-
${obs.narrative ? `<div class="obs-detail-section"><label>${t('narrative')}</label><div class="obs-card-narrative">${
|
|
1089
|
-
${obs.facts && obs.facts.length > 0 ? `<div class="obs-detail-section"><label>${t('facts')}</label><div class="obs-card-facts">${obs.facts.map(f => `<span class="fact-tag">${
|
|
1090
|
-
${obs.concepts && obs.concepts.length > 0 ? `<div class="obs-detail-section"><label>${t('concepts')}</label><div class="obs-card-facts">${obs.concepts.map(c => `<span class="fact-tag concept-tag">${
|
|
1091
|
-
${obs.filesModified && obs.filesModified.length > 0 ? `<div class="obs-detail-section"><label>${t('files')}</label><div class="obs-card-facts">${obs.filesModified.map(f => `<span class="fact-tag file-tag">${
|
|
1158
|
+
${obs.narrative ? `<div class="obs-detail-section"><label>${t('narrative')}</label><div class="obs-card-narrative">${hl(obs.narrative)}</div></div>` : ''}
|
|
1159
|
+
${obs.facts && obs.facts.length > 0 ? `<div class="obs-detail-section"><label>${t('facts')}</label><div class="obs-card-facts">${obs.facts.map(f => `<span class="fact-tag">${hl(f)}</span>`).join('')}</div></div>` : ''}
|
|
1160
|
+
${obs.concepts && obs.concepts.length > 0 ? `<div class="obs-detail-section"><label>${t('concepts')}</label><div class="obs-card-facts">${obs.concepts.map(c => `<span class="fact-tag concept-tag">${hl(c)}</span>`).join('')}</div></div>` : ''}
|
|
1161
|
+
${obs.filesModified && obs.filesModified.length > 0 ? `<div class="obs-detail-section"><label>${t('files')}</label><div class="obs-card-facts">${obs.filesModified.map(f => `<span class="fact-tag file-tag">${hl(f)}</span>`).join('')}</div></div>` : ''}
|
|
1092
1162
|
<div class="obs-detail-actions">
|
|
1093
1163
|
<button class="delete-btn" onclick="deleteObs(${obs.id}, event)">
|
|
1094
1164
|
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 4h12M5 4V3a1 1 0 011-1h4a1 1 0 011 1v1M6 7v5M10 7v5M3 4l1 9a1 1 0 001 1h6a1 1 0 001-1l1-9"/></svg>
|
|
@@ -1332,4 +1332,17 @@ body {
|
|
|
1332
1332
|
background: rgba(239, 68, 68, 0.1);
|
|
1333
1333
|
border-color: var(--accent-red);
|
|
1334
1334
|
opacity: 1;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
/* Search highlight */
|
|
1338
|
+
mark {
|
|
1339
|
+
background: rgba(245, 158, 11, 0.25);
|
|
1340
|
+
color: inherit;
|
|
1341
|
+
padding: 1px 2px;
|
|
1342
|
+
border-radius: 2px;
|
|
1343
|
+
font-weight: 600;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
[data-theme="light"] mark {
|
|
1347
|
+
background: rgba(245, 158, 11, 0.35);
|
|
1335
1348
|
}
|