@vheins/local-memory-mcp 0.1.44 → 0.2.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/dist/dashboard/dashboard.test.js +11 -3
- package/dist/dashboard/dashboard.test.js.map +1 -1
- package/dist/dashboard/public/app.js +541 -93
- package/dist/dashboard/public/index.html +1070 -718
- package/dist/dashboard/server.js +110 -16
- package/dist/dashboard/server.js.map +1 -1
- package/dist/e2e.test.js +25 -7
- package/dist/e2e.test.js.map +1 -1
- package/dist/prompts/registry.d.ts +12 -0
- package/dist/prompts/registry.d.ts.map +1 -1
- package/dist/prompts/registry.js +31 -0
- package/dist/prompts/registry.js.map +1 -1
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +23 -0
- package/dist/resources/index.js.map +1 -1
- package/dist/resources/index.test.js +2 -0
- package/dist/resources/index.test.js.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +5 -0
- package/dist/router.js.map +1 -1
- package/dist/router.test.js +1 -1
- package/dist/router.test.js.map +1 -1
- package/dist/storage/sqlite.d.ts +6 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +156 -21
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/storage/sqlite.test.js +2 -0
- package/dist/storage/sqlite.test.js.map +1 -1
- package/dist/tasks.e2e.test.d.ts +2 -0
- package/dist/tasks.e2e.test.d.ts.map +1 -0
- package/dist/tasks.e2e.test.js +119 -0
- package/dist/tasks.e2e.test.js.map +1 -0
- package/dist/tools/memory.store.d.ts.map +1 -1
- package/dist/tools/memory.store.js +2 -0
- package/dist/tools/memory.store.js.map +1 -1
- package/dist/tools/schemas.d.ts +274 -0
- package/dist/tools/schemas.d.ts.map +1 -1
- package/dist/tools/schemas.js +128 -1
- package/dist/tools/schemas.js.map +1 -1
- package/dist/tools/task.manage.d.ts +16 -0
- package/dist/tools/task.manage.d.ts.map +1 -0
- package/dist/tools/task.manage.js +128 -0
- package/dist/tools/task.manage.js.map +1 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +47 -7
- package/dist/utils/logger.js.map +1 -1
- package/dist/v2-features.test.js +7 -1
- package/dist/v2-features.test.js.map +1 -1
- package/package.json +1 -1
- package/dist/search_memory_example.d.ts +0 -3
- package/dist/search_memory_example.d.ts.map +0 -1
- package/dist/search_memory_example.js +0 -56
- package/dist/search_memory_example.js.map +0 -1
- package/dist/server_robustness.test.d.ts +0 -2
- package/dist/server_robustness.test.d.ts.map +0 -1
- package/dist/server_robustness.test.js +0 -107
- package/dist/server_robustness.test.js.map +0 -1
- package/dist/store_memory_example.d.ts +0 -3
- package/dist/store_memory_example.d.ts.map +0 -1
- package/dist/store_memory_example.js +0 -69
- package/dist/store_memory_example.js.map +0 -1
- package/dist/test_quotes_client.d.ts +0 -3
- package/dist/test_quotes_client.d.ts.map +0 -1
- package/dist/test_quotes_client.js +0 -72
- package/dist/test_quotes_client.js.map +0 -1
|
@@ -23,6 +23,18 @@ let pinnedRepoOrder = [];
|
|
|
23
23
|
let draggedPinnedRepo = null;
|
|
24
24
|
let isGlobalFilterActive = false;
|
|
25
25
|
|
|
26
|
+
function syncStickyOffsets() {
|
|
27
|
+
const topBar = document.getElementById('mainTopBar');
|
|
28
|
+
const tabNav = document.querySelector('.sticky-tab-nav');
|
|
29
|
+
if (!topBar) return;
|
|
30
|
+
|
|
31
|
+
const topBarHeight = Math.ceil(topBar.getBoundingClientRect().height);
|
|
32
|
+
const tabNavHeight = tabNav ? Math.ceil(tabNav.getBoundingClientRect().height) : 0;
|
|
33
|
+
|
|
34
|
+
document.documentElement.style.setProperty('--dashboard-header-offset', `${topBarHeight}px`);
|
|
35
|
+
document.documentElement.style.setProperty('--dashboard-tab-offset', `${tabNavHeight}px`);
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
async function loadRecentActions(page = recentActionsPage) {
|
|
27
39
|
try {
|
|
28
40
|
let url = `/api/recent-actions?page=${page}&pageSize=${recentActionsPageSize}`;
|
|
@@ -97,22 +109,22 @@ function getBubbleStyle(action) {
|
|
|
97
109
|
bubble: 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-100',
|
|
98
110
|
label: 'text-gray-500 dark:text-gray-400',
|
|
99
111
|
meta: 'text-gray-400 dark:text-gray-500',
|
|
100
|
-
align: 'items-
|
|
101
|
-
tail: '
|
|
112
|
+
align: 'items-start',
|
|
113
|
+
tail: 'left-2 -bottom-1.5 border-r-gray-100 dark:border-r-gray-700',
|
|
102
114
|
},
|
|
103
115
|
update: {
|
|
104
116
|
bubble: 'bg-amber-100 dark:bg-amber-900 text-amber-900 dark:text-amber-100',
|
|
105
117
|
label: 'text-amber-600 dark:text-amber-400',
|
|
106
118
|
meta: 'text-amber-500 dark:text-amber-500',
|
|
107
|
-
align: 'items-
|
|
108
|
-
tail: '
|
|
119
|
+
align: 'items-start',
|
|
120
|
+
tail: 'left-2 -bottom-1.5 border-r-amber-100 dark:border-r-amber-900',
|
|
109
121
|
},
|
|
110
122
|
delete: {
|
|
111
123
|
bubble: 'bg-red-100 dark:bg-red-900 text-red-900 dark:text-red-100',
|
|
112
124
|
label: 'text-red-500 dark:text-red-400',
|
|
113
125
|
meta: 'text-red-400',
|
|
114
|
-
align: 'items-
|
|
115
|
-
tail: '
|
|
126
|
+
align: 'items-start',
|
|
127
|
+
tail: 'left-2 -bottom-1.5 border-r-red-100 dark:border-r-red-900',
|
|
116
128
|
},
|
|
117
129
|
};
|
|
118
130
|
return styles[action] || styles.search;
|
|
@@ -120,22 +132,28 @@ function getBubbleStyle(action) {
|
|
|
120
132
|
|
|
121
133
|
function renderActionBubble(action) {
|
|
122
134
|
const s = getBubbleStyle(action.action);
|
|
123
|
-
const isRight =
|
|
135
|
+
const isRight = false; // Force left alignment
|
|
124
136
|
|
|
125
137
|
// Main content line
|
|
126
138
|
let mainText = '';
|
|
127
139
|
let subText = '';
|
|
128
140
|
|
|
129
141
|
if (action.action === 'search') {
|
|
130
|
-
mainText =
|
|
142
|
+
mainText = `🔍 "${action.query || ''}"`;
|
|
131
143
|
subText = action.result_count != null ? `${action.result_count} result${action.result_count !== 1 ? 's' : ''} found` : '';
|
|
132
144
|
} else {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
145
|
+
if (action.task_id) {
|
|
146
|
+
mainText = action.task_title || action.task_code || action.task_id.substring(0, 8);
|
|
147
|
+
const verb = { write: '💾 Created Task', update: '🔄 Updated Task', delete: '🗑️ Deleted Task' }[action.action] || action.action;
|
|
148
|
+
subText = action.task_code ? `${verb} [${action.task_code}]` : verb;
|
|
149
|
+
} else {
|
|
150
|
+
mainText = action.memory_title
|
|
151
|
+
? action.memory_title
|
|
152
|
+
: action.memory_id ? action.memory_id.substring(0, 8) + '…' : '—';
|
|
153
|
+
const typeLabel = action.memory_type ? `[${action.memory_type}]` : '';
|
|
154
|
+
const verb = { write: '💾 Stored', update: '🔄 Updated', delete: '🗑️ Deleted', read: '📖 Read' }[action.action] || action.action;
|
|
155
|
+
subText = [verb, typeLabel].filter(Boolean).join(' ');
|
|
156
|
+
}
|
|
139
157
|
}
|
|
140
158
|
|
|
141
159
|
const burst = action.burstCount > 1
|
|
@@ -144,14 +162,14 @@ function renderActionBubble(action) {
|
|
|
144
162
|
|
|
145
163
|
return `
|
|
146
164
|
<div class="flex flex-col ${s.align} mb-4">
|
|
147
|
-
<div class="flex items-center gap-1.5 mb-1 px-1
|
|
148
|
-
<div class="w-5 h-5 rounded-full ${s.
|
|
149
|
-
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">${getActionIcon(action.action)}</svg>
|
|
165
|
+
<div class="flex items-center gap-1.5 mb-1 px-1">
|
|
166
|
+
<div class="w-5 h-5 rounded-full ${s.bubble} flex items-center justify-center flex-shrink-0">
|
|
167
|
+
<svg class="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">${getActionIcon(action.action)}</svg>
|
|
150
168
|
</div>
|
|
151
169
|
<span class="text-xs font-semibold capitalize ${s.label.replace('text-', 'text-').replace('100','500').replace('200','500')}">${action.action}${burst}</span>
|
|
152
170
|
</div>
|
|
153
|
-
<div class="relative max-w-[
|
|
154
|
-
<div class="${s.bubble} rounded-2xl
|
|
171
|
+
<div class="relative max-w-[95%]">
|
|
172
|
+
<div class="${s.bubble} rounded-2xl rounded-bl-sm px-3 py-2 shadow-sm">
|
|
155
173
|
<p class="text-sm font-medium leading-snug break-words">${mainText}</p>
|
|
156
174
|
${subText ? `<p class="text-xs mt-0.5 ${s.meta}">${subText}</p>` : ''}
|
|
157
175
|
</div>
|
|
@@ -401,6 +419,7 @@ function renderRepoSidebar() {
|
|
|
401
419
|
document.getElementById('repoSidebarList').innerHTML = renderGroups();
|
|
402
420
|
const mobile = document.getElementById('repoSidebarListMobile');
|
|
403
421
|
if (mobile) mobile.innerHTML = renderGroups();
|
|
422
|
+
syncStickyOffsets();
|
|
404
423
|
}
|
|
405
424
|
|
|
406
425
|
async function setCurrentRepo(repo) {
|
|
@@ -417,6 +436,7 @@ async function setCurrentRepo(repo) {
|
|
|
417
436
|
loadMemories(),
|
|
418
437
|
loadRecentActions(),
|
|
419
438
|
]);
|
|
439
|
+
syncStickyOffsets();
|
|
420
440
|
}
|
|
421
441
|
|
|
422
442
|
function openRepoSidebarDrawer() {
|
|
@@ -517,6 +537,11 @@ async function checkStatus() {
|
|
|
517
537
|
dbPathLabel.title = data.dbPath;
|
|
518
538
|
}
|
|
519
539
|
|
|
540
|
+
const appVersion = document.getElementById('appVersion');
|
|
541
|
+
if (appVersion && data.version) {
|
|
542
|
+
appVersion.textContent = `v${data.version}`;
|
|
543
|
+
}
|
|
544
|
+
|
|
520
545
|
const summary = document.getElementById('memorySummaryLabel');
|
|
521
546
|
if (summary) {
|
|
522
547
|
summary.textContent = `${data.memoryCount || 0} memories indexed`;
|
|
@@ -561,6 +586,9 @@ async function loadStats() {
|
|
|
561
586
|
try {
|
|
562
587
|
const url = currentRepo ? `/api/stats?repo=${encodeURIComponent(currentRepo)}` : '/api/stats';
|
|
563
588
|
const response = await fetch(url);
|
|
589
|
+
if (!response.ok) {
|
|
590
|
+
throw new Error(`Stats request failed with ${response.status}`);
|
|
591
|
+
}
|
|
564
592
|
const data = await response.json();
|
|
565
593
|
|
|
566
594
|
document.getElementById('totalCount').textContent = data.total;
|
|
@@ -576,6 +604,7 @@ async function loadStats() {
|
|
|
576
604
|
updateTimeSeriesChart(data.timeSeries || {});
|
|
577
605
|
updateScatterChart(data.scatterData || []);
|
|
578
606
|
updateTopMemoriesChart(data.topMemories);
|
|
607
|
+
syncStickyOffsets();
|
|
579
608
|
} catch (err) {
|
|
580
609
|
console.error('Failed to load stats:', err);
|
|
581
610
|
}
|
|
@@ -583,38 +612,58 @@ async function loadStats() {
|
|
|
583
612
|
|
|
584
613
|
function updateTypeChart(byType) {
|
|
585
614
|
const ctx = document.getElementById('typeChart');
|
|
586
|
-
if (!window.Chart)
|
|
587
|
-
ctx.parentElement.innerHTML = '<div class="p-8 text-center text-gray-500">Chart.js not available</div>';
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
615
|
+
if (!window.Chart || !ctx) return;
|
|
590
616
|
if (charts.typeChart) charts.typeChart.destroy();
|
|
591
617
|
|
|
592
|
-
const
|
|
593
|
-
const counts =
|
|
618
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
619
|
+
const counts = [
|
|
620
|
+
byType?.decision || 0,
|
|
621
|
+
byType?.mistake || 0,
|
|
622
|
+
byType?.code_fact || 0,
|
|
623
|
+
byType?.pattern || 0
|
|
624
|
+
];
|
|
594
625
|
|
|
595
626
|
charts.typeChart = new Chart(ctx, {
|
|
596
|
-
type: '
|
|
627
|
+
type: 'doughnut',
|
|
597
628
|
data: {
|
|
598
|
-
labels:
|
|
629
|
+
labels: ['Decision', 'Mistake', 'Code Fact', 'Pattern'],
|
|
599
630
|
datasets: [{
|
|
600
631
|
data: counts,
|
|
601
|
-
backgroundColor: ['#
|
|
602
|
-
|
|
603
|
-
|
|
632
|
+
backgroundColor: ['#fb7185', '#c084fc', '#38bdf8', '#34d399'],
|
|
633
|
+
borderWidth: 2,
|
|
634
|
+
borderColor: isDark ? '#1e293b' : '#ffffff',
|
|
635
|
+
hoverOffset: 12
|
|
604
636
|
}]
|
|
605
637
|
},
|
|
606
|
-
options: {
|
|
638
|
+
options: {
|
|
639
|
+
responsive: true,
|
|
640
|
+
maintainAspectRatio: false,
|
|
641
|
+
cutout: '68%',
|
|
642
|
+
plugins: {
|
|
643
|
+
legend: { display: false },
|
|
644
|
+
tooltip: {
|
|
645
|
+
backgroundColor: isDark ? '#1e293b' : '#ffffff',
|
|
646
|
+
titleColor: isDark ? '#f8fafc' : '#1e293b',
|
|
647
|
+
bodyColor: isDark ? '#94a3b8' : '#64748b',
|
|
648
|
+
borderColor: isDark ? '#334155' : '#e2e8f0',
|
|
649
|
+
borderWidth: 1,
|
|
650
|
+
padding: 10,
|
|
651
|
+
cornerRadius: 10
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
607
655
|
});
|
|
608
656
|
}
|
|
609
657
|
|
|
610
658
|
function updateTopMemoriesChart(memories = []) {
|
|
611
659
|
const ctx = document.getElementById('topMemoriesChart');
|
|
612
660
|
if (!window.Chart) {
|
|
613
|
-
ctx.parentElement.innerHTML = '<div class="p-
|
|
661
|
+
ctx.parentElement.innerHTML = '<div class="p-4 text-center text-gray-500 text-xs">Chart.js not available</div>';
|
|
614
662
|
return;
|
|
615
663
|
}
|
|
616
664
|
if (charts.topMemoriesChart) charts.topMemoriesChart.destroy();
|
|
617
665
|
|
|
666
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
618
667
|
charts.topMemoriesChart = new Chart(ctx, {
|
|
619
668
|
type: 'bar',
|
|
620
669
|
data: {
|
|
@@ -622,59 +671,263 @@ function updateTopMemoriesChart(memories = []) {
|
|
|
622
671
|
datasets: [{
|
|
623
672
|
label: 'Hit Count',
|
|
624
673
|
data: memories.map(m => m.hit_count || m.importance),
|
|
625
|
-
backgroundColor:
|
|
626
|
-
|
|
674
|
+
backgroundColor: isDark
|
|
675
|
+
? 'rgba(56,189,248,0.45)'
|
|
676
|
+
: 'rgba(37,99,235,0.55)',
|
|
677
|
+
borderColor: isDark ? '#38bdf8' : '#2563eb',
|
|
678
|
+
borderWidth: 1,
|
|
679
|
+
borderRadius: 6,
|
|
680
|
+
borderSkipped: false
|
|
627
681
|
}]
|
|
628
682
|
},
|
|
629
|
-
options: {
|
|
683
|
+
options: {
|
|
684
|
+
responsive: true,
|
|
685
|
+
maintainAspectRatio: false,
|
|
686
|
+
plugins: {
|
|
687
|
+
legend: { display: false },
|
|
688
|
+
tooltip: {
|
|
689
|
+
backgroundColor: isDark ? '#1e293b' : '#ffffff',
|
|
690
|
+
titleColor: isDark ? '#f8fafc' : '#1e293b',
|
|
691
|
+
bodyColor: isDark ? '#94a3b8' : '#64748b',
|
|
692
|
+
borderColor: isDark ? '#334155' : '#e2e8f0',
|
|
693
|
+
borderWidth: 1,
|
|
694
|
+
padding: 8,
|
|
695
|
+
cornerRadius: 8
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
scales: {
|
|
699
|
+
y: {
|
|
700
|
+
beginAtZero: true,
|
|
701
|
+
grid: { color: isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.04)', drawBorder: false },
|
|
702
|
+
ticks: { color: isDark ? '#64748b' : '#94a3b8', font: { size: 10 } }
|
|
703
|
+
},
|
|
704
|
+
x: {
|
|
705
|
+
grid: { display: false },
|
|
706
|
+
ticks: { color: isDark ? '#64748b' : '#94a3b8', font: { size: 10 } }
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
630
710
|
});
|
|
631
711
|
}
|
|
632
712
|
|
|
633
713
|
function updateTimeSeriesChart(timeSeries) {
|
|
634
714
|
const ctx = document.getElementById('timeSeriesChart');
|
|
635
|
-
if (!window.Chart) return;
|
|
715
|
+
if (!window.Chart || !ctx) return;
|
|
636
716
|
if (charts.timeSeriesChart) charts.timeSeriesChart.destroy();
|
|
637
717
|
|
|
638
|
-
const labels = Object.keys(timeSeries)
|
|
639
|
-
const
|
|
718
|
+
const labels = Object.keys(timeSeries);
|
|
719
|
+
const creationData = labels.map(k => (typeof timeSeries[k] === 'object' ? timeSeries[k].write : timeSeries[k]) || 0);
|
|
720
|
+
const readData = labels.map(k => (typeof timeSeries[k] === 'object' ? timeSeries[k].read : 0) || 0);
|
|
721
|
+
const searchData = labels.map(k => (typeof timeSeries[k] === 'object' ? timeSeries[k].search : 0) || 0);
|
|
722
|
+
|
|
723
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
640
724
|
|
|
641
725
|
charts.timeSeriesChart = new Chart(ctx, {
|
|
642
726
|
type: 'line',
|
|
643
727
|
data: {
|
|
644
|
-
labels,
|
|
645
|
-
datasets: [
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
728
|
+
labels: labels.map(l => l.split('-').slice(1).join('/')),
|
|
729
|
+
datasets: [
|
|
730
|
+
{
|
|
731
|
+
label: 'Created',
|
|
732
|
+
data: creationData,
|
|
733
|
+
borderColor: '#22d3ee',
|
|
734
|
+
backgroundColor: 'rgba(34, 211, 238, 0.1)',
|
|
735
|
+
borderWidth: 2,
|
|
736
|
+
pointRadius: 3,
|
|
737
|
+
tension: 0.4,
|
|
738
|
+
fill: true
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
label: 'Read',
|
|
742
|
+
data: readData,
|
|
743
|
+
borderColor: '#10b981',
|
|
744
|
+
backgroundColor: 'transparent',
|
|
745
|
+
borderWidth: 2,
|
|
746
|
+
pointRadius: 3,
|
|
747
|
+
tension: 0.4,
|
|
748
|
+
borderDash: [5, 5]
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
label: 'Search',
|
|
752
|
+
data: searchData,
|
|
753
|
+
borderColor: '#6366f1',
|
|
754
|
+
backgroundColor: 'transparent',
|
|
755
|
+
borderWidth: 2,
|
|
756
|
+
pointRadius: 3,
|
|
757
|
+
tension: 0.4,
|
|
758
|
+
borderDash: [2, 2]
|
|
759
|
+
}
|
|
760
|
+
]
|
|
655
761
|
},
|
|
656
|
-
options: {
|
|
762
|
+
options: {
|
|
763
|
+
responsive: true,
|
|
764
|
+
maintainAspectRatio: false,
|
|
765
|
+
plugins: {
|
|
766
|
+
legend: {
|
|
767
|
+
position: 'top',
|
|
768
|
+
align: 'end',
|
|
769
|
+
labels: {
|
|
770
|
+
color: isDark ? '#94a3b8' : '#64748b',
|
|
771
|
+
usePointStyle: true,
|
|
772
|
+
boxWidth: 6,
|
|
773
|
+
font: { size: 10, weight: 'bold' }
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
scales: {
|
|
778
|
+
y: {
|
|
779
|
+
beginAtZero: true,
|
|
780
|
+
grid: { color: isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)', drawBorder: false },
|
|
781
|
+
ticks: { color: isDark ? '#64748b' : '#94a3b8', font: { size: 10 }, stepSize: 1 }
|
|
782
|
+
},
|
|
783
|
+
x: {
|
|
784
|
+
grid: { display: false },
|
|
785
|
+
ticks: { color: isDark ? '#64748b' : '#94a3b8', font: { size: 10 }, maxRotation: 0 }
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
657
789
|
});
|
|
658
790
|
}
|
|
659
791
|
|
|
792
|
+
function showAddMemoryModal() {
|
|
793
|
+
if (!currentRepo) {
|
|
794
|
+
showToast('Please select a repository first', 'error');
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
document.getElementById('addMemoryModal').classList.remove('hidden');
|
|
798
|
+
document.body.classList.add('drawer-open');
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function hideAddMemoryModal() {
|
|
802
|
+
document.getElementById('addMemoryModal').classList.add('hidden');
|
|
803
|
+
document.body.classList.remove('drawer-open');
|
|
804
|
+
document.getElementById('addMemoryForm').reset();
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function showAddTaskModal() {
|
|
808
|
+
if (!currentRepo) {
|
|
809
|
+
showToast('Please select a repository first', 'error');
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
document.getElementById('addTaskModal').classList.remove('hidden');
|
|
813
|
+
document.body.classList.add('drawer-open');
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function hideAddTaskModal() {
|
|
817
|
+
document.getElementById('addTaskModal').classList.add('hidden');
|
|
818
|
+
document.body.classList.remove('drawer-open');
|
|
819
|
+
document.getElementById('addTaskForm').reset();
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
async function handleMemorySubmit(event) {
|
|
823
|
+
event.preventDefault();
|
|
824
|
+
const formData = new FormData(event.target);
|
|
825
|
+
const data = Object.fromEntries(formData.entries());
|
|
826
|
+
data.repo = currentRepo;
|
|
827
|
+
data.is_global = event.target.is_global.checked;
|
|
828
|
+
// agent and model are already in data from FormData entries
|
|
829
|
+
|
|
830
|
+
try {
|
|
831
|
+
const res = await fetch('/api/memories', {
|
|
832
|
+
method: 'POST',
|
|
833
|
+
headers: { 'Content-Type': 'application/json' },
|
|
834
|
+
body: JSON.stringify(data)
|
|
835
|
+
});
|
|
836
|
+
if (res.ok) {
|
|
837
|
+
showToast('Memory added successfully', 'success');
|
|
838
|
+
hideAddMemoryModal();
|
|
839
|
+
loadData();
|
|
840
|
+
} else {
|
|
841
|
+
const err = await res.json();
|
|
842
|
+
showToast(err.error || 'Failed to add memory', 'error');
|
|
843
|
+
}
|
|
844
|
+
} catch (err) {
|
|
845
|
+
showToast('Network error', 'error');
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
async function handleTaskSubmit(event) {
|
|
850
|
+
event.preventDefault();
|
|
851
|
+
const formData = new FormData(event.target);
|
|
852
|
+
const data = Object.fromEntries(formData.entries());
|
|
853
|
+
data.repo = currentRepo;
|
|
854
|
+
|
|
855
|
+
try {
|
|
856
|
+
const res = await fetch('/api/tasks', {
|
|
857
|
+
method: 'POST',
|
|
858
|
+
headers: { 'Content-Type': 'application/json' },
|
|
859
|
+
body: JSON.stringify(data)
|
|
860
|
+
});
|
|
861
|
+
if (res.ok) {
|
|
862
|
+
showToast('Task created successfully', 'success');
|
|
863
|
+
hideAddTaskModal();
|
|
864
|
+
loadTasks();
|
|
865
|
+
loadRecentActions();
|
|
866
|
+
} else {
|
|
867
|
+
const err = await res.json();
|
|
868
|
+
showToast(err.error || 'Failed to create task', 'error');
|
|
869
|
+
}
|
|
870
|
+
} catch (err) {
|
|
871
|
+
showToast('Network error', 'error');
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
window.showAddMemoryModal = showAddMemoryModal;
|
|
876
|
+
window.hideAddMemoryModal = hideAddMemoryModal;
|
|
877
|
+
window.showAddTaskModal = showAddTaskModal;
|
|
878
|
+
window.hideAddTaskModal = hideAddTaskModal;
|
|
879
|
+
window.handleMemorySubmit = handleMemorySubmit;
|
|
880
|
+
window.handleTaskSubmit = handleTaskSubmit;
|
|
881
|
+
|
|
660
882
|
function updateScatterChart(scatterData) {
|
|
661
883
|
const ctx = document.getElementById('scatterChart');
|
|
662
884
|
if (!window.Chart) return;
|
|
663
885
|
if (charts.scatterChart) charts.scatterChart.destroy();
|
|
664
886
|
|
|
887
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
665
888
|
charts.scatterChart = new Chart(ctx, {
|
|
666
889
|
type: 'scatter',
|
|
667
890
|
data: {
|
|
668
891
|
datasets: [{
|
|
669
892
|
label: 'Memories',
|
|
670
893
|
data: scatterData,
|
|
671
|
-
backgroundColor: 'rgba(96,
|
|
672
|
-
borderColor: '#
|
|
673
|
-
|
|
674
|
-
|
|
894
|
+
backgroundColor: isDark ? 'rgba(129,140,248,0.55)' : 'rgba(96,165,250,0.7)',
|
|
895
|
+
borderColor: isDark ? '#818cf8' : '#6366f1',
|
|
896
|
+
borderWidth: 1,
|
|
897
|
+
pointRadius: 5,
|
|
898
|
+
pointHoverRadius: 7
|
|
675
899
|
}]
|
|
676
900
|
},
|
|
677
|
-
options: {
|
|
901
|
+
options: {
|
|
902
|
+
responsive: true,
|
|
903
|
+
maintainAspectRatio: false,
|
|
904
|
+
plugins: {
|
|
905
|
+
legend: { display: false },
|
|
906
|
+
tooltip: {
|
|
907
|
+
backgroundColor: isDark ? '#1e293b' : '#ffffff',
|
|
908
|
+
titleColor: isDark ? '#f8fafc' : '#1e293b',
|
|
909
|
+
bodyColor: isDark ? '#94a3b8' : '#64748b',
|
|
910
|
+
borderColor: isDark ? '#334155' : '#e2e8f0',
|
|
911
|
+
borderWidth: 1,
|
|
912
|
+
padding: 8,
|
|
913
|
+
cornerRadius: 8
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
scales: {
|
|
917
|
+
x: {
|
|
918
|
+
title: { display: true, text: 'Importance', color: isDark ? '#64748b' : '#94a3b8', font: { size: 10 } },
|
|
919
|
+
min: 0, max: 6,
|
|
920
|
+
grid: { color: isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.04)' },
|
|
921
|
+
ticks: { color: isDark ? '#64748b' : '#94a3b8', font: { size: 10 } }
|
|
922
|
+
},
|
|
923
|
+
y: {
|
|
924
|
+
title: { display: true, text: 'Hit Count', color: isDark ? '#64748b' : '#94a3b8', font: { size: 10 } },
|
|
925
|
+
beginAtZero: true,
|
|
926
|
+
grid: { color: isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.04)' },
|
|
927
|
+
ticks: { color: isDark ? '#64748b' : '#94a3b8', font: { size: 10 } }
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
678
931
|
});
|
|
679
932
|
}
|
|
680
933
|
|
|
@@ -721,6 +974,7 @@ function renderTableSkeleton() {
|
|
|
721
974
|
<tr class="border-b-2 border-gray-200 dark:border-gray-700">
|
|
722
975
|
<th class="text-left p-3 bg-gray-50 dark:bg-gray-700 font-semibold"></th>
|
|
723
976
|
<th class="text-left p-3 bg-gray-50 dark:bg-gray-700 font-semibold">Memory</th>
|
|
977
|
+
<th class="text-left p-3 bg-gray-50 dark:bg-gray-700 font-semibold">Source</th>
|
|
724
978
|
<th class="text-left p-3 bg-gray-50 dark:bg-gray-700 font-semibold">Type</th>
|
|
725
979
|
<th class="text-left p-3 bg-gray-50 dark:bg-gray-700 font-semibold">Priority</th>
|
|
726
980
|
<th class="text-left p-3 bg-gray-50 dark:bg-gray-700 font-semibold">Usage</th>
|
|
@@ -733,6 +987,7 @@ function renderTableSkeleton() {
|
|
|
733
987
|
<tr class="border-b border-gray-100 dark:border-gray-700">
|
|
734
988
|
<td class="p-3"><div class="skeleton h-4 w-4"></div></td>
|
|
735
989
|
<td class="p-3"><div class="skeleton h-4 w-52 mb-2"></div><div class="skeleton h-3 w-36"></div></td>
|
|
990
|
+
<td class="p-3"><div class="skeleton h-4 w-20 mb-2"></div><div class="skeleton h-3 w-16"></div></td>
|
|
736
991
|
<td class="p-3"><div class="skeleton h-6 w-20"></div></td>
|
|
737
992
|
<td class="p-3"><div class="skeleton h-6 w-12 mb-2"></div><div class="skeleton h-3 w-12"></div></td>
|
|
738
993
|
<td class="p-3"><div class="skeleton h-4 w-16 mb-2"></div><div class="skeleton h-3 w-14"></div></td>
|
|
@@ -752,6 +1007,24 @@ function escapeHtml(text) {
|
|
|
752
1007
|
return div.innerHTML;
|
|
753
1008
|
}
|
|
754
1009
|
|
|
1010
|
+
function renderMarkdown(text) {
|
|
1011
|
+
if (!text) return '';
|
|
1012
|
+
if (typeof marked === 'undefined') {
|
|
1013
|
+
// Fallback if marked.js didn't load
|
|
1014
|
+
const div = document.createElement('div');
|
|
1015
|
+
div.textContent = text;
|
|
1016
|
+
return `<pre style="white-space:pre-wrap;font-size:0.85rem;line-height:1.7">${div.innerHTML}</pre>`;
|
|
1017
|
+
}
|
|
1018
|
+
try {
|
|
1019
|
+
marked.setOptions({ breaks: true, gfm: true });
|
|
1020
|
+
return marked.parse(text);
|
|
1021
|
+
} catch (e) {
|
|
1022
|
+
const div = document.createElement('div');
|
|
1023
|
+
div.textContent = text;
|
|
1024
|
+
return `<pre style="white-space:pre-wrap">${div.innerHTML}</pre>`;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
755
1028
|
function formatDate(dateStr) {
|
|
756
1029
|
const date = new Date(dateStr);
|
|
757
1030
|
const now = new Date();
|
|
@@ -786,7 +1059,9 @@ function renderTable(memories) {
|
|
|
786
1059
|
<tr class="border-b-2 border-gray-200 dark:border-gray-700">
|
|
787
1060
|
<th class="text-left p-3 bg-gray-50 dark:bg-gray-700 font-semibold"><input type="checkbox" id="selectAll" onchange="toggleSelectAll()"></th>
|
|
788
1061
|
<th class="text-left p-3 bg-gray-50 dark:bg-gray-700 font-semibold cursor-pointer" onclick="sortTable('title')" data-sort="title">Memory <span class="sort-icon"></span></th>
|
|
1062
|
+
<th class="text-left p-3 bg-gray-50 dark:bg-gray-700 font-semibold cursor-pointer" onclick="sortTable('agent')" data-sort="agent">Source <span class="sort-icon"></span></th>
|
|
789
1063
|
<th class="text-left p-3 bg-gray-50 dark:bg-gray-700 font-semibold cursor-pointer" onclick="sortTable('type')" data-sort="type">Type <span class="sort-icon"></span></th>
|
|
1064
|
+
|
|
790
1065
|
<th class="text-left p-3 bg-gray-50 dark:bg-gray-700 font-semibold cursor-pointer" onclick="sortTable('importance')" data-sort="importance">Priority <span class="sort-icon"></span></th>
|
|
791
1066
|
<th class="text-left p-3 bg-gray-50 dark:bg-gray-700 font-semibold cursor-pointer" onclick="sortTable('hit_count')" data-sort="hit_count">Usage <span class="sort-icon"></span></th>
|
|
792
1067
|
<th class="text-left p-3 bg-gray-50 dark:bg-gray-700 font-semibold cursor-pointer" onclick="sortTable('created_at')" data-sort="created_at">Freshness <span class="sort-icon"></span></th>
|
|
@@ -811,6 +1086,12 @@ function renderTable(memories) {
|
|
|
811
1086
|
<span>${m.scope?.repo || 'Unknown repo'}</span>
|
|
812
1087
|
</div>
|
|
813
1088
|
</td>
|
|
1089
|
+
<td class="p-3">
|
|
1090
|
+
<div class="flex flex-col">
|
|
1091
|
+
<span class="text-[10px] font-bold text-sky-600 dark:text-sky-400 truncate max-w-[100px]" title="${m.agent || 'unknown'}">${m.agent || 'unknown'}</span>
|
|
1092
|
+
<span class="text-[9px] text-gray-400 dark:text-gray-500 truncate max-w-[100px]" title="${m.model || 'unknown'}">${m.model || 'unknown'}</span>
|
|
1093
|
+
</div>
|
|
1094
|
+
</td>
|
|
814
1095
|
<td class="p-3"><span class="table-chip type-${m.type}">${formatTypeLabel(m.type)}</span></td>
|
|
815
1096
|
<td class="p-3">
|
|
816
1097
|
<div class="metric-badge ${getImportanceBadgeClass(m.importance)}">${m.importance}/5</div>
|
|
@@ -825,9 +1106,9 @@ function renderTable(memories) {
|
|
|
825
1106
|
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400">Updated ${formatDate(m.updated_at)}</div>
|
|
826
1107
|
</td>
|
|
827
1108
|
<td class="p-3 sticky-actions">
|
|
828
|
-
<div class="flex flex-wrap gap-
|
|
829
|
-
<button onclick="openDrawer('${m.id}')" class="
|
|
830
|
-
<button onclick="startInlineEdit('${m.id}')" class="
|
|
1109
|
+
<div class="flex flex-wrap gap-1.5">
|
|
1110
|
+
<button onclick="openDrawer('${m.id}')" class="btn-open">Open</button>
|
|
1111
|
+
<button onclick="startInlineEdit('${m.id}')" class="btn-edit-light">Edit</button>
|
|
831
1112
|
</div>
|
|
832
1113
|
</td>
|
|
833
1114
|
</tr>
|
|
@@ -910,11 +1191,11 @@ function getImportanceLabel(importance) {
|
|
|
910
1191
|
}
|
|
911
1192
|
|
|
912
1193
|
function getImportanceBadgeClass(importance) {
|
|
913
|
-
if (importance >= 5) return 'bg-red-
|
|
914
|
-
if (importance >= 4) return 'bg-orange-
|
|
915
|
-
if (importance >= 3) return 'bg-amber-
|
|
916
|
-
if (importance >= 2) return 'bg-sky-
|
|
917
|
-
return 'bg-
|
|
1194
|
+
if (importance >= 5) return 'bg-gradient-to-r from-red-500/10 to-rose-500/10 text-red-700 dark:text-red-300 border-red-400/30';
|
|
1195
|
+
if (importance >= 4) return 'bg-gradient-to-r from-orange-500/10 to-amber-500/10 text-orange-700 dark:text-orange-300 border-orange-400/30';
|
|
1196
|
+
if (importance >= 3) return 'bg-gradient-to-r from-amber-400/10 to-yellow-400/10 text-amber-700 dark:text-amber-300 border-amber-400/30';
|
|
1197
|
+
if (importance >= 2) return 'bg-gradient-to-r from-sky-400/10 to-blue-400/10 text-sky-700 dark:text-sky-300 border-sky-400/30';
|
|
1198
|
+
return 'bg-gradient-to-r from-slate-300/10 to-gray-300/10 text-slate-600 dark:text-slate-400 border-slate-300/30';
|
|
918
1199
|
}
|
|
919
1200
|
|
|
920
1201
|
function formatUsageCount(hitCount) {
|
|
@@ -994,27 +1275,34 @@ function renderDetailPanel(data) {
|
|
|
994
1275
|
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400 mb-2">Summary</div>
|
|
995
1276
|
<p class="text-sm leading-6 text-gray-700 dark:text-gray-300">${escapeHtml(getContentPreview(data))}</p>
|
|
996
1277
|
</div>
|
|
997
|
-
<div class="grid gap-4 md:grid-cols-
|
|
1278
|
+
<div class="grid gap-4 md:grid-cols-3">
|
|
998
1279
|
<div class="rounded-xl border border-gray-200 dark:border-gray-700 p-4">
|
|
999
1280
|
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400 mb-3">Memory Info</div>
|
|
1000
1281
|
<div class="space-y-2 text-sm">
|
|
1001
1282
|
<div><strong>Type:</strong> ${formatTypeLabel(data.type)}</div>
|
|
1002
|
-
<div><strong>ID:</strong> <span class="font-mono">${data.id}</span></div>
|
|
1003
|
-
<div><strong>
|
|
1004
|
-
<div><strong>Priority:</strong> ${data.importance}/5 (${getImportanceLabel(data.importance)})</div>
|
|
1283
|
+
<div><strong>ID:</strong> <span class="font-mono text-[10px]">${data.id}</span></div>
|
|
1284
|
+
<div><strong>Priority:</strong> ${data.importance}/5</div>
|
|
1005
1285
|
<div><strong>Status:</strong> <span class="capitalize px-1.5 py-0.5 rounded ${data.status === 'active' ? 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300' : 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-400'}">${data.status}</span></div>
|
|
1006
1286
|
</div>
|
|
1007
1287
|
</div>
|
|
1288
|
+
<div class="rounded-xl border border-gray-200 dark:border-gray-700 p-4">
|
|
1289
|
+
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400 mb-3">Source Info</div>
|
|
1290
|
+
<div class="space-y-2 text-sm">
|
|
1291
|
+
<div><strong>Agent:</strong> ${escapeHtml(data.agent || 'unknown')}</div>
|
|
1292
|
+
<div><strong>Model:</strong> ${escapeHtml(data.model || 'unknown')}</div>
|
|
1293
|
+
<div><strong>Repo:</strong> ${escapeHtml(data.scope?.repo || 'N/A')}</div>
|
|
1294
|
+
</div>
|
|
1295
|
+
</div>
|
|
1008
1296
|
<div class="rounded-xl border border-gray-200 dark:border-gray-700 p-4">
|
|
1009
1297
|
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400 mb-3">Usage</div>
|
|
1010
1298
|
<div class="space-y-2 text-sm">
|
|
1011
1299
|
<div><strong>Hit Count:</strong> ${data.hit_count || 0}</div>
|
|
1012
|
-
<div><strong>Recall Count:</strong> ${data.recall_count || 0}</div>
|
|
1013
1300
|
<div><strong>Recall Rate:</strong> ${formatRecallRate(data.recall_rate)}</div>
|
|
1014
|
-
<div><strong>Last Used:</strong> ${data.last_used_at ? new Date(data.last_used_at).
|
|
1301
|
+
<div><strong>Last Used:</strong> ${data.last_used_at ? new Date(data.last_used_at).toLocaleDateString() : 'Never'}</div>
|
|
1015
1302
|
</div>
|
|
1016
1303
|
</div>
|
|
1017
1304
|
</div>
|
|
1305
|
+
|
|
1018
1306
|
${data.supersedes ? `
|
|
1019
1307
|
<div class="rounded-xl border border-blue-200 dark:border-blue-900 bg-blue-50 dark:bg-blue-900/20 p-4">
|
|
1020
1308
|
<div class="text-xs uppercase tracking-wide text-blue-500 dark:text-blue-400 mb-2">Supersedes</div>
|
|
@@ -1032,9 +1320,15 @@ function renderDetailPanel(data) {
|
|
|
1032
1320
|
<div><strong>Expires:</strong> ${data.expires_at ? new Date(data.expires_at).toLocaleString() : 'Never'}</div>
|
|
1033
1321
|
</div>
|
|
1034
1322
|
</div>
|
|
1035
|
-
<div class="rounded-xl border border-gray-200 dark:border-gray-700
|
|
1036
|
-
<div class="
|
|
1037
|
-
|
|
1323
|
+
<div class="rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
1324
|
+
<div class="flex items-center justify-between px-4 py-2.5 bg-gray-50 dark:bg-gray-900/60 border-b border-gray-200 dark:border-gray-700">
|
|
1325
|
+
<div class="text-xs uppercase tracking-wide font-bold text-gray-500 dark:text-gray-400">Full Content</div>
|
|
1326
|
+
<span class="text-[10px] text-gray-400 dark:text-gray-500 flex items-center gap-1">
|
|
1327
|
+
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6 4.5-6 4.5z"/></svg>
|
|
1328
|
+
Markdown
|
|
1329
|
+
</span>
|
|
1330
|
+
</div>
|
|
1331
|
+
<div class="p-4 md:p-5 markdown-body">${renderMarkdown(data.content)}</div>
|
|
1038
1332
|
</div>
|
|
1039
1333
|
<div class="rounded-xl border border-gray-200 dark:border-gray-700 p-4">
|
|
1040
1334
|
<div class="flex items-center justify-between mb-3">
|
|
@@ -1083,6 +1377,16 @@ async function openDrawer(id) {
|
|
|
1083
1377
|
`;
|
|
1084
1378
|
document.getElementById('memoryDrawer').classList.remove('hidden');
|
|
1085
1379
|
document.body.classList.add('drawer-open');
|
|
1380
|
+
|
|
1381
|
+
// Trigger slide-in animation
|
|
1382
|
+
setTimeout(() => {
|
|
1383
|
+
const aside = document.getElementById('drawerAside');
|
|
1384
|
+
if (aside) {
|
|
1385
|
+
aside.classList.remove('translate-x-full');
|
|
1386
|
+
aside.classList.add('translate-x-0');
|
|
1387
|
+
}
|
|
1388
|
+
}, 10);
|
|
1389
|
+
|
|
1086
1390
|
const response = await fetch(`/api/memories/${id}?repo=${encodeURIComponent(currentRepo)}`);
|
|
1087
1391
|
const data = await response.json();
|
|
1088
1392
|
if (!response.ok) throw new Error(data.error || 'Failed to load memory');
|
|
@@ -1095,10 +1399,20 @@ async function openDrawer(id) {
|
|
|
1095
1399
|
}
|
|
1096
1400
|
|
|
1097
1401
|
function closeDrawer() {
|
|
1098
|
-
document.getElementById('
|
|
1099
|
-
if (
|
|
1100
|
-
|
|
1402
|
+
const aside = document.getElementById('drawerAside');
|
|
1403
|
+
if (aside) {
|
|
1404
|
+
aside.classList.remove('translate-x-0');
|
|
1405
|
+
aside.classList.add('translate-x-full');
|
|
1101
1406
|
}
|
|
1407
|
+
|
|
1408
|
+
// Delay hiding the container until animation completes
|
|
1409
|
+
setTimeout(() => {
|
|
1410
|
+
document.getElementById('memoryDrawer').classList.add('hidden');
|
|
1411
|
+
if (!document.getElementById('repoSidebarDrawer') || document.getElementById('repoSidebarDrawer').classList.contains('hidden')) {
|
|
1412
|
+
document.body.classList.remove('drawer-open');
|
|
1413
|
+
}
|
|
1414
|
+
}, 500);
|
|
1415
|
+
|
|
1102
1416
|
activeEditMemoryId = null;
|
|
1103
1417
|
currentDrawerMemoryId = null;
|
|
1104
1418
|
}
|
|
@@ -1265,25 +1579,157 @@ async function loadData() {
|
|
|
1265
1579
|
await Promise.all([
|
|
1266
1580
|
loadStats(),
|
|
1267
1581
|
loadMemories(),
|
|
1582
|
+
loadTasks(),
|
|
1268
1583
|
checkStatus(),
|
|
1269
1584
|
loadRecentActions(),
|
|
1270
1585
|
]);
|
|
1271
1586
|
}
|
|
1272
1587
|
|
|
1273
|
-
|
|
1588
|
+
let currentTasks = [];
|
|
1589
|
+
|
|
1590
|
+
async function loadTasks() {
|
|
1591
|
+
if (!currentRepo) return;
|
|
1592
|
+
try {
|
|
1593
|
+
const response = await fetch(`/api/tasks?repo=${encodeURIComponent(currentRepo)}`);
|
|
1594
|
+
const data = await response.json();
|
|
1595
|
+
currentTasks = data.tasks || [];
|
|
1596
|
+
renderTasks();
|
|
1597
|
+
} catch (err) {
|
|
1598
|
+
console.error('Failed to load tasks:', err);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
function renderTasks() {
|
|
1603
|
+
if (!document.getElementById('todoTasks')) return;
|
|
1604
|
+
|
|
1605
|
+
const todoTasks = currentTasks.filter(t => t.status === 'pending' || t.status === 'blocked');
|
|
1606
|
+
const inProgressTasks = currentTasks.filter(t => t.status === 'in_progress');
|
|
1607
|
+
const completedTasks = currentTasks.filter(t => t.status === 'completed');
|
|
1608
|
+
|
|
1609
|
+
document.getElementById('todoCount').textContent = todoTasks.length;
|
|
1610
|
+
document.getElementById('inProgressCount').textContent = inProgressTasks.length;
|
|
1611
|
+
document.getElementById('completedCount').textContent = completedTasks.length;
|
|
1612
|
+
|
|
1613
|
+
renderTaskColumn('todoTasks', todoTasks);
|
|
1614
|
+
renderTaskColumn('inProgressTasks', inProgressTasks);
|
|
1615
|
+
renderTaskColumn('completedTasks', completedTasks);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
function renderTaskColumn(id, tasks) {
|
|
1619
|
+
const container = document.getElementById(id);
|
|
1620
|
+
if (!container) return;
|
|
1621
|
+
|
|
1622
|
+
if (!tasks || tasks.length === 0) {
|
|
1623
|
+
container.innerHTML = '<div class="text-center py-8 text-gray-400 text-xs italic bg-gray-50/50 dark:bg-gray-900/20 rounded-xl border border-dashed border-gray-200 dark:border-gray-700">No tasks</div>';
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
container.innerHTML = tasks.map(t => `
|
|
1628
|
+
<div class="bg-white dark:bg-gray-700 p-4 rounded-xl shadow-sm border border-gray-100 dark:border-gray-600 hover:shadow-md transition-all group">
|
|
1629
|
+
<div class="flex items-center justify-between mb-2">
|
|
1630
|
+
<div class="flex items-center gap-2">
|
|
1631
|
+
<span class="px-1.5 py-0.5 rounded bg-gray-100 dark:bg-gray-800 text-[10px] font-bold text-gray-600 dark:text-gray-400 font-mono border border-gray-200 dark:border-gray-700">${t.task_code}</span>
|
|
1632
|
+
<span class="text-[10px] font-bold uppercase tracking-wider text-gray-400">${t.phase}</span>
|
|
1633
|
+
</div>
|
|
1634
|
+
<div class="flex items-center gap-1">
|
|
1635
|
+
${t.priority >= 4 ? '<span class="w-2 h-2 rounded-full bg-red-500 animate-pulse"></span>' : ''}
|
|
1636
|
+
<span class="text-[10px] font-bold ${getPriorityColor(t.priority)}">P${t.priority}</span>
|
|
1637
|
+
</div>
|
|
1638
|
+
</div>
|
|
1639
|
+
<h4 class="font-bold text-sm text-gray-900 dark:text-gray-100 mb-1">${escapeHtml(t.title)}</h4>
|
|
1640
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 line-clamp-2 mb-3">${escapeHtml(t.description || '')}</p>
|
|
1641
|
+
${t.depends_on ? `
|
|
1642
|
+
<div class="mt-2 pt-2 border-t border-gray-50 dark:border-gray-600 flex items-center gap-1.5">
|
|
1643
|
+
<svg class="w-3 h-3 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"></path></svg>
|
|
1644
|
+
<span class="text-[10px] font-medium text-amber-600 dark:text-amber-400">Depends on: ${t.depends_on.substring(0, 8)}</span>
|
|
1645
|
+
</div>
|
|
1646
|
+
` : ''}
|
|
1647
|
+
<div class="mt-3 flex items-center justify-between">
|
|
1648
|
+
<span class="text-[10px] font-mono text-gray-400">${t.id.substring(0, 8)}</span>
|
|
1649
|
+
<span class="text-[10px] text-gray-400">${formatDate(t.created_at)}</span>
|
|
1650
|
+
</div>
|
|
1651
|
+
</div>
|
|
1652
|
+
`).join('');
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
function getPriorityColor(p) {
|
|
1656
|
+
if (p >= 5) return 'text-red-600 dark:text-red-400';
|
|
1657
|
+
if (p >= 4) return 'text-orange-600 dark:text-orange-400';
|
|
1658
|
+
if (p >= 3) return 'text-amber-600 dark:text-amber-400';
|
|
1659
|
+
return 'text-gray-500 dark:text-gray-400';
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
let currentTab = localStorage.getItem('activeTab') || 'dashboard';
|
|
1663
|
+
|
|
1664
|
+
function switchTab(tab) {
|
|
1665
|
+
const dashTab = document.getElementById('dashboardTabBtn');
|
|
1666
|
+
const memTab = document.getElementById('memoriesTabBtn');
|
|
1667
|
+
const taskTab = document.getElementById('tasksTabBtn');
|
|
1668
|
+
const indicator = document.getElementById('tabIndicator');
|
|
1669
|
+
|
|
1670
|
+
const dashContent = document.getElementById('dashboardContent');
|
|
1671
|
+
const memContent = document.getElementById('memoriesContent');
|
|
1672
|
+
const taskContent = document.getElementById('tasksContent');
|
|
1673
|
+
|
|
1674
|
+
currentTab = tab;
|
|
1675
|
+
localStorage.setItem('activeTab', tab);
|
|
1676
|
+
|
|
1677
|
+
const tabs = [dashTab, memTab, taskTab];
|
|
1678
|
+
const contents = [dashContent, memContent, taskContent];
|
|
1679
|
+
const targetId = tab + 'TabBtn';
|
|
1680
|
+
const targetContentId = tab + 'Content';
|
|
1681
|
+
|
|
1682
|
+
// Update indicator
|
|
1683
|
+
if (indicator) {
|
|
1684
|
+
if (tab === 'dashboard') indicator.style.transform = 'translateX(0)';
|
|
1685
|
+
else if (tab === 'memories') indicator.style.transform = 'translateX(100%)';
|
|
1686
|
+
else if (tab === 'tasks') indicator.style.transform = 'translateX(200%)';
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
// Update button states
|
|
1690
|
+
tabs.forEach(t => {
|
|
1691
|
+
if (t) {
|
|
1692
|
+
if (t.id === targetId) {
|
|
1693
|
+
t.classList.add('text-white');
|
|
1694
|
+
t.classList.remove('text-gray-500', 'dark:text-gray-400');
|
|
1695
|
+
} else {
|
|
1696
|
+
t.classList.remove('text-white');
|
|
1697
|
+
t.classList.add('text-gray-500', 'dark:text-gray-400');
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
// Update content visibility
|
|
1703
|
+
contents.forEach(c => {
|
|
1704
|
+
if (c) {
|
|
1705
|
+
if (c.id === targetContentId) {
|
|
1706
|
+
c.classList.remove('hidden');
|
|
1707
|
+
} else {
|
|
1708
|
+
c.classList.add('hidden');
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
|
|
1713
|
+
// Trigger data loads if needed
|
|
1714
|
+
if (tab === 'tasks') loadTasks();
|
|
1715
|
+
if (tab === 'dashboard') loadStats();
|
|
1716
|
+
if (tab === 'memories') loadMemories();
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
window.loadTasks = loadTasks;
|
|
1720
|
+
window.switchTab = switchTab;
|
|
1721
|
+
window.charts = charts;
|
|
1722
|
+
|
|
1723
|
+
const safeAddEventListener = (id, event, handler) => {
|
|
1724
|
+
const el = document.getElementById(id);
|
|
1725
|
+
if (el) el.addEventListener(event, handler);
|
|
1726
|
+
};
|
|
1727
|
+
|
|
1728
|
+
safeAddEventListener('repoSearchInput', 'input', () => {
|
|
1274
1729
|
renderRepoSidebar();
|
|
1275
1730
|
});
|
|
1276
1731
|
|
|
1277
|
-
|
|
1278
|
-
window.closeRepoSidebarDrawer = closeRepoSidebarDrawer;
|
|
1279
|
-
window.togglePinnedRepo = togglePinnedRepo;
|
|
1280
|
-
window.startPinnedRepoDrag = startPinnedRepoDrag;
|
|
1281
|
-
window.overPinnedRepoDrag = overPinnedRepoDrag;
|
|
1282
|
-
window.leavePinnedRepoDrag = leavePinnedRepoDrag;
|
|
1283
|
-
window.dropPinnedRepo = dropPinnedRepo;
|
|
1284
|
-
window.endPinnedRepoDrag = endPinnedRepoDrag;
|
|
1285
|
-
|
|
1286
|
-
document.getElementById('repoSearchInput').addEventListener('keydown', (e) => {
|
|
1732
|
+
safeAddEventListener('repoSearchInput', 'keydown', (e) => {
|
|
1287
1733
|
if (e.key === 'Enter') {
|
|
1288
1734
|
const firstMatch = availableRepos.find((item) => item.repo.toLowerCase().includes(e.target.value.trim().toLowerCase()));
|
|
1289
1735
|
if (firstMatch) {
|
|
@@ -1292,11 +1738,11 @@ document.getElementById('repoSearchInput').addEventListener('keydown', (e) => {
|
|
|
1292
1738
|
}
|
|
1293
1739
|
});
|
|
1294
1740
|
|
|
1295
|
-
|
|
1741
|
+
safeAddEventListener('repoSearchInputMobile', 'input', () => {
|
|
1296
1742
|
renderRepoSidebar();
|
|
1297
1743
|
});
|
|
1298
1744
|
|
|
1299
|
-
|
|
1745
|
+
safeAddEventListener('repoSearchInputMobile', 'keydown', (e) => {
|
|
1300
1746
|
if (e.key === 'Enter') {
|
|
1301
1747
|
const firstMatch = availableRepos.find((item) => item.repo.toLowerCase().includes(e.target.value.trim().toLowerCase()));
|
|
1302
1748
|
if (firstMatch) {
|
|
@@ -1305,26 +1751,28 @@ document.getElementById('repoSearchInputMobile').addEventListener('keydown', (e)
|
|
|
1305
1751
|
}
|
|
1306
1752
|
});
|
|
1307
1753
|
|
|
1308
|
-
|
|
1754
|
+
safeAddEventListener('repoNavToggle', 'click', () => {
|
|
1309
1755
|
openRepoSidebarDrawer();
|
|
1310
1756
|
});
|
|
1311
1757
|
|
|
1312
|
-
|
|
1758
|
+
safeAddEventListener('repoSidebarCollapseToggle', 'click', () => {
|
|
1313
1759
|
toggleRepoSidebarCollapse();
|
|
1314
1760
|
});
|
|
1315
1761
|
|
|
1316
|
-
|
|
1762
|
+
safeAddEventListener('repoCollapsedSummaryButton', 'click', () => {
|
|
1317
1763
|
if (isRepoSidebarCollapsed) {
|
|
1318
1764
|
toggleRepoSidebarCollapse();
|
|
1319
1765
|
}
|
|
1320
1766
|
});
|
|
1321
1767
|
|
|
1768
|
+
window.addEventListener('resize', syncStickyOffsets);
|
|
1769
|
+
|
|
1322
1770
|
initTheme();
|
|
1323
1771
|
initRepoSidebarState();
|
|
1324
1772
|
initPinnedRepos();
|
|
1325
1773
|
renderRecentActions();
|
|
1326
1774
|
loadData();
|
|
1775
|
+
switchTab(currentTab);
|
|
1776
|
+
syncStickyOffsets();
|
|
1327
1777
|
startCountdown();
|
|
1328
1778
|
setInterval(checkStatus, 30000);
|
|
1329
|
-
|
|
1330
|
-
|