superlocalmemory 2.4.2 → 2.5.1

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.
@@ -0,0 +1,59 @@
1
+ // SuperLocalMemory V2 - Search
2
+ // Depends on: core.js, memories.js (renderMemoriesTable, loadMemories)
3
+
4
+ var lastSearchResults = null;
5
+
6
+ async function searchMemories() {
7
+ var query = document.getElementById('search-query').value;
8
+ if (!query.trim()) { loadMemories(); return; }
9
+
10
+ showLoading('memories-list', 'Searching...');
11
+ try {
12
+ var response = await fetch('/api/search', {
13
+ method: 'POST',
14
+ headers: { 'Content-Type': 'application/json' },
15
+ body: JSON.stringify({ query: query, limit: 20, min_score: 0.3 })
16
+ });
17
+ var data = await response.json();
18
+
19
+ var results = data.results || [];
20
+ results.sort(function(a, b) { return (b.score || 0) - (a.score || 0); });
21
+
22
+ lastSearchResults = results;
23
+
24
+ var exportBtn = document.getElementById('export-search-btn');
25
+ if (exportBtn) exportBtn.style.display = results.length > 0 ? '' : 'none';
26
+
27
+ renderMemoriesTable(results, true);
28
+ } catch (error) {
29
+ console.error('Error searching:', error);
30
+ showEmpty('memories-list', 'exclamation-triangle', 'Search failed. Please try again.');
31
+ }
32
+ }
33
+
34
+ // ============================================================================
35
+ // Export All / Search Results
36
+ // ============================================================================
37
+
38
+ function exportAll(format) {
39
+ var url = '/api/export?format=' + encodeURIComponent(format);
40
+ var category = document.getElementById('filter-category').value;
41
+ var project = document.getElementById('filter-project').value;
42
+ if (category) url += '&category=' + encodeURIComponent(category);
43
+ if (project) url += '&project_name=' + encodeURIComponent(project);
44
+ window.location.href = url;
45
+ }
46
+
47
+ function exportSearchResults() {
48
+ if (!lastSearchResults || lastSearchResults.length === 0) {
49
+ showToast('No search results to export');
50
+ return;
51
+ }
52
+ var content = JSON.stringify({
53
+ exported_at: new Date().toISOString(),
54
+ query: document.getElementById('search-query').value,
55
+ total: lastSearchResults.length,
56
+ results: lastSearchResults
57
+ }, null, 2);
58
+ downloadFile('search-results-' + Date.now() + '.json', content, 'application/json');
59
+ }
@@ -0,0 +1,167 @@
1
+ // SuperLocalMemory V2 - Settings & Backup
2
+ // Depends on: core.js, profiles.js (loadProfilesTable)
3
+
4
+ async function loadSettings() {
5
+ loadProfilesTable();
6
+ loadBackupStatus();
7
+ loadBackupList();
8
+ }
9
+
10
+ async function loadBackupStatus() {
11
+ try {
12
+ var response = await fetch('/api/backup/status');
13
+ var data = await response.json();
14
+ renderBackupStatus(data);
15
+ document.getElementById('backup-interval').value = data.interval_hours <= 24 ? '24' : '168';
16
+ document.getElementById('backup-max').value = data.max_backups || 10;
17
+ document.getElementById('backup-enabled').checked = data.enabled !== false;
18
+ } catch (error) {
19
+ var container = document.getElementById('backup-status');
20
+ var alert = document.createElement('div');
21
+ alert.className = 'alert alert-warning mb-0';
22
+ alert.textContent = 'Auto-backup not available. Update to v2.4.0+.';
23
+ container.textContent = '';
24
+ container.appendChild(alert);
25
+ }
26
+ }
27
+
28
+ function renderBackupStatus(data) {
29
+ var container = document.getElementById('backup-status');
30
+ container.textContent = '';
31
+
32
+ var lastBackup = data.last_backup ? formatDateFull(data.last_backup) : 'Never';
33
+ var nextBackup = data.next_backup || 'N/A';
34
+ if (nextBackup === 'overdue') nextBackup = 'Overdue';
35
+ else if (nextBackup !== 'N/A' && nextBackup !== 'unknown') nextBackup = formatDateFull(nextBackup);
36
+
37
+ var statusColor = data.enabled ? 'text-success' : 'text-secondary';
38
+ var statusText = data.enabled ? 'Active' : 'Disabled';
39
+
40
+ var row = document.createElement('div');
41
+ row.className = 'row g-2 mb-2';
42
+
43
+ var stats = [
44
+ { value: statusText, label: 'Status', cls: statusColor },
45
+ { value: String(data.backup_count || 0), label: 'Backups', cls: '' },
46
+ { value: (data.total_size_mb || 0) + ' MB', label: 'Storage', cls: '' }
47
+ ];
48
+
49
+ stats.forEach(function(s) {
50
+ var col = document.createElement('div');
51
+ col.className = 'col-4';
52
+ var stat = document.createElement('div');
53
+ stat.className = 'backup-stat';
54
+ var val = document.createElement('div');
55
+ val.className = 'value ' + s.cls;
56
+ val.textContent = s.value;
57
+ var lbl = document.createElement('div');
58
+ lbl.className = 'label';
59
+ lbl.textContent = s.label;
60
+ stat.appendChild(val);
61
+ stat.appendChild(lbl);
62
+ col.appendChild(stat);
63
+ row.appendChild(col);
64
+ });
65
+ container.appendChild(row);
66
+
67
+ var details = [
68
+ { label: 'Last backup:', value: lastBackup },
69
+ { label: 'Next backup:', value: nextBackup },
70
+ { label: 'Interval:', value: data.interval_display || '-' }
71
+ ];
72
+ details.forEach(function(d) {
73
+ var div = document.createElement('div');
74
+ div.className = 'small text-muted';
75
+ var strong = document.createElement('strong');
76
+ strong.textContent = d.label + ' ';
77
+ div.appendChild(strong);
78
+ div.appendChild(document.createTextNode(d.value));
79
+ container.appendChild(div);
80
+ });
81
+ }
82
+
83
+ async function saveBackupConfig() {
84
+ try {
85
+ var response = await fetch('/api/backup/configure', {
86
+ method: 'POST',
87
+ headers: { 'Content-Type': 'application/json' },
88
+ body: JSON.stringify({
89
+ interval_hours: parseInt(document.getElementById('backup-interval').value),
90
+ max_backups: parseInt(document.getElementById('backup-max').value),
91
+ enabled: document.getElementById('backup-enabled').checked
92
+ })
93
+ });
94
+ var data = await response.json();
95
+ renderBackupStatus(data);
96
+ showToast('Backup settings saved');
97
+ } catch (error) {
98
+ console.error('Error saving backup config:', error);
99
+ showToast('Failed to save backup settings');
100
+ }
101
+ }
102
+
103
+ async function createBackupNow() {
104
+ showToast('Creating backup...');
105
+ try {
106
+ var response = await fetch('/api/backup/create', { method: 'POST' });
107
+ var data = await response.json();
108
+ if (data.success) {
109
+ showToast('Backup created: ' + data.filename);
110
+ loadBackupStatus();
111
+ loadBackupList();
112
+ } else {
113
+ showToast('Backup failed');
114
+ }
115
+ } catch (error) {
116
+ console.error('Error creating backup:', error);
117
+ showToast('Backup failed');
118
+ }
119
+ }
120
+
121
+ async function loadBackupList() {
122
+ try {
123
+ var response = await fetch('/api/backup/list');
124
+ var data = await response.json();
125
+ renderBackupList(data.backups || []);
126
+ } catch (error) {
127
+ var container = document.getElementById('backup-list');
128
+ container.textContent = 'Backup list unavailable';
129
+ }
130
+ }
131
+
132
+ function renderBackupList(backups) {
133
+ var container = document.getElementById('backup-list');
134
+ if (!backups || backups.length === 0) {
135
+ showEmpty('backup-list', 'archive', 'No backups yet. Create your first backup above.');
136
+ return;
137
+ }
138
+
139
+ var table = document.createElement('table');
140
+ table.className = 'table table-sm';
141
+ var thead = document.createElement('thead');
142
+ var headRow = document.createElement('tr');
143
+ ['Filename', 'Size', 'Age', 'Created'].forEach(function(h) {
144
+ var th = document.createElement('th');
145
+ th.textContent = h;
146
+ headRow.appendChild(th);
147
+ });
148
+ thead.appendChild(headRow);
149
+ table.appendChild(thead);
150
+
151
+ var tbody = document.createElement('tbody');
152
+ backups.forEach(function(b) {
153
+ var row = document.createElement('tr');
154
+ var age = b.age_hours < 48 ? Math.round(b.age_hours) + 'h ago' : Math.round(b.age_hours / 24) + 'd ago';
155
+ var cells = [b.filename, b.size_mb + ' MB', age, formatDateFull(b.created)];
156
+ cells.forEach(function(text) {
157
+ var td = document.createElement('td');
158
+ td.textContent = text;
159
+ row.appendChild(td);
160
+ });
161
+ tbody.appendChild(row);
162
+ });
163
+ table.appendChild(tbody);
164
+
165
+ container.textContent = '';
166
+ container.appendChild(table);
167
+ }
@@ -0,0 +1,32 @@
1
+ // SuperLocalMemory V2 - Timeline View (D3.js bar chart)
2
+ // Depends on: core.js
3
+
4
+ async function loadTimeline() {
5
+ showLoading('timeline-chart', 'Loading timeline...');
6
+ try {
7
+ var response = await fetch('/api/timeline?days=30');
8
+ var data = await response.json();
9
+ renderTimeline(data.timeline);
10
+ } catch (error) {
11
+ console.error('Error loading timeline:', error);
12
+ showEmpty('timeline-chart', 'clock-history', 'Failed to load timeline');
13
+ }
14
+ }
15
+
16
+ function renderTimeline(timeline) {
17
+ var container = document.getElementById('timeline-chart');
18
+ if (!timeline || timeline.length === 0) {
19
+ showEmpty('timeline-chart', 'clock-history', 'No timeline data for the last 30 days.');
20
+ return;
21
+ }
22
+ var margin = { top: 20, right: 20, bottom: 50, left: 50 };
23
+ var width = container.clientWidth - margin.left - margin.right;
24
+ var height = 300 - margin.top - margin.bottom;
25
+ container.textContent = '';
26
+ var svg = d3.select('#timeline-chart').append('svg').attr('width', width + margin.left + margin.right).attr('height', height + margin.top + margin.bottom).append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
27
+ var x = d3.scaleBand().range([0, width]).domain(timeline.map(function(d) { return d.date || d.period; })).padding(0.1);
28
+ var y = d3.scaleLinear().range([height, 0]).domain([0, d3.max(timeline, function(d) { return d.count; })]);
29
+ svg.append('g').attr('transform', 'translate(0,' + height + ')').call(d3.axisBottom(x)).selectAll('text').attr('transform', 'rotate(-45)').style('text-anchor', 'end');
30
+ svg.append('g').call(d3.axisLeft(y));
31
+ svg.selectAll('.bar').data(timeline).enter().append('rect').attr('class', 'bar').attr('x', function(d) { return x(d.date || d.period); }).attr('y', function(d) { return y(d.count); }).attr('width', x.bandwidth()).attr('height', function(d) { return height - y(d.count); }).attr('fill', '#667eea').attr('rx', 3);
32
+ }