claude-memory-layer 1.0.48 → 1.0.50
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/dist/cli/index.js +442 -8
- package/dist/cli/index.js.map +4 -4
- package/dist/server/api/index.js +439 -5
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +441 -7
- package/dist/server/index.js.map +4 -4
- package/dist/ui/assets/js/bootstrap.js +63 -1
- package/dist/ui/assets/js/disclosure.js +158 -10
- package/dist/ui/assets/js/modals.js +39 -18
- package/dist/ui/assets/js/overview.js +99 -9
- package/dist/ui/assets/js/state.js +28 -1
- package/dist/ui/assets/js/views.js +626 -15
- package/dist/ui/index.html +130 -0
- package/dist/ui/style.css +460 -0
- package/package.json +4 -1
|
@@ -32,6 +32,88 @@ async function loadVectorHealthData() {
|
|
|
32
32
|
.catch(() => null);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
async function loadProjectDetailData() {
|
|
36
|
+
if (!state.currentProject) {
|
|
37
|
+
state.projectDetail = null;
|
|
38
|
+
state.projectDetailProject = null;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const projectHash = state.currentProject;
|
|
42
|
+
state.projectDetail = await fetch(apiUrl(`${API_BASE}/projects/${encodeURIComponent(projectHash)}/detail`))
|
|
43
|
+
.then(r => r.ok !== false ? r.json() : null)
|
|
44
|
+
.catch(() => null);
|
|
45
|
+
state.projectDetailProject = projectHash;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function topEntries(record, limit = 3) {
|
|
49
|
+
return Object.entries(record || {})
|
|
50
|
+
.sort((a, b) => Number(b[1] || 0) - Number(a[1] || 0))
|
|
51
|
+
.slice(0, limit);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function updateProjectDetailUI() {
|
|
55
|
+
const el = document.getElementById('project-detail-card');
|
|
56
|
+
if (!el) return;
|
|
57
|
+
|
|
58
|
+
if (!state.currentProject) {
|
|
59
|
+
el.hidden = true;
|
|
60
|
+
el.innerHTML = '';
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
el.hidden = false;
|
|
65
|
+
const detail = state.projectDetailProject === state.currentProject ? state.projectDetail : null;
|
|
66
|
+
if (!detail) {
|
|
67
|
+
el.innerHTML = `
|
|
68
|
+
<div class="card-header">
|
|
69
|
+
<div class="card-title"><i class="ri-folder-chart-line"></i><span>Project Detail</span></div>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="disclosure-empty">Project detail is unavailable for the selected scope.</div>
|
|
72
|
+
`;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const project = detail.project || {};
|
|
77
|
+
const storage = detail.storage || {};
|
|
78
|
+
const sessions = detail.sessions || {};
|
|
79
|
+
const retrieval = detail.retrieval || {};
|
|
80
|
+
const outbox = detail.outbox || {};
|
|
81
|
+
const eventTypeChips = topEntries(detail.eventTypes, 4)
|
|
82
|
+
.map(([label, count]) => `<span class="event-type-badge ${eventTypeBadgeClass(label)}">${escapeHtml(label)} · ${formatNumber(count)}</span>`)
|
|
83
|
+
.join('') || '<span class="session-muted">No event types</span>';
|
|
84
|
+
const sourceChips = topEntries(detail.sources, 3)
|
|
85
|
+
.map(([label, count]) => `<span class="disclosure-scope-pill">${escapeHtml(label)} · ${formatNumber(count)}</span>`)
|
|
86
|
+
.join('') || '<span class="session-muted">No source metadata</span>';
|
|
87
|
+
const selectionRate = `${((retrieval.selectionRate || 0) * 100).toFixed(1)}% selection`;
|
|
88
|
+
|
|
89
|
+
el.innerHTML = `
|
|
90
|
+
<div class="card-header" style="align-items:flex-start;">
|
|
91
|
+
<div>
|
|
92
|
+
<div class="card-title"><i class="ri-folder-chart-line"></i><span>Project Detail</span></div>
|
|
93
|
+
<div class="session-muted">${escapeHtml(project.projectName || state.currentProject)} · ${escapeHtml(project.registered ? 'registered' : 'unregistered')}</div>
|
|
94
|
+
</div>
|
|
95
|
+
<span class="disclosure-scope-pill">${escapeHtml(project.hash || state.currentProject)}</span>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="stats-grid kpi-grid" style="margin-top:0; margin-bottom:14px;">
|
|
98
|
+
<div class="stat-card kpi-card"><div class="stat-value">${formatNumber(storage.eventCount || 0)} events</div><div class="stat-label"><i class="ri-file-list-3-line"></i> total</div></div>
|
|
99
|
+
<div class="stat-card kpi-card"><div class="stat-value">${formatNumber(sessions.total || 0)} sessions</div><div class="stat-label"><i class="ri-discuss-line"></i> active</div></div>
|
|
100
|
+
<div class="stat-card kpi-card"><div class="stat-value">${formatNumber(storage.vectorCount || 0)} vectors</div><div class="stat-label"><i class="ri-node-tree"></i> indexed</div></div>
|
|
101
|
+
<div class="stat-card kpi-card"><div class="stat-value">${formatNumber(retrieval.totalQueries || 0)}</div><div class="stat-label"><i class="ri-search-eye-line"></i> ${selectionRate}</div></div>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="cfg-grid">
|
|
104
|
+
<div class="cfg-section">
|
|
105
|
+
<div class="cfg-section-title"><i class="ri-price-tag-3-line"></i>Event types</div>
|
|
106
|
+
<div style="display:flex; gap:6px; flex-wrap:wrap;">${eventTypeChips}</div>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="cfg-section">
|
|
109
|
+
<div class="cfg-section-title"><i class="ri-router-line"></i>Sources & outbox</div>
|
|
110
|
+
<div style="display:flex; gap:6px; flex-wrap:wrap; margin-bottom:8px;">${sourceChips}</div>
|
|
111
|
+
<div class="session-muted">${formatNumber(outbox.pending || 0)} pending · ${formatNumber(outbox.processing || 0)} processing · ${formatNumber(outbox.failed || 0)} failed · ${formatNumber(outbox.stuckProcessing || 0)} stuck</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
`;
|
|
115
|
+
}
|
|
116
|
+
|
|
35
117
|
async function refreshData() {
|
|
36
118
|
const btn = document.getElementById('refresh-btn');
|
|
37
119
|
if(btn) btn.classList.add('loading');
|
|
@@ -41,7 +123,7 @@ async function refreshData() {
|
|
|
41
123
|
const kpiWindowAtStart = state.kpiWindow;
|
|
42
124
|
|
|
43
125
|
try {
|
|
44
|
-
const [stats, shared, mostAccessed, helpfulness, memoryUsefulness, retrievalTraces, retrievalReviewQueue, operationsStats, perspectiveStats, adherenceSummary, vectorHealth] = await Promise.all([
|
|
126
|
+
const [stats, shared, mostAccessed, helpfulness, memoryUsefulness, retrievalTraces, retrievalReviewQueue, operationsStats, perspectiveStats, adherenceSummary, vectorHealth, projectDetail] = await Promise.all([
|
|
45
127
|
fetch(apiUrl(`${API_BASE}/stats`)).then(r => r.json()).catch(() => null),
|
|
46
128
|
fetch(apiUrl(`${API_BASE}/stats/shared`)).then(r => r.json()).catch(() => null),
|
|
47
129
|
fetch(apiUrl(`${API_BASE}/stats/most-accessed`, { limit: 10 })).then(r => r.json()).catch(() => null),
|
|
@@ -52,7 +134,10 @@ async function refreshData() {
|
|
|
52
134
|
fetch(apiUrl(`${API_BASE}/stats/operations`, { windowDays: operationStatsWindowDays() })).then(r => r.ok ? r.json() : null).catch(() => null),
|
|
53
135
|
fetch(apiUrl(`${API_BASE}/stats/perspective`, { windowDays: operationStatsWindowDays() })).then(r => r.ok ? r.json() : null).catch(() => null),
|
|
54
136
|
fetchAdherenceSummary().catch(() => null),
|
|
55
|
-
fetch(apiUrl(`${API_BASE}/health`)).then(r => r.ok ? r.json() : null).catch(() => null)
|
|
137
|
+
fetch(apiUrl(`${API_BASE}/health`)).then(r => r.ok ? r.json() : null).catch(() => null),
|
|
138
|
+
state.currentProject
|
|
139
|
+
? fetch(apiUrl(`${API_BASE}/projects/${encodeURIComponent(state.currentProject)}/detail`)).then(r => r.ok ? r.json() : null).catch(() => null)
|
|
140
|
+
: Promise.resolve(null)
|
|
56
141
|
]);
|
|
57
142
|
|
|
58
143
|
if (
|
|
@@ -74,11 +159,14 @@ async function refreshData() {
|
|
|
74
159
|
state.perspectiveStats = perspectiveStats;
|
|
75
160
|
state.adherenceSummary = adherenceSummary;
|
|
76
161
|
state.vectorHealth = vectorHealth;
|
|
162
|
+
state.projectDetail = projectDetail;
|
|
163
|
+
state.projectDetailProject = state.currentProject || null;
|
|
77
164
|
|
|
78
165
|
await loadKpiData();
|
|
79
166
|
if (refreshRequestId !== state.refreshRequestId) return;
|
|
80
167
|
|
|
81
168
|
updateStatsUI();
|
|
169
|
+
updateProjectDetailUI();
|
|
82
170
|
updateSharedUI();
|
|
83
171
|
updateMemoryUsageUI();
|
|
84
172
|
updateKpiCardsUI();
|
|
@@ -120,6 +208,7 @@ async function loadLevelEvents(level, sort) {
|
|
|
120
208
|
|
|
121
209
|
function updateStatsUI() {
|
|
122
210
|
if (!state.stats) return;
|
|
211
|
+
if (typeof updateProjectScopeUI === 'function') updateProjectScopeUI();
|
|
123
212
|
|
|
124
213
|
const eventCount = state.stats.storage?.eventCount || 0;
|
|
125
214
|
const sessionCount = state.stats.sessions?.total || 0;
|
|
@@ -319,7 +408,7 @@ function updateEventsListUI() {
|
|
|
319
408
|
|
|
320
409
|
const time = new Date(event.timestamp).toLocaleString();
|
|
321
410
|
const eventType = event.eventType || event.type || 'unknown';
|
|
322
|
-
const typeClass =
|
|
411
|
+
const typeClass = eventTypeBadgeClass(eventType);
|
|
323
412
|
const preview = event.preview || event.content || '';
|
|
324
413
|
const accessBadge = event.accessCount > 0
|
|
325
414
|
? `<span class="access-badge"><i class="ri-eye-line"></i> ${event.accessCount}</span>`
|
|
@@ -332,7 +421,7 @@ function updateEventsListUI() {
|
|
|
332
421
|
el.innerHTML = `
|
|
333
422
|
<div class="event-header">
|
|
334
423
|
<div style="display:flex; gap:8px; align-items:center;">
|
|
335
|
-
<span class="event-type-badge ${typeClass}">${eventType}</span>
|
|
424
|
+
<span class="event-type-badge ${typeClass}">${escapeHtml(eventType)}</span>
|
|
336
425
|
${adherenceBadge}
|
|
337
426
|
</div>
|
|
338
427
|
<div style="display:flex; gap:8px; align-items:center;">
|
|
@@ -367,11 +456,12 @@ function updateTopAccessedEventsUI() {
|
|
|
367
456
|
const preview = (m.summary || m.preview || m.content || '').replace(/<[^>]*>/g, '').slice(0, 80);
|
|
368
457
|
const lastAccessed = m.lastAccessedAt ? new Date(m.lastAccessedAt).toLocaleDateString() : (m.lastAccessed ? new Date(m.lastAccessed).toLocaleDateString() : '-');
|
|
369
458
|
const id = m.id || m.memoryId || '';
|
|
459
|
+
const typeClass = eventTypeBadgeClass(type);
|
|
370
460
|
return `
|
|
371
|
-
<div class="shared-item" style="cursor:pointer;" ${id ? `onclick="openDetailModal(
|
|
461
|
+
<div class="shared-item" style="cursor:pointer;" ${id ? `onclick="openDetailModal(${jsAttrArg(id)})"` : ''}>
|
|
372
462
|
<div class="shared-info" style="flex-direction:column; align-items:flex-start; gap:2px;">
|
|
373
463
|
<div style="display:flex; gap:6px; align-items:center;">
|
|
374
|
-
<span class="event-type-badge
|
|
464
|
+
<span class="event-type-badge ${typeClass}">${escapeHtml(type)}</span>
|
|
375
465
|
<span style="font-size:10px; color:var(--text-muted);">last: ${lastAccessed}</span>
|
|
376
466
|
</div>
|
|
377
467
|
<span style="font-size:12px; color:var(--text-secondary); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:200px;" title="${escapeHtml(preview)}">${escapeHtml(preview) || '(no preview)'}</span>
|
|
@@ -1233,9 +1323,9 @@ function updateRetrievalTraceUI() {
|
|
|
1233
1323
|
const selectedIdsHtml = selectedDetails.length > 0
|
|
1234
1324
|
? selectedDetails.map((d) => {
|
|
1235
1325
|
const breakdown = `score=${Number(d.score || 0).toFixed(3)} · s=${Number(d.semanticScore || 0).toFixed(3)} · l=${Number(d.lexicalScore || 0).toFixed(3)} · r=${Number(d.recencyScore || 0).toFixed(3)}`;
|
|
1236
|
-
return `<span class="event-type-badge" style="cursor:pointer;" onclick="openDetailModal(
|
|
1326
|
+
return `<span class="event-type-badge" style="cursor:pointer;" onclick="openDetailModal(${jsAttrArg(d.eventId || '')})" title="${escapeHtml(breakdown)}">${escapeHtml((d.eventId || '').slice(0, 8))}...</span>`;
|
|
1237
1327
|
}).join(' ')
|
|
1238
|
-
: ((t.selectedEventIds || []).slice(0, 2).map((id) => `<span class="event-type-badge" style="cursor:pointer;" onclick="openDetailModal(
|
|
1328
|
+
: ((t.selectedEventIds || []).slice(0, 2).map((id) => `<span class="event-type-badge" style="cursor:pointer;" onclick="openDetailModal(${jsAttrArg(id || '')})">${escapeHtml((id || '').slice(0, 8))}...</span>`).join(' ') || '-');
|
|
1239
1329
|
|
|
1240
1330
|
const scoreBreakdownHtml = selectedDetails.length > 0
|
|
1241
1331
|
? selectedDetails.map((d) => `<div style="font-size:10px; color:var(--text-muted);">${escapeHtml((d.eventId || '').slice(0, 8))}... → score ${Number(d.score || 0).toFixed(3)} (s ${Number(d.semanticScore || 0).toFixed(3)}, l ${Number(d.lexicalScore || 0).toFixed(3)}, r ${Number(d.recencyScore || 0).toFixed(3)})</div>`).join('')
|
|
@@ -1247,7 +1337,7 @@ function updateRetrievalTraceUI() {
|
|
|
1247
1337
|
<span style="font-size:12px; color:var(--text-secondary);"><strong>Trace:</strong> ${escapeHtml((t.traceId || '').slice(0, 12)) || '-'}</span>
|
|
1248
1338
|
<span style="font-size:11px; color:var(--text-muted);">${ts} · strategy=${escapeHtml(t.strategy || 'auto')} · conf=${escapeHtml(confidence)} ${rewriteBadge}</span>
|
|
1249
1339
|
<span style="font-size:11px; color:var(--text-muted);">selected IDs: ${selectedIdsHtml}</span>
|
|
1250
|
-
<span style="font-size:11px; color:var(--text-muted);">candidates: ${candidateDetails.map((d) => `<span class=\"event-type-badge\" style=\"cursor:pointer;\" onclick=\"openDetailModal(
|
|
1340
|
+
<span style="font-size:11px; color:var(--text-muted);">candidates: ${candidateDetails.map((d) => `<span class=\"event-type-badge\" style=\"cursor:pointer;\" onclick=\"openDetailModal(${jsAttrArg(d.eventId || '')})\">${escapeHtml((d.eventId || '').slice(0, 8))}...</span>`).join(' ') || '-'}</span>
|
|
1251
1341
|
${scoreBreakdownHtml}
|
|
1252
1342
|
</div>
|
|
1253
1343
|
<div style="display:flex; flex-direction:column; align-items:flex-end; gap:2px; min-width:68px;">
|
|
@@ -17,6 +17,8 @@ const state = {
|
|
|
17
17
|
operationsStats: null,
|
|
18
18
|
perspectiveStats: null,
|
|
19
19
|
vectorHealth: null,
|
|
20
|
+
projectDetail: null,
|
|
21
|
+
projectDetailProject: null,
|
|
20
22
|
vectorHealthRecovery: null,
|
|
21
23
|
vectorHealthRecoveryProject: null,
|
|
22
24
|
isVectorRecoveryRunning: false,
|
|
@@ -26,6 +28,19 @@ const state = {
|
|
|
26
28
|
userPromptItems: [],
|
|
27
29
|
userPromptPage: 1,
|
|
28
30
|
userPromptPageSize: 30,
|
|
31
|
+
sessionInspectorSessions: [],
|
|
32
|
+
sessionInspectorPage: 1,
|
|
33
|
+
sessionInspectorPageSize: 50,
|
|
34
|
+
selectedSession: null,
|
|
35
|
+
selectedSessionTurns: [],
|
|
36
|
+
selectedSessionEvents: [],
|
|
37
|
+
sessionSnapshotTab: 'overview',
|
|
38
|
+
sessionJumpEventId: null,
|
|
39
|
+
pendingSessionJump: null,
|
|
40
|
+
sessionInspectorRequestId: 0,
|
|
41
|
+
sessionDetailRequestId: 0,
|
|
42
|
+
isSessionInspectorLoading: false,
|
|
43
|
+
isSessionDetailLoading: false,
|
|
29
44
|
currentLevel: 'L0',
|
|
30
45
|
currentSort: 'recent',
|
|
31
46
|
currentView: 'overview',
|
|
@@ -46,15 +61,27 @@ const state = {
|
|
|
46
61
|
chatCurrentTab: 'chat',
|
|
47
62
|
disclosureResults: [],
|
|
48
63
|
disclosureMeta: null,
|
|
64
|
+
disclosureQuery: '',
|
|
49
65
|
disclosureSelectedId: null,
|
|
50
66
|
disclosureExpansion: null,
|
|
51
67
|
disclosureSource: null,
|
|
52
|
-
isDisclosureLoading: false
|
|
68
|
+
isDisclosureLoading: false,
|
|
69
|
+
playgroundLastRun: null,
|
|
70
|
+
isPlaygroundLoading: false
|
|
53
71
|
};
|
|
54
72
|
|
|
55
73
|
// Utils
|
|
56
74
|
const formatNumber = (num) => new Intl.NumberFormat().format(num || 0);
|
|
57
75
|
|
|
76
|
+
function eventTypeBadgeClass(type) {
|
|
77
|
+
const token = String(type || 'unknown')
|
|
78
|
+
.toLowerCase()
|
|
79
|
+
.replace(/_/g, '-')
|
|
80
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
81
|
+
.replace(/^-+|-+$/g, '') || 'unknown';
|
|
82
|
+
return `type-${token}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
58
85
|
// Colors for Chart
|
|
59
86
|
const CHART_COLORS = {
|
|
60
87
|
L0: '#7B61FF',
|