superlocalmemory 3.4.17 → 3.4.19

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.
Files changed (80) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +1 -3
  3. package/pyproject.toml +10 -1
  4. package/src/superlocalmemory/cli/setup_wizard.py +30 -0
  5. package/src/superlocalmemory/core/embeddings.py +8 -2
  6. package/src/superlocalmemory/retrieval/reranker.py +4 -2
  7. package/src/superlocalmemory.egg-info/PKG-INFO +4 -1
  8. package/src/superlocalmemory.egg-info/requires.txt +3 -0
  9. package/docs/ARCHITECTURE.md +0 -149
  10. package/docs/api-reference.md +0 -284
  11. package/docs/auto-memory.md +0 -150
  12. package/docs/cli-reference.md +0 -327
  13. package/docs/cloud-backup.md +0 -174
  14. package/docs/compliance.md +0 -191
  15. package/docs/configuration.md +0 -182
  16. package/docs/getting-started.md +0 -102
  17. package/docs/ide-setup.md +0 -261
  18. package/docs/mcp-tools.md +0 -220
  19. package/docs/migration-from-v2.md +0 -170
  20. package/docs/profiles.md +0 -173
  21. package/docs/screenshots/01-dashboard-main.png +0 -0
  22. package/docs/screenshots/02-knowledge-graph.png +0 -0
  23. package/docs/screenshots/03-math-health.png +0 -0
  24. package/docs/screenshots/03-patterns-learning.png +0 -0
  25. package/docs/screenshots/04-learning-dashboard.png +0 -0
  26. package/docs/screenshots/04-recall-lab.png +0 -0
  27. package/docs/screenshots/05-behavioral-analysis.png +0 -0
  28. package/docs/screenshots/05-trust-dashboard.png +0 -0
  29. package/docs/screenshots/06-graph-communities.png +0 -0
  30. package/docs/screenshots/06-settings.png +0 -0
  31. package/docs/screenshots/07-memories-blurred.png +0 -0
  32. package/docs/skill-evolution.md +0 -256
  33. package/docs/troubleshooting.md +0 -310
  34. package/docs/v2-archive/ACCESSIBILITY.md +0 -291
  35. package/docs/v2-archive/ARCHITECTURE.md +0 -886
  36. package/docs/v2-archive/CLI-COMMANDS-REFERENCE.md +0 -425
  37. package/docs/v2-archive/COMPRESSION-README.md +0 -390
  38. package/docs/v2-archive/FRAMEWORK-INTEGRATIONS.md +0 -300
  39. package/docs/v2-archive/MCP-MANUAL-SETUP.md +0 -775
  40. package/docs/v2-archive/MCP-TROUBLESHOOTING.md +0 -787
  41. package/docs/v2-archive/PATTERN-LEARNING.md +0 -228
  42. package/docs/v2-archive/PROFILES-GUIDE.md +0 -453
  43. package/docs/v2-archive/RESET-GUIDE.md +0 -353
  44. package/docs/v2-archive/SEARCH-ENGINE-V2.2.0.md +0 -749
  45. package/docs/v2-archive/SEARCH-INTEGRATION-GUIDE.md +0 -502
  46. package/docs/v2-archive/UI-SERVER.md +0 -262
  47. package/docs/v2-archive/UNIVERSAL-INTEGRATION.md +0 -488
  48. package/docs/v2-archive/V2.2.0-OPTIONAL-SEARCH.md +0 -666
  49. package/docs/v2-archive/WINDOWS-INSTALL-README.txt +0 -34
  50. package/docs/v2-archive/WINDOWS-POST-INSTALL.txt +0 -45
  51. package/docs/v2-archive/example_graph_usage.py +0 -146
  52. package/ui/index.html +0 -1879
  53. package/ui/js/agents.js +0 -192
  54. package/ui/js/auto-settings.js +0 -399
  55. package/ui/js/behavioral.js +0 -276
  56. package/ui/js/clusters.js +0 -206
  57. package/ui/js/compliance.js +0 -252
  58. package/ui/js/core.js +0 -246
  59. package/ui/js/dashboard.js +0 -110
  60. package/ui/js/events.js +0 -178
  61. package/ui/js/fact-detail.js +0 -92
  62. package/ui/js/feedback.js +0 -333
  63. package/ui/js/graph-core.js +0 -447
  64. package/ui/js/graph-filters.js +0 -220
  65. package/ui/js/graph-interactions.js +0 -351
  66. package/ui/js/graph-ui.js +0 -214
  67. package/ui/js/ide-status.js +0 -102
  68. package/ui/js/init.js +0 -45
  69. package/ui/js/learning.js +0 -435
  70. package/ui/js/lifecycle.js +0 -298
  71. package/ui/js/math-health.js +0 -98
  72. package/ui/js/memories.js +0 -264
  73. package/ui/js/modal.js +0 -357
  74. package/ui/js/patterns.js +0 -93
  75. package/ui/js/profiles.js +0 -236
  76. package/ui/js/recall-lab.js +0 -292
  77. package/ui/js/search.js +0 -59
  78. package/ui/js/settings.js +0 -224
  79. package/ui/js/timeline.js +0 -32
  80. package/ui/js/trust-dashboard.js +0 -73
package/ui/js/agents.js DELETED
@@ -1,192 +0,0 @@
1
- // SuperLocalMemory V2 - Connected Agents + Trust Overview (v2.5)
2
- // Depends on: core.js
3
- // Security: All DOM built with safe methods (createElement/textContent).
4
-
5
- async function loadAgents() {
6
- try {
7
- var response = await fetch('/api/agents');
8
- var data = await response.json();
9
- var agents = data.agents || [];
10
- var stats = data.stats || {};
11
-
12
- var el;
13
- el = document.getElementById('agent-stat-total');
14
- if (el) el.textContent = (stats.total_agents || 0).toLocaleString();
15
- el = document.getElementById('agent-stat-active');
16
- if (el) el.textContent = (stats.active_last_24h || 0).toLocaleString();
17
- el = document.getElementById('agent-stat-writes');
18
- if (el) el.textContent = (stats.total_writes || 0).toLocaleString();
19
- el = document.getElementById('agent-stat-recalls');
20
- if (el) el.textContent = (stats.total_recalls || 0).toLocaleString();
21
-
22
- var container = document.getElementById('agents-list');
23
- if (!container) return;
24
-
25
- if (agents.length === 0) {
26
- container.textContent = '';
27
- var empty = document.createElement('div');
28
- empty.className = 'text-muted text-center py-4';
29
- var emptyIcon = document.createElement('i');
30
- emptyIcon.className = 'bi bi-robot';
31
- emptyIcon.style.fontSize = '2rem';
32
- empty.appendChild(emptyIcon);
33
- var emptyText = document.createElement('p');
34
- emptyText.className = 'mt-2';
35
- emptyText.textContent = 'No agents registered yet. Agents appear automatically when they connect via MCP, CLI, or REST.';
36
- empty.appendChild(emptyText);
37
- container.appendChild(empty);
38
- loadTrustOverview();
39
- return;
40
- }
41
-
42
- var table = document.createElement('table');
43
- table.className = 'table table-hover table-sm';
44
- var thead = document.createElement('thead');
45
- var headerRow = document.createElement('tr');
46
- ['Agent', 'Protocol', 'Trust', 'Writes', 'Recalls', 'Last Seen'].forEach(function(h) {
47
- var th = document.createElement('th');
48
- th.textContent = h;
49
- headerRow.appendChild(th);
50
- });
51
- thead.appendChild(headerRow);
52
- table.appendChild(thead);
53
-
54
- var tbody = document.createElement('tbody');
55
- agents.forEach(function(agent) {
56
- var tr = document.createElement('tr');
57
-
58
- var tdName = document.createElement('td');
59
- var strong = document.createElement('strong');
60
- strong.textContent = agent.agent_name || agent.agent_id;
61
- tdName.appendChild(strong);
62
- tdName.appendChild(document.createElement('br'));
63
- var smallId = document.createElement('small');
64
- smallId.className = 'text-muted';
65
- smallId.textContent = agent.agent_id;
66
- tdName.appendChild(smallId);
67
- tr.appendChild(tdName);
68
-
69
- var tdProto = document.createElement('td');
70
- var protoBadge = document.createElement('span');
71
- var protocolColors = {
72
- 'mcp': 'bg-primary', 'cli': 'bg-success', 'rest': 'bg-info',
73
- 'python': 'bg-secondary'
74
- };
75
- protoBadge.className = 'badge ' + (protocolColors[agent.protocol] || 'bg-secondary');
76
- protoBadge.textContent = agent.protocol;
77
- tdProto.appendChild(protoBadge);
78
- tr.appendChild(tdProto);
79
-
80
- var tdTrust = document.createElement('td');
81
- var trustScore = agent.trust_score != null ? agent.trust_score : 0.667;
82
- tdTrust.className = trustScore < 0.3 ? 'text-danger fw-bold'
83
- : trustScore < 0.5 ? 'text-warning fw-bold' : 'text-success fw-bold';
84
- tdTrust.textContent = trustScore.toFixed(2);
85
- tr.appendChild(tdTrust);
86
-
87
- var tdW = document.createElement('td');
88
- tdW.textContent = agent.memories_written || 0;
89
- tr.appendChild(tdW);
90
-
91
- var tdR = document.createElement('td');
92
- tdR.textContent = agent.memories_recalled || 0;
93
- tr.appendChild(tdR);
94
-
95
- var tdLast = document.createElement('td');
96
- var lastSmall = document.createElement('small');
97
- lastSmall.textContent = agent.last_seen ? new Date(agent.last_seen).toLocaleString() : 'Never';
98
- tdLast.appendChild(lastSmall);
99
- tr.appendChild(tdLast);
100
-
101
- tbody.appendChild(tr);
102
- });
103
- table.appendChild(tbody);
104
-
105
- container.textContent = '';
106
- container.appendChild(table);
107
-
108
- loadTrustOverview();
109
-
110
- } catch (err) {
111
- console.log('Agents not available:', err);
112
- var container = document.getElementById('agents-list');
113
- if (container) {
114
- container.textContent = '';
115
- var msg = document.createElement('small');
116
- msg.className = 'text-muted';
117
- msg.textContent = 'Agent registry not available. This feature requires v2.5+.';
118
- container.appendChild(msg);
119
- }
120
- }
121
- }
122
-
123
- async function loadTrustOverview() {
124
- try {
125
- var response = await fetch('/api/trust/stats');
126
- var stats = await response.json();
127
- var container = document.getElementById('trust-overview');
128
- if (!container) return;
129
-
130
- container.textContent = '';
131
- var row = document.createElement('div');
132
- row.className = 'row g-3';
133
-
134
- var cardData = [
135
- { value: (stats.total_signals || 0).toLocaleString(), label: 'Total Signals Collected', cls: '' },
136
- { value: (stats.avg_trust_score || 0.667).toFixed(3), label: 'Average Trust Score', cls: '' },
137
- { value: stats.enforcement || 'disabled', label: 'Enforcement Status', cls: 'text-info' }
138
- ];
139
-
140
- cardData.forEach(function(c) {
141
- var col = document.createElement('div');
142
- col.className = 'col-md-4';
143
- var card = document.createElement('div');
144
- card.className = 'border rounded p-3 text-center';
145
- var val = document.createElement('div');
146
- val.className = 'fs-4 fw-bold ' + c.cls;
147
- val.textContent = c.value;
148
- card.appendChild(val);
149
- var lbl = document.createElement('small');
150
- lbl.className = 'text-muted';
151
- lbl.textContent = c.label;
152
- card.appendChild(lbl);
153
- col.appendChild(card);
154
- row.appendChild(col);
155
- });
156
-
157
- container.appendChild(row);
158
-
159
- if (stats.by_signal_type && Object.keys(stats.by_signal_type).length > 0) {
160
- var breakdownDiv = document.createElement('div');
161
- breakdownDiv.className = 'col-12 mt-3';
162
- var h6 = document.createElement('h6');
163
- h6.textContent = 'Signal Breakdown';
164
- breakdownDiv.appendChild(h6);
165
- var badgeWrap = document.createElement('div');
166
- badgeWrap.className = 'd-flex flex-wrap gap-2';
167
- Object.keys(stats.by_signal_type).forEach(function(type) {
168
- var count = stats.by_signal_type[type];
169
- var signalClass = (type.indexOf('high_volume') >= 0 || type.indexOf('quick_delete') >= 0)
170
- ? 'bg-danger' : (type.indexOf('recalled') >= 0 || type.indexOf('high_importance') >= 0)
171
- ? 'bg-success' : 'bg-secondary';
172
- var b = document.createElement('span');
173
- b.className = 'badge ' + signalClass;
174
- b.textContent = type + ': ' + count;
175
- badgeWrap.appendChild(b);
176
- });
177
- breakdownDiv.appendChild(badgeWrap);
178
- container.appendChild(breakdownDiv);
179
- }
180
-
181
- } catch (err) {
182
- console.log('Trust stats not available:', err);
183
- var container = document.getElementById('trust-overview');
184
- if (container) {
185
- container.textContent = '';
186
- var msg = document.createElement('small');
187
- msg.className = 'text-muted';
188
- msg.textContent = 'Trust scoring data will appear here once agents interact with memory.';
189
- container.appendChild(msg);
190
- }
191
- }
192
- }
@@ -1,399 +0,0 @@
1
- // SuperLocalMemory V3 — Auto-Capture/Recall Settings
2
- // Wires the auto-capture and auto-recall toggle switches to the V3 API.
3
-
4
- async function loadAutoSettings() {
5
- try {
6
- var captureResp = await fetch('/api/v3/auto-capture/config');
7
- var recallResp = await fetch('/api/v3/auto-recall/config');
8
- var capture = captureResp.ok ? await captureResp.json() : {};
9
- var recall = recallResp.ok ? await recallResp.json() : {};
10
-
11
- var cc = capture.config || {};
12
- var rc = recall.config || {};
13
-
14
- var el;
15
- el = document.getElementById('auto-capture-toggle');
16
- if (el) el.checked = cc.enabled !== false;
17
- el = document.getElementById('auto-capture-decisions');
18
- if (el) el.checked = cc.capture_decisions !== false;
19
- el = document.getElementById('auto-capture-bugs');
20
- if (el) el.checked = cc.capture_bugs !== false;
21
- el = document.getElementById('auto-recall-toggle');
22
- if (el) el.checked = rc.enabled !== false;
23
- el = document.getElementById('auto-recall-session');
24
- if (el) el.checked = rc.on_session_start !== false;
25
- } catch (e) {
26
- console.log('Auto settings load error:', e);
27
- }
28
- }
29
-
30
- function saveAutoCaptureConfig() {
31
- var payload = {
32
- enabled: document.getElementById('auto-capture-toggle')?.checked,
33
- capture_decisions: document.getElementById('auto-capture-decisions')?.checked,
34
- capture_bugs: document.getElementById('auto-capture-bugs')?.checked
35
- };
36
- fetch('/api/v3/auto-capture/config', {
37
- method: 'PUT',
38
- headers: { 'Content-Type': 'application/json' },
39
- body: JSON.stringify(payload)
40
- }).catch(function(e) { console.log('Save auto-capture error:', e); });
41
- }
42
-
43
- function saveAutoRecallConfig() {
44
- var payload = {
45
- enabled: document.getElementById('auto-recall-toggle')?.checked,
46
- on_session_start: document.getElementById('auto-recall-session')?.checked
47
- };
48
- fetch('/api/v3/auto-recall/config', {
49
- method: 'PUT',
50
- headers: { 'Content-Type': 'application/json' },
51
- body: JSON.stringify(payload)
52
- }).catch(function(e) { console.log('Save auto-recall error:', e); });
53
- }
54
-
55
- // Bind change listeners for auto-capture toggles
56
- document.querySelectorAll('#auto-capture-toggle, #auto-capture-decisions, #auto-capture-bugs').forEach(function(el) {
57
- if (el) {
58
- el.addEventListener('change', saveAutoCaptureConfig);
59
- }
60
- });
61
-
62
- // Bind change listeners for auto-recall toggles
63
- document.querySelectorAll('#auto-recall-toggle, #auto-recall-session').forEach(function(el) {
64
- if (el) {
65
- el.addEventListener('change', saveAutoRecallConfig);
66
- }
67
- });
68
-
69
- // ============================================================================
70
- // Mode / Provider / Model Configuration (Professional Settings)
71
- // ============================================================================
72
-
73
- var PROVIDER_CONFIG = {
74
- 'ollama': {
75
- name: 'Ollama',
76
- needsKey: false,
77
- endpoint: 'http://localhost:11434',
78
- endpointEditable: true,
79
- detectModels: true, // auto-detect via /api/v3/ollama/status
80
- },
81
- 'openrouter': {
82
- name: 'OpenRouter',
83
- needsKey: true,
84
- endpoint: 'https://openrouter.ai/api/v1',
85
- endpointEditable: false,
86
- },
87
- 'openai': {
88
- name: 'OpenAI',
89
- needsKey: true,
90
- endpoint: 'https://api.openai.com/v1',
91
- endpointEditable: true, // editable for Azure OpenAI
92
- },
93
- 'anthropic': {
94
- name: 'Anthropic',
95
- needsKey: true,
96
- endpoint: 'https://api.anthropic.com',
97
- endpointEditable: false,
98
- },
99
- };
100
-
101
- var MODEL_OPTIONS = {
102
- 'none': [],
103
- 'ollama': [
104
- {value: 'llama3.1:8b', label: 'Llama 3.1 8B'},
105
- {value: 'llama3.2:latest', label: 'Llama 3.2'},
106
- {value: 'qwen3-vl:8b', label: 'Qwen3 VL 8B'},
107
- {value: 'mistral:latest', label: 'Mistral'},
108
- ],
109
- 'openrouter': [
110
- {value: 'meta-llama/llama-3.1-8b-instruct:free', label: 'Llama 3.1 8B (Free)'},
111
- {value: 'google/gemini-2.0-flash-001', label: 'Gemini 2.0 Flash'},
112
- {value: 'anthropic/claude-3.5-haiku', label: 'Claude 3.5 Haiku'},
113
- {value: 'openai/gpt-4o-mini', label: 'GPT-4o Mini'},
114
- {value: 'deepseek/deepseek-chat-v3-0324:free', label: 'DeepSeek V3 (Free)'},
115
- ],
116
- 'openai': [
117
- {value: 'gpt-4o-mini', label: 'GPT-4o Mini'},
118
- {value: 'gpt-4o', label: 'GPT-4o'},
119
- {value: 'gpt-4-turbo', label: 'GPT-4 Turbo'},
120
- ],
121
- 'anthropic': [
122
- {value: 'claude-3-5-haiku-latest', label: 'Claude 3.5 Haiku'},
123
- {value: 'claude-3-5-sonnet-latest', label: 'Claude 3.5 Sonnet'},
124
- {value: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6'},
125
- ],
126
- };
127
-
128
- async function loadModeSettings() {
129
- try {
130
- var resp = await fetch('/api/v3/mode');
131
- if (!resp.ok) return;
132
- var data = await resp.json();
133
- var mode = data.mode || 'a';
134
- var provider = data.provider || 'none';
135
- var model = data.model || '';
136
-
137
- // Set radio button
138
- var radio = document.getElementById('mode-' + mode + '-radio');
139
- if (radio) radio.checked = true;
140
-
141
- // Set provider dropdown
142
- var provEl = document.getElementById('settings-provider');
143
- if (provEl && provider !== 'none') provEl.value = provider;
144
-
145
- // Update banner
146
- var modeNames = {a: 'Mode A — Local Guardian', b: 'Mode B — Smart Local', c: 'Mode C — Full Power'};
147
- var bannerMode = document.getElementById('settings-current-mode');
148
- if (bannerMode) {
149
- var label = modeNames[mode] || mode;
150
- if (provider && provider !== 'none') label += ' | ' + provider;
151
- if (model) label += ' | ' + model;
152
- bannerMode.textContent = label;
153
- }
154
-
155
- var bannerDetail = document.getElementById('settings-current-detail');
156
- if (bannerDetail) {
157
- if (mode === 'a') bannerDetail.textContent = 'Zero cloud — EU AI Act compliant';
158
- else if (data.has_key) bannerDetail.textContent = 'API key configured';
159
- else if (provider === 'ollama') bannerDetail.textContent = 'No API key needed';
160
- else bannerDetail.textContent = 'API key not set';
161
- }
162
-
163
- var banner = document.getElementById('settings-current-banner');
164
- if (banner) {
165
- banner.className = mode === 'a' ? 'alert alert-success mb-3' :
166
- mode === 'b' ? 'alert alert-info mb-3' :
167
- 'alert alert-warning mb-3';
168
- }
169
-
170
- // Show provider panel and populate model dropdown
171
- updateModeUI();
172
-
173
- // After provider UI updates, set the saved model value
174
- if (model) {
175
- setTimeout(function() {
176
- var modelEl = document.getElementById('settings-model');
177
- if (modelEl) {
178
- // Check if option exists, if not add it
179
- var found = false;
180
- for (var i = 0; i < modelEl.options.length; i++) {
181
- if (modelEl.options[i].value === model) { found = true; break; }
182
- }
183
- if (!found) {
184
- var opt = document.createElement('option');
185
- opt.value = model;
186
- opt.textContent = model + ' (current)';
187
- modelEl.insertBefore(opt, modelEl.firstChild);
188
- }
189
- modelEl.value = model;
190
- }
191
- }, 500);
192
- }
193
- } catch (e) {
194
- console.log('Load mode settings error:', e);
195
- }
196
- }
197
-
198
- function updateModeUI() {
199
- var mode = document.querySelector('input[name="settings-mode-radio"]:checked')?.value || 'a';
200
- var panel = document.getElementById('settings-provider-panel');
201
- if (panel) {
202
- panel.style.display = (mode === 'a') ? 'none' : 'block';
203
- }
204
- // Only set provider if it's currently empty (first load or Mode A→B/C)
205
- var providerEl = document.getElementById('settings-provider');
206
- if (providerEl && !providerEl.value) {
207
- if (mode === 'b') providerEl.value = 'ollama';
208
- }
209
- if (mode !== 'a') updateProviderUI();
210
- }
211
-
212
- function updateProviderUI() {
213
- var provider = document.getElementById('settings-provider')?.value || 'none';
214
- var modelSelect = document.getElementById('settings-model');
215
- var modelHint = document.getElementById('settings-model-hint');
216
-
217
- // Preserve current model before rebuilding dropdown
218
- var currentModel = modelSelect ? modelSelect.value : '';
219
-
220
- var cfg = PROVIDER_CONFIG[provider] || {};
221
-
222
- // Show/hide API key column
223
- var keyCol = document.getElementById('settings-key-col');
224
- if (keyCol) keyCol.style.display = cfg.needsKey ? 'block' : 'none';
225
-
226
- // Show/hide endpoint row
227
- var endpointRow = document.getElementById('settings-endpoint-row');
228
- var endpointInput = document.getElementById('settings-endpoint');
229
- if (endpointRow) {
230
- endpointRow.style.display = cfg.endpointEditable ? 'block' : 'none';
231
- if (endpointInput && cfg.endpoint) endpointInput.value = cfg.endpoint;
232
- }
233
-
234
- // For Ollama: check live status and populate real models
235
- if (provider === 'ollama') {
236
- if (modelHint) modelHint.textContent = 'Checking Ollama...';
237
- fetch('/api/v3/ollama/status').then(function(r) { return r.json(); }).then(function(data) {
238
- if (modelSelect) {
239
- modelSelect.textContent = '';
240
- if (data.running && data.models.length > 0) {
241
- data.models.forEach(function(m) {
242
- var opt = document.createElement('option');
243
- opt.value = m.name;
244
- opt.textContent = m.name;
245
- modelSelect.appendChild(opt);
246
- });
247
- if (modelHint) modelHint.textContent = 'Ollama running (' + data.count + ' models)';
248
- if (modelHint) modelHint.className = 'text-success small';
249
- } else {
250
- var opt = document.createElement('option');
251
- opt.value = '';
252
- opt.textContent = 'Ollama not running!';
253
- modelSelect.appendChild(opt);
254
- if (modelHint) modelHint.textContent = 'Ollama not detected. Run: ollama serve';
255
- if (modelHint) modelHint.className = 'text-danger small';
256
- }
257
- }
258
- }).catch(function() {
259
- if (modelHint) { modelHint.textContent = 'Ollama not reachable'; modelHint.className = 'text-danger small'; }
260
- });
261
- return;
262
- }
263
-
264
- // For other providers: use static model list
265
- if (modelSelect) {
266
- modelSelect.textContent = '';
267
- var options = MODEL_OPTIONS[provider] || [];
268
- if (options.length === 0) {
269
- var opt = document.createElement('option');
270
- opt.value = '';
271
- opt.textContent = 'N/A (Mode A)';
272
- modelSelect.appendChild(opt);
273
- } else {
274
- options.forEach(function(o) {
275
- var opt = document.createElement('option');
276
- opt.value = o.value;
277
- opt.textContent = o.label;
278
- modelSelect.appendChild(opt);
279
- });
280
- }
281
- // Restore previous model selection if it exists in the new list
282
- if (currentModel) {
283
- var found = false;
284
- for (var i = 0; i < modelSelect.options.length; i++) {
285
- if (modelSelect.options[i].value === currentModel) { found = true; break; }
286
- }
287
- if (found) {
288
- modelSelect.value = currentModel;
289
- } else if (currentModel) {
290
- // Model not in list — add it as custom option so user doesn't lose their choice
291
- var custom = document.createElement('option');
292
- custom.value = currentModel;
293
- custom.textContent = currentModel + ' (saved)';
294
- modelSelect.insertBefore(custom, modelSelect.firstChild);
295
- modelSelect.value = currentModel;
296
- }
297
- }
298
- }
299
-
300
- // Update hint
301
- if (modelHint) {
302
- var hints = {
303
- 'none': 'No LLM needed in Mode A',
304
- 'openrouter': '200+ models via OpenRouter API',
305
- 'openai': 'OpenAI models (requires API key)',
306
- 'anthropic': 'Anthropic models (requires API key)',
307
- };
308
- modelHint.textContent = hints[provider] || '';
309
- modelHint.className = 'text-muted small';
310
- }
311
- }
312
-
313
- async function testConnection() {
314
- var provider = document.getElementById('settings-provider')?.value || '';
315
- var model = document.getElementById('settings-model')?.value || '';
316
- var apiKey = document.getElementById('settings-api-key')?.value || '';
317
- var resultEl = document.getElementById('settings-test-result');
318
-
319
- if (!provider) {
320
- if (resultEl) { resultEl.textContent = 'Select a provider first'; resultEl.className = 'ms-2 small text-danger'; }
321
- return;
322
- }
323
-
324
- if (resultEl) { resultEl.textContent = 'Testing...'; resultEl.className = 'ms-2 small text-muted'; }
325
-
326
- try {
327
- var resp = await fetch('/api/v3/provider/test', {
328
- method: 'POST',
329
- headers: {'Content-Type': 'application/json'},
330
- body: JSON.stringify({provider: provider, model: model, api_key: apiKey})
331
- });
332
- var data = await resp.json();
333
- if (data.success) {
334
- if (resultEl) { resultEl.textContent = 'Connected! ' + (data.message || ''); resultEl.className = 'ms-2 small text-success fw-bold'; }
335
- } else {
336
- if (resultEl) { resultEl.textContent = 'Failed: ' + (data.error || 'Unknown'); resultEl.className = 'ms-2 small text-danger'; }
337
- }
338
- } catch (e) {
339
- if (resultEl) { resultEl.textContent = 'Error: ' + e.message; resultEl.className = 'ms-2 small text-danger'; }
340
- }
341
- }
342
-
343
- async function saveAllSettings() {
344
- var mode = document.querySelector('input[name="settings-mode-radio"]:checked')?.value || 'a';
345
- var provider = document.getElementById('settings-provider')?.value || 'none';
346
- if (mode === 'a') provider = 'none';
347
- var model = document.getElementById('settings-model')?.value || '';
348
- var apiKey = document.getElementById('settings-api-key')?.value || '';
349
-
350
- var statusEl = document.getElementById('settings-save-status');
351
- var saveBtn = document.getElementById('settings-save-all');
352
- if (saveBtn) saveBtn.disabled = true;
353
- if (statusEl) { statusEl.textContent = 'Saving...'; statusEl.style.display = 'inline'; statusEl.className = 'ms-2 text-muted'; }
354
-
355
- try {
356
- // Save mode
357
- var modeResp = await fetch('/api/v3/mode/set', {
358
- method: 'POST',
359
- headers: {'Content-Type': 'application/json'},
360
- body: JSON.stringify({mode: mode, provider: provider, model: model, api_key: apiKey})
361
- });
362
-
363
- if (modeResp.ok) {
364
- if (statusEl) {
365
- statusEl.textContent = 'Configuration saved! Mode: ' + mode.toUpperCase() +
366
- (provider !== 'none' ? ' | Provider: ' + provider : '');
367
- statusEl.className = 'ms-2 text-success fw-bold';
368
- }
369
- loadModeSettings();
370
- } else {
371
- if (statusEl) { statusEl.textContent = 'Save failed'; statusEl.className = 'ms-2 text-danger'; }
372
- }
373
- } catch (e) {
374
- if (statusEl) { statusEl.textContent = 'Error: ' + e.message; statusEl.className = 'ms-2 text-danger'; }
375
- }
376
- if (saveBtn) saveBtn.disabled = false;
377
-
378
- // Auto-hide status after 5 seconds
379
- setTimeout(function() {
380
- if (statusEl) statusEl.style.display = 'none';
381
- }, 5000);
382
- }
383
-
384
- // Bind events
385
- document.getElementById('settings-provider')?.addEventListener('change', updateProviderUI);
386
- document.getElementById('settings-save-all')?.addEventListener('click', saveAllSettings);
387
- document.getElementById('settings-test-btn')?.addEventListener('click', testConnection);
388
-
389
- // Mode radio buttons
390
- document.querySelectorAll('input[name="settings-mode-radio"]').forEach(function(radio) {
391
- radio.addEventListener('change', updateModeUI);
392
- });
393
-
394
- // Load settings when the settings tab is shown
395
- document.getElementById('settings-tab')?.addEventListener('shown.bs.tab', function() {
396
- loadAutoSettings();
397
- loadModeSettings();
398
- updateModeUI();
399
- });