runtime-memory 3.0.0__py3-none-any.whl
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.
- runtime_memory/__init__.py +28 -0
- runtime_memory/claude_code/__init__.py +48 -0
- runtime_memory/claude_code/commands.py +698 -0
- runtime_memory/claude_code/daemon.py +852 -0
- runtime_memory/claude_code/hooks.py +722 -0
- runtime_memory/cli/__init__.py +8 -0
- runtime_memory/cli/main.py +1936 -0
- runtime_memory/core/__init__.py +216 -0
- runtime_memory/core/config.py +473 -0
- runtime_memory/core/embeddings.py +908 -0
- runtime_memory/core/engine.py +1007 -0
- runtime_memory/core/exceptions.py +547 -0
- runtime_memory/core/legacy_env.py +39 -0
- runtime_memory/core/logging.py +160 -0
- runtime_memory/core/models.py +1051 -0
- runtime_memory/core/observability.py +725 -0
- runtime_memory/core/paths.py +30 -0
- runtime_memory/core/resilience.py +511 -0
- runtime_memory/core/retrieval.py +819 -0
- runtime_memory/core/storage.py +1105 -0
- runtime_memory/extraction/__init__.py +36 -0
- runtime_memory/extraction/extractor.py +1143 -0
- runtime_memory/hermes/__init__.py +39 -0
- runtime_memory/hermes/_base.py +154 -0
- runtime_memory/hermes/bridge.py +119 -0
- runtime_memory/hermes/plugin.yaml +13 -0
- runtime_memory/hermes/provider.py +536 -0
- runtime_memory/hermes/tools.py +230 -0
- runtime_memory/hermes/trace.py +177 -0
- runtime_memory/plugin/__init__.py +646 -0
- runtime_memory/sdk/__init__.py +97 -0
- runtime_memory/sdk/client.py +1577 -0
- runtime_memory/server/__init__.py +75 -0
- runtime_memory/server/api.py +1665 -0
- runtime_memory/server/mcp.py +1574 -0
- runtime_memory/server/static/css/styles.css +1110 -0
- runtime_memory/server/static/index.html +264 -0
- runtime_memory/server/static/js/api.js +294 -0
- runtime_memory/server/static/js/app.js +771 -0
- runtime_memory/tasks/__init__.py +114 -0
- runtime_memory/tasks/adapter.py +501 -0
- runtime_memory/tasks/claude_code_adapter.py +495 -0
- runtime_memory/tasks/claude_code_parser.py +339 -0
- runtime_memory/tasks/cli_bridge.py +415 -0
- runtime_memory/tasks/linking.py +397 -0
- runtime_memory/tasks/models.py +520 -0
- runtime_memory/tasks/outcomes.py +320 -0
- runtime_memory/tasks/parser.py +305 -0
- runtime_memory/tasks/unified_adapter.py +661 -0
- runtime_memory-3.0.0.dist-info/METADATA +497 -0
- runtime_memory-3.0.0.dist-info/RECORD +54 -0
- runtime_memory-3.0.0.dist-info/WHEEL +4 -0
- runtime_memory-3.0.0.dist-info/entry_points.txt +6 -0
- runtime_memory-3.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Memory Web UI Application
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
class MemoryLayerApp {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.currentView = 'dashboard';
|
|
8
|
+
this.memories = [];
|
|
9
|
+
this.currentPage = 1;
|
|
10
|
+
this.pageSize = 50;
|
|
11
|
+
this.selectedMemory = null;
|
|
12
|
+
this.stats = null;
|
|
13
|
+
|
|
14
|
+
this.init();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// =========================================================================
|
|
18
|
+
// Initialization
|
|
19
|
+
// =========================================================================
|
|
20
|
+
|
|
21
|
+
async init() {
|
|
22
|
+
this.setupEventListeners();
|
|
23
|
+
this.loadTheme();
|
|
24
|
+
await this.loadDashboard();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setupEventListeners() {
|
|
28
|
+
// Navigation
|
|
29
|
+
document.querySelectorAll('.nav-btn').forEach(btn => {
|
|
30
|
+
btn.addEventListener('click', (e) => {
|
|
31
|
+
this.showView(e.target.dataset.view);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Theme toggle
|
|
36
|
+
document.getElementById('theme-toggle').addEventListener('click', () => {
|
|
37
|
+
this.toggleTheme();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Add memory form
|
|
41
|
+
document.getElementById('add-memory-form').addEventListener('submit', (e) => {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
this.addMemory();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Search on enter
|
|
47
|
+
document.getElementById('search-input').addEventListener('keypress', (e) => {
|
|
48
|
+
if (e.key === 'Enter') {
|
|
49
|
+
this.performSearch();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Filters
|
|
54
|
+
document.getElementById('filter-category').addEventListener('change', () => {
|
|
55
|
+
this.loadMemories();
|
|
56
|
+
});
|
|
57
|
+
document.getElementById('filter-project').addEventListener('change', () => {
|
|
58
|
+
this.loadMemories();
|
|
59
|
+
});
|
|
60
|
+
document.getElementById('filter-search').addEventListener('input', () => {
|
|
61
|
+
this.filterMemoriesLocally();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Close modal on background click
|
|
65
|
+
document.getElementById('memory-modal').addEventListener('click', (e) => {
|
|
66
|
+
if (e.target.id === 'memory-modal') {
|
|
67
|
+
this.closeModal();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Escape key closes modal
|
|
72
|
+
document.addEventListener('keydown', (e) => {
|
|
73
|
+
if (e.key === 'Escape') {
|
|
74
|
+
this.closeModal();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// =========================================================================
|
|
80
|
+
// Theme Management
|
|
81
|
+
// =========================================================================
|
|
82
|
+
|
|
83
|
+
loadTheme() {
|
|
84
|
+
const savedTheme = localStorage.getItem('theme') || 'dark';
|
|
85
|
+
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
86
|
+
this.updateThemeIcon(savedTheme);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
toggleTheme() {
|
|
90
|
+
const current = document.documentElement.getAttribute('data-theme');
|
|
91
|
+
const next = current === 'dark' ? 'light' : 'dark';
|
|
92
|
+
document.documentElement.setAttribute('data-theme', next);
|
|
93
|
+
localStorage.setItem('theme', next);
|
|
94
|
+
this.updateThemeIcon(next);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
updateThemeIcon(theme) {
|
|
98
|
+
const icon = document.querySelector('.theme-icon');
|
|
99
|
+
icon.textContent = theme === 'dark' ? '\u263E' : '\u2600';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// =========================================================================
|
|
103
|
+
// View Navigation
|
|
104
|
+
// =========================================================================
|
|
105
|
+
|
|
106
|
+
showView(viewName) {
|
|
107
|
+
this.currentView = viewName;
|
|
108
|
+
|
|
109
|
+
// Update nav buttons
|
|
110
|
+
document.querySelectorAll('.nav-btn').forEach(btn => {
|
|
111
|
+
btn.classList.toggle('active', btn.dataset.view === viewName);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Show/hide views
|
|
115
|
+
document.querySelectorAll('.view').forEach(view => {
|
|
116
|
+
view.classList.toggle('active', view.id === `view-${viewName}`);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Load data for view
|
|
120
|
+
switch (viewName) {
|
|
121
|
+
case 'dashboard':
|
|
122
|
+
this.loadDashboard();
|
|
123
|
+
break;
|
|
124
|
+
case 'memories':
|
|
125
|
+
this.loadMemories();
|
|
126
|
+
break;
|
|
127
|
+
case 'search':
|
|
128
|
+
document.getElementById('search-input').focus();
|
|
129
|
+
break;
|
|
130
|
+
case 'beads':
|
|
131
|
+
this.loadBeads();
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// =========================================================================
|
|
137
|
+
// Dashboard
|
|
138
|
+
// =========================================================================
|
|
139
|
+
|
|
140
|
+
async loadDashboard() {
|
|
141
|
+
try {
|
|
142
|
+
this.stats = await api.getStats();
|
|
143
|
+
this.renderStats();
|
|
144
|
+
await this.loadRecentMemories();
|
|
145
|
+
} catch (error) {
|
|
146
|
+
this.showToast('Failed to load dashboard', 'error');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
renderStats() {
|
|
151
|
+
const stats = this.stats;
|
|
152
|
+
document.getElementById('stat-total').textContent = stats.total_memories || 0;
|
|
153
|
+
document.getElementById('stat-active').textContent = stats.active_memories || 0;
|
|
154
|
+
document.getElementById('stat-archived').textContent = stats.archived_memories || 0;
|
|
155
|
+
document.getElementById('stat-avg-score').textContent =
|
|
156
|
+
(stats.avg_outcome_score || 0).toFixed(2);
|
|
157
|
+
|
|
158
|
+
// Render category bars
|
|
159
|
+
this.renderCategoryBars(stats.by_category || {});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
renderCategoryBars(byCategory) {
|
|
163
|
+
const container = document.getElementById('category-bars');
|
|
164
|
+
const maxCount = Math.max(...Object.values(byCategory), 1);
|
|
165
|
+
|
|
166
|
+
// All categories with their colors (matching CSS)
|
|
167
|
+
const categoryColors = {
|
|
168
|
+
'architecture': '#3b82f6', // blue
|
|
169
|
+
'convention': '#8b5cf6', // purple
|
|
170
|
+
'decision': '#06b6d4', // cyan
|
|
171
|
+
'pattern': '#10b981', // green (success)
|
|
172
|
+
'gotcha': '#ef4444', // red (danger)
|
|
173
|
+
'workaround': '#f97316', // orange
|
|
174
|
+
'troubleshooting': '#f59e0b', // amber (warning)
|
|
175
|
+
'command': '#64748b', // slate
|
|
176
|
+
'preference': '#ec4899', // pink
|
|
177
|
+
'general': '#6b7280' // gray
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const categories = Object.keys(categoryColors);
|
|
181
|
+
|
|
182
|
+
container.innerHTML = categories
|
|
183
|
+
.map(cat => {
|
|
184
|
+
const count = byCategory[cat] || 0;
|
|
185
|
+
const width = maxCount > 0 ? (count / maxCount * 100).toFixed(1) : 0;
|
|
186
|
+
const color = categoryColors[cat];
|
|
187
|
+
return `
|
|
188
|
+
<div class="category-bar">
|
|
189
|
+
<span class="label">${cat}</span>
|
|
190
|
+
<div class="bar-container">
|
|
191
|
+
<div class="bar" style="width: ${width}%; background-color: ${color}"></div>
|
|
192
|
+
</div>
|
|
193
|
+
<span class="count">${count}</span>
|
|
194
|
+
</div>
|
|
195
|
+
`;
|
|
196
|
+
})
|
|
197
|
+
.join('');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async loadRecentMemories() {
|
|
201
|
+
try {
|
|
202
|
+
const memories = await api.getMemories({ limit: 5 });
|
|
203
|
+
this.renderRecentMemories(memories);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error('Failed to load recent memories', error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
renderRecentMemories(memories) {
|
|
210
|
+
const container = document.getElementById('recent-memories');
|
|
211
|
+
|
|
212
|
+
if (!memories || memories.length === 0) {
|
|
213
|
+
container.innerHTML = '<div class="empty-state"><p>No memories yet</p></div>';
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
container.innerHTML = memories.map(m => `
|
|
218
|
+
<div class="recent-item" onclick="app.viewMemory('${m.id}')">
|
|
219
|
+
<div class="content">${this.escapeHtml(m.content)}</div>
|
|
220
|
+
<div class="meta">
|
|
221
|
+
<span class="category-badge ${m.category}">${m.category}</span>
|
|
222
|
+
<span>${this.formatDate(m.created_at)}</span>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
`).join('');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// =========================================================================
|
|
229
|
+
// Memories List
|
|
230
|
+
// =========================================================================
|
|
231
|
+
|
|
232
|
+
async loadMemories() {
|
|
233
|
+
try {
|
|
234
|
+
const category = document.getElementById('filter-category').value;
|
|
235
|
+
const project = document.getElementById('filter-project').value;
|
|
236
|
+
|
|
237
|
+
console.log('Loading memories...');
|
|
238
|
+
this.memories = await api.getMemories({
|
|
239
|
+
category: category || undefined,
|
|
240
|
+
project: project || undefined,
|
|
241
|
+
limit: 100
|
|
242
|
+
});
|
|
243
|
+
console.log('Loaded memories:', this.memories);
|
|
244
|
+
|
|
245
|
+
this.renderMemoriesTable();
|
|
246
|
+
this.updateProjectFilter();
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.error('Failed to load memories:', error);
|
|
249
|
+
const msg = error.message || JSON.stringify(error);
|
|
250
|
+
this.showToast('Failed to load memories: ' + msg, 'error');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
renderMemoriesTable() {
|
|
255
|
+
const tbody = document.getElementById('memories-tbody');
|
|
256
|
+
const start = (this.currentPage - 1) * this.pageSize;
|
|
257
|
+
const pageMemories = this.memories.slice(start, start + this.pageSize);
|
|
258
|
+
|
|
259
|
+
if (pageMemories.length === 0) {
|
|
260
|
+
tbody.innerHTML = `
|
|
261
|
+
<tr>
|
|
262
|
+
<td colspan="6" class="empty-state">
|
|
263
|
+
<p>No memories found</p>
|
|
264
|
+
</td>
|
|
265
|
+
</tr>
|
|
266
|
+
`;
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
tbody.innerHTML = pageMemories.map(m => `
|
|
271
|
+
<tr onclick="app.viewMemory('${m.id}')">
|
|
272
|
+
<td><span class="category-badge ${m.category}">${m.category}</span></td>
|
|
273
|
+
<td class="content-cell">${this.escapeHtml(m.content)}</td>
|
|
274
|
+
<td class="score-cell ${m.outcome_score > 0 ? 'positive' : m.outcome_score < 0 ? 'negative' : ''}">
|
|
275
|
+
${m.outcome_score.toFixed(2)}
|
|
276
|
+
</td>
|
|
277
|
+
<td>${m.project || 'global'}</td>
|
|
278
|
+
<td>${this.formatDate(m.created_at)}</td>
|
|
279
|
+
<td class="actions-cell">
|
|
280
|
+
<button class="btn btn-sm btn-secondary" onclick="event.stopPropagation(); app.viewMemory('${m.id}')">
|
|
281
|
+
View
|
|
282
|
+
</button>
|
|
283
|
+
</td>
|
|
284
|
+
</tr>
|
|
285
|
+
`).join('');
|
|
286
|
+
|
|
287
|
+
this.renderPagination();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
renderPagination() {
|
|
291
|
+
const totalPages = Math.ceil(this.memories.length / this.pageSize);
|
|
292
|
+
const container = document.getElementById('pagination');
|
|
293
|
+
|
|
294
|
+
if (totalPages <= 1) {
|
|
295
|
+
container.innerHTML = '';
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let html = '';
|
|
300
|
+
for (let i = 1; i <= totalPages; i++) {
|
|
301
|
+
html += `<button class="${i === this.currentPage ? 'active' : ''}"
|
|
302
|
+
onclick="app.goToPage(${i})">${i}</button>`;
|
|
303
|
+
}
|
|
304
|
+
container.innerHTML = html;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
goToPage(page) {
|
|
308
|
+
this.currentPage = page;
|
|
309
|
+
this.renderMemoriesTable();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
updateProjectFilter() {
|
|
313
|
+
const projects = [...new Set(this.memories.map(m => m.project).filter(Boolean))];
|
|
314
|
+
const select = document.getElementById('filter-project');
|
|
315
|
+
const current = select.value;
|
|
316
|
+
|
|
317
|
+
select.innerHTML = '<option value="">All Projects</option>' +
|
|
318
|
+
projects.map(p => `<option value="${p}">${p}</option>`).join('');
|
|
319
|
+
|
|
320
|
+
select.value = current;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
filterMemoriesLocally() {
|
|
324
|
+
const searchTerm = document.getElementById('filter-search').value.toLowerCase();
|
|
325
|
+
const rows = document.querySelectorAll('#memories-tbody tr');
|
|
326
|
+
|
|
327
|
+
rows.forEach(row => {
|
|
328
|
+
const text = row.textContent.toLowerCase();
|
|
329
|
+
row.style.display = text.includes(searchTerm) ? '' : 'none';
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// =========================================================================
|
|
334
|
+
// Search
|
|
335
|
+
// =========================================================================
|
|
336
|
+
|
|
337
|
+
async performSearch() {
|
|
338
|
+
const query = document.getElementById('search-input').value.trim();
|
|
339
|
+
if (!query) return;
|
|
340
|
+
|
|
341
|
+
const categories = Array.from(document.querySelectorAll('.search-cat:checked'))
|
|
342
|
+
.map(cb => cb.value);
|
|
343
|
+
const searchType = document.getElementById('search-type').value;
|
|
344
|
+
|
|
345
|
+
const container = document.getElementById('search-results');
|
|
346
|
+
container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
|
|
347
|
+
|
|
348
|
+
console.log('Performing search:', { query, categories, searchType });
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const results = await api.search(query, {
|
|
352
|
+
categories: categories.length > 0 ? categories : null,
|
|
353
|
+
limit: 20,
|
|
354
|
+
searchType: searchType
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
console.log('Search results:', results);
|
|
358
|
+
this.renderSearchResults(results, searchType);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.error('Search failed:', error);
|
|
361
|
+
container.innerHTML = '<div class="empty-state"><p>Search failed: ' + (error.message || 'Unknown error') + '</p></div>';
|
|
362
|
+
this.showToast('Search failed: ' + (error.message || 'Unknown error'), 'error');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
renderSearchResults(results, searchType = 'semantic') {
|
|
367
|
+
const container = document.getElementById('search-results');
|
|
368
|
+
|
|
369
|
+
if (!results || results.length === 0) {
|
|
370
|
+
container.innerHTML = '<div class="empty-state"><p>No results found</p></div>';
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
container.innerHTML = results.map(r => `
|
|
375
|
+
<div class="search-result" onclick="app.viewMemory('${r.memory.id}')">
|
|
376
|
+
<div class="header">
|
|
377
|
+
<span class="category-badge ${r.memory.category}">${r.memory.category}</span>
|
|
378
|
+
${searchType === 'semantic'
|
|
379
|
+
? `<span class="relevance-score">${(r.score * 100).toFixed(0)}% match</span>`
|
|
380
|
+
: `<span class="relevance-score">Keyword match</span>`
|
|
381
|
+
}
|
|
382
|
+
</div>
|
|
383
|
+
<div class="content">${this.escapeHtml(r.memory.content)}</div>
|
|
384
|
+
<div class="meta">
|
|
385
|
+
<span>Score: ${r.memory.outcome_score.toFixed(2)}</span>
|
|
386
|
+
<span>${r.memory.project || 'global'}</span>
|
|
387
|
+
<span>${this.formatDate(r.memory.created_at)}</span>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
`).join('');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// =========================================================================
|
|
394
|
+
// Add Memory
|
|
395
|
+
// =========================================================================
|
|
396
|
+
|
|
397
|
+
async addMemory() {
|
|
398
|
+
const content = document.getElementById('memory-content').value.trim();
|
|
399
|
+
const category = document.getElementById('memory-category').value;
|
|
400
|
+
const project = document.getElementById('memory-project').value.trim() || null;
|
|
401
|
+
const tagsInput = document.getElementById('memory-tags').value.trim();
|
|
402
|
+
const tags = tagsInput ? tagsInput.split(',').map(t => t.trim()).filter(Boolean) : [];
|
|
403
|
+
|
|
404
|
+
if (!content) {
|
|
405
|
+
this.showToast('Content is required', 'warning');
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
await api.createMemory({
|
|
411
|
+
content,
|
|
412
|
+
category,
|
|
413
|
+
project,
|
|
414
|
+
tags
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
this.showToast('Memory saved successfully', 'success');
|
|
418
|
+
document.getElementById('add-memory-form').reset();
|
|
419
|
+
this.showView('memories');
|
|
420
|
+
} catch (error) {
|
|
421
|
+
this.showToast('Failed to save memory: ' + error.message, 'error');
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// =========================================================================
|
|
426
|
+
// Memory Detail Modal
|
|
427
|
+
// =========================================================================
|
|
428
|
+
|
|
429
|
+
async viewMemory(id) {
|
|
430
|
+
try {
|
|
431
|
+
const memory = await api.getMemory(id);
|
|
432
|
+
this.selectedMemory = memory;
|
|
433
|
+
this.renderMemoryDetail(memory);
|
|
434
|
+
document.getElementById('memory-modal').classList.add('active');
|
|
435
|
+
} catch (error) {
|
|
436
|
+
this.showToast('Failed to load memory', 'error');
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
renderMemoryDetail(memory) {
|
|
441
|
+
const body = document.getElementById('modal-body');
|
|
442
|
+
body.innerHTML = `
|
|
443
|
+
<div class="detail-row">
|
|
444
|
+
<div class="detail-label">Category</div>
|
|
445
|
+
<div class="detail-value">
|
|
446
|
+
<span class="category-badge ${memory.category}">${memory.category}</span>
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
<div class="detail-row">
|
|
450
|
+
<div class="detail-label">Content</div>
|
|
451
|
+
<div class="detail-content">${this.escapeHtml(memory.content)}</div>
|
|
452
|
+
</div>
|
|
453
|
+
<div class="detail-row">
|
|
454
|
+
<div class="detail-label">Outcome Score</div>
|
|
455
|
+
<div class="detail-value">${memory.outcome_score.toFixed(2)}</div>
|
|
456
|
+
</div>
|
|
457
|
+
<div class="detail-row">
|
|
458
|
+
<div class="detail-label">Project</div>
|
|
459
|
+
<div class="detail-value">${memory.project || 'global'}</div>
|
|
460
|
+
</div>
|
|
461
|
+
<div class="detail-row">
|
|
462
|
+
<div class="detail-label">Tags</div>
|
|
463
|
+
<div class="detail-value">${memory.tags?.join(', ') || 'None'}</div>
|
|
464
|
+
</div>
|
|
465
|
+
<div class="detail-row">
|
|
466
|
+
<div class="detail-label">Created</div>
|
|
467
|
+
<div class="detail-value">${this.formatDate(memory.created_at)}</div>
|
|
468
|
+
</div>
|
|
469
|
+
<div class="detail-row">
|
|
470
|
+
<div class="detail-label">ID</div>
|
|
471
|
+
<div class="detail-value" style="font-family: monospace; font-size: 0.8rem;">${memory.id}</div>
|
|
472
|
+
</div>
|
|
473
|
+
`;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
closeModal() {
|
|
477
|
+
document.getElementById('memory-modal').classList.remove('active');
|
|
478
|
+
this.selectedMemory = null;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async recordOutcome(outcome) {
|
|
482
|
+
if (!this.selectedMemory) return;
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
await api.recordOutcome(this.selectedMemory.id, outcome);
|
|
486
|
+
this.showToast(`Recorded outcome: ${outcome}`, 'success');
|
|
487
|
+
this.closeModal();
|
|
488
|
+
await this.loadDashboard();
|
|
489
|
+
} catch (error) {
|
|
490
|
+
this.showToast('Failed to record outcome', 'error');
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async deleteMemory() {
|
|
495
|
+
if (!this.selectedMemory) return;
|
|
496
|
+
|
|
497
|
+
if (!confirm('Are you sure you want to delete this memory?')) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
try {
|
|
502
|
+
await api.deleteMemory(this.selectedMemory.id);
|
|
503
|
+
this.showToast('Memory deleted', 'success');
|
|
504
|
+
this.closeModal();
|
|
505
|
+
await this.loadMemories();
|
|
506
|
+
await this.loadDashboard();
|
|
507
|
+
} catch (error) {
|
|
508
|
+
this.showToast('Failed to delete memory', 'error');
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// =========================================================================
|
|
513
|
+
// Export
|
|
514
|
+
// =========================================================================
|
|
515
|
+
|
|
516
|
+
async exportMemories() {
|
|
517
|
+
try {
|
|
518
|
+
const memories = await api.exportMemories();
|
|
519
|
+
const json = JSON.stringify(memories, null, 2);
|
|
520
|
+
const blob = new Blob([json], { type: 'application/json' });
|
|
521
|
+
const url = URL.createObjectURL(blob);
|
|
522
|
+
|
|
523
|
+
const a = document.createElement('a');
|
|
524
|
+
a.href = url;
|
|
525
|
+
a.download = `memory-layer-export-${new Date().toISOString().split('T')[0]}.json`;
|
|
526
|
+
a.click();
|
|
527
|
+
|
|
528
|
+
URL.revokeObjectURL(url);
|
|
529
|
+
this.showToast('Export complete', 'success');
|
|
530
|
+
} catch (error) {
|
|
531
|
+
this.showToast('Export failed', 'error');
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// =========================================================================
|
|
536
|
+
// Unified Tasks Integration (Phase 7 - Beads + Claude Code)
|
|
537
|
+
// =========================================================================
|
|
538
|
+
|
|
539
|
+
async loadBeads() {
|
|
540
|
+
// Now uses unified tasks API
|
|
541
|
+
try {
|
|
542
|
+
const [statsResponse, tasksResponse] = await Promise.all([
|
|
543
|
+
api.getTasksStats(),
|
|
544
|
+
api.getTasks({ limit: 100 })
|
|
545
|
+
]);
|
|
546
|
+
|
|
547
|
+
this.renderTasksStats(statsResponse);
|
|
548
|
+
this.renderTasks(tasksResponse.tasks || [], tasksResponse.sources || []);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
console.error('Failed to load tasks:', error);
|
|
551
|
+
this.showToast('Failed to load tasks: ' + (error.message || 'Unknown error'), 'error');
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
renderTasksStats(stats) {
|
|
556
|
+
// Combine stats from all sources
|
|
557
|
+
let total = 0;
|
|
558
|
+
let inProgress = 0;
|
|
559
|
+
let pending = 0;
|
|
560
|
+
let done = 0;
|
|
561
|
+
|
|
562
|
+
// Add Beads stats
|
|
563
|
+
if (stats.beads && stats.beads.tasks) {
|
|
564
|
+
const beadsStats = stats.beads.tasks.by_status || {};
|
|
565
|
+
total += stats.beads.tasks.total_tasks || 0;
|
|
566
|
+
inProgress += beadsStats.in_progress || 0;
|
|
567
|
+
pending += beadsStats.pending || 0;
|
|
568
|
+
done += beadsStats.done || 0;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Add Claude Code stats
|
|
572
|
+
if (stats.claude_code && stats.claude_code.tasks) {
|
|
573
|
+
const ccStats = stats.claude_code.tasks.by_status || {};
|
|
574
|
+
total += stats.claude_code.tasks.total_tasks || 0;
|
|
575
|
+
inProgress += ccStats.in_progress || 0;
|
|
576
|
+
pending += ccStats.pending || 0;
|
|
577
|
+
done += ccStats.completed || 0;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
document.getElementById('beads-total').textContent = total;
|
|
581
|
+
document.getElementById('beads-in-progress').textContent = inProgress;
|
|
582
|
+
document.getElementById('beads-pending').textContent = pending;
|
|
583
|
+
document.getElementById('beads-done').textContent = done;
|
|
584
|
+
|
|
585
|
+
// Update sources info
|
|
586
|
+
const sourcesInfo = document.getElementById('tasks-sources');
|
|
587
|
+
if (sourcesInfo) {
|
|
588
|
+
const sources = stats.available_sources || [];
|
|
589
|
+
sourcesInfo.textContent = sources.length > 0 ? `Sources: ${sources.join(', ')}` : 'No task sources available';
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
renderTasks(tasks, sources) {
|
|
594
|
+
const container = document.getElementById('beads-task-list');
|
|
595
|
+
|
|
596
|
+
if (!tasks || tasks.length === 0) {
|
|
597
|
+
container.innerHTML = `
|
|
598
|
+
<div class="empty-state">
|
|
599
|
+
<p>No tasks found</p>
|
|
600
|
+
<p class="text-muted">${sources.length > 0 ? `Available sources: ${sources.join(', ')}` : 'No task sources available'}</p>
|
|
601
|
+
</div>
|
|
602
|
+
`;
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const statusColors = {
|
|
607
|
+
'done': 'success',
|
|
608
|
+
'completed': 'success',
|
|
609
|
+
'in_progress': 'warning',
|
|
610
|
+
'pending': 'secondary',
|
|
611
|
+
'blocked': 'danger',
|
|
612
|
+
'cancelled': 'danger'
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
const statusIcons = {
|
|
616
|
+
'done': '✓',
|
|
617
|
+
'completed': '✓',
|
|
618
|
+
'in_progress': '►',
|
|
619
|
+
'pending': '○',
|
|
620
|
+
'blocked': '⊘',
|
|
621
|
+
'cancelled': '✗'
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
const sourceLabels = {
|
|
625
|
+
'beads': 'B',
|
|
626
|
+
'claude_code': 'C'
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
const sourceColors = {
|
|
630
|
+
'beads': 'source-beads',
|
|
631
|
+
'claude_code': 'source-claude'
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
container.innerHTML = tasks.map(task => `
|
|
635
|
+
<div class="task-item ${task.status}" onclick="app.showTaskContext('${task.id}', '${task.source || ''}')">
|
|
636
|
+
<div class="task-status">
|
|
637
|
+
<span class="status-icon ${statusColors[task.status] || 'secondary'}">${statusIcons[task.status] || '?'}</span>
|
|
638
|
+
</div>
|
|
639
|
+
<div class="task-source">
|
|
640
|
+
<span class="source-badge ${sourceColors[task.source] || ''}" title="${task.source || 'unknown'}">${sourceLabels[task.source] || '?'}</span>
|
|
641
|
+
</div>
|
|
642
|
+
<div class="task-info">
|
|
643
|
+
<div class="task-title">${this.escapeHtml(task.title)}</div>
|
|
644
|
+
<div class="task-meta">
|
|
645
|
+
<span class="task-id">${task.id}</span>
|
|
646
|
+
<span class="task-status-text">${(task.status || '').replace('_', ' ')}</span>
|
|
647
|
+
</div>
|
|
648
|
+
</div>
|
|
649
|
+
<div class="task-actions">
|
|
650
|
+
<button class="btn btn-sm btn-secondary" onclick="event.stopPropagation(); app.showTaskContext('${task.id}', '${task.source || ''}')">
|
|
651
|
+
View Context
|
|
652
|
+
</button>
|
|
653
|
+
</div>
|
|
654
|
+
</div>
|
|
655
|
+
`).join('');
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
async showTaskContext(taskId, source = null) {
|
|
659
|
+
try {
|
|
660
|
+
// Use unified tasks API
|
|
661
|
+
const context = await api.getTaskContext(taskId, source);
|
|
662
|
+
|
|
663
|
+
document.getElementById('task-context').style.display = 'block';
|
|
664
|
+
document.getElementById('task-context-title').textContent = `Context: ${context.task_title}`;
|
|
665
|
+
|
|
666
|
+
const content = document.getElementById('task-context-content');
|
|
667
|
+
|
|
668
|
+
if (context.memories_count === 0) {
|
|
669
|
+
content.innerHTML = '<div class="empty-state"><p>No relevant memories found for this task</p></div>';
|
|
670
|
+
} else {
|
|
671
|
+
// Parse the formatted markdown-like content
|
|
672
|
+
content.innerHTML = `
|
|
673
|
+
<div class="context-header">
|
|
674
|
+
<span class="task-status-badge ${context.task_status}">${(context.task_status || '').replace('_', ' ')}</span>
|
|
675
|
+
<span class="task-source-badge">${context.source || 'unknown'}</span>
|
|
676
|
+
<span class="memories-count">${context.memories_count} relevant memories</span>
|
|
677
|
+
</div>
|
|
678
|
+
<div class="context-memories">
|
|
679
|
+
${this.formatContextMemories(context.formatted)}
|
|
680
|
+
</div>
|
|
681
|
+
`;
|
|
682
|
+
}
|
|
683
|
+
} catch (error) {
|
|
684
|
+
console.error('Failed to load task context:', error);
|
|
685
|
+
this.showToast('Failed to load task context', 'error');
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
formatContextMemories(formatted) {
|
|
690
|
+
// Extract memory lines from formatted text
|
|
691
|
+
const lines = formatted.split('\n').filter(line => line.startsWith('- **['));
|
|
692
|
+
|
|
693
|
+
if (lines.length === 0) {
|
|
694
|
+
return '<p>No memories in context</p>';
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return lines.map(line => {
|
|
698
|
+
// Parse: - **[category]** content...
|
|
699
|
+
const match = line.match(/- \*\*\[(\w+)\]\*\* (.+)/);
|
|
700
|
+
if (match) {
|
|
701
|
+
const [, category, content] = match;
|
|
702
|
+
return `
|
|
703
|
+
<div class="context-memory">
|
|
704
|
+
<span class="category-badge ${category}">${category}</span>
|
|
705
|
+
<span class="memory-content">${this.escapeHtml(content)}</span>
|
|
706
|
+
</div>
|
|
707
|
+
`;
|
|
708
|
+
}
|
|
709
|
+
return '';
|
|
710
|
+
}).join('');
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
closeTaskContext() {
|
|
714
|
+
document.getElementById('task-context').style.display = 'none';
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
async syncBeads() {
|
|
718
|
+
// Now uses unified tasks API
|
|
719
|
+
try {
|
|
720
|
+
this.showToast('Syncing tasks...', 'info');
|
|
721
|
+
const result = await api.syncTasks();
|
|
722
|
+
|
|
723
|
+
if (result.success) {
|
|
724
|
+
const synced = result.total_tasks_synced || result.tasks_synced || 0;
|
|
725
|
+
const outcomes = result.total_outcomes_recorded || result.outcomes_recorded || 0;
|
|
726
|
+
this.showToast(`Synced: ${synced} tasks, ${outcomes} outcomes`, 'success');
|
|
727
|
+
await this.loadBeads();
|
|
728
|
+
} else {
|
|
729
|
+
this.showToast('Sync failed', 'error');
|
|
730
|
+
}
|
|
731
|
+
} catch (error) {
|
|
732
|
+
console.error('Sync failed:', error);
|
|
733
|
+
this.showToast('Sync failed: ' + (error.message || 'Unknown error'), 'error');
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// =========================================================================
|
|
738
|
+
// Utilities
|
|
739
|
+
// =========================================================================
|
|
740
|
+
|
|
741
|
+
escapeHtml(text) {
|
|
742
|
+
const div = document.createElement('div');
|
|
743
|
+
div.textContent = text;
|
|
744
|
+
return div.innerHTML;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
formatDate(dateString) {
|
|
748
|
+
if (!dateString) return '';
|
|
749
|
+
const date = new Date(dateString);
|
|
750
|
+
return date.toLocaleDateString('en-US', {
|
|
751
|
+
year: 'numeric',
|
|
752
|
+
month: 'short',
|
|
753
|
+
day: 'numeric'
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
showToast(message, type = 'info') {
|
|
758
|
+
const container = document.getElementById('toast-container');
|
|
759
|
+
const toast = document.createElement('div');
|
|
760
|
+
toast.className = `toast ${type}`;
|
|
761
|
+
toast.textContent = message;
|
|
762
|
+
container.appendChild(toast);
|
|
763
|
+
|
|
764
|
+
setTimeout(() => {
|
|
765
|
+
toast.remove();
|
|
766
|
+
}, 3000);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Initialize app
|
|
771
|
+
const app = new MemoryLayerApp();
|