superlocalmemory 3.4.16 → 3.4.18

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 (83) hide show
  1. package/CHANGELOG.md +20 -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/server/routes/entity.py +5 -9
  6. package/src/superlocalmemory/server/routes/helpers.py +120 -15
  7. package/src/superlocalmemory/server/routes/ingest.py +2 -3
  8. package/src/superlocalmemory/server/routes/v3_api.py +42 -2
  9. package/src/superlocalmemory/server/unified_daemon.py +21 -11
  10. package/src/superlocalmemory.egg-info/PKG-INFO +5 -2
  11. package/src/superlocalmemory.egg-info/requires.txt +3 -0
  12. package/docs/ARCHITECTURE.md +0 -149
  13. package/docs/api-reference.md +0 -284
  14. package/docs/auto-memory.md +0 -150
  15. package/docs/cli-reference.md +0 -327
  16. package/docs/cloud-backup.md +0 -174
  17. package/docs/compliance.md +0 -191
  18. package/docs/configuration.md +0 -182
  19. package/docs/getting-started.md +0 -102
  20. package/docs/ide-setup.md +0 -261
  21. package/docs/mcp-tools.md +0 -220
  22. package/docs/migration-from-v2.md +0 -170
  23. package/docs/profiles.md +0 -173
  24. package/docs/screenshots/01-dashboard-main.png +0 -0
  25. package/docs/screenshots/02-knowledge-graph.png +0 -0
  26. package/docs/screenshots/03-math-health.png +0 -0
  27. package/docs/screenshots/03-patterns-learning.png +0 -0
  28. package/docs/screenshots/04-learning-dashboard.png +0 -0
  29. package/docs/screenshots/04-recall-lab.png +0 -0
  30. package/docs/screenshots/05-behavioral-analysis.png +0 -0
  31. package/docs/screenshots/05-trust-dashboard.png +0 -0
  32. package/docs/screenshots/06-graph-communities.png +0 -0
  33. package/docs/screenshots/06-settings.png +0 -0
  34. package/docs/screenshots/07-memories-blurred.png +0 -0
  35. package/docs/skill-evolution.md +0 -256
  36. package/docs/troubleshooting.md +0 -310
  37. package/docs/v2-archive/ACCESSIBILITY.md +0 -291
  38. package/docs/v2-archive/ARCHITECTURE.md +0 -886
  39. package/docs/v2-archive/CLI-COMMANDS-REFERENCE.md +0 -425
  40. package/docs/v2-archive/COMPRESSION-README.md +0 -390
  41. package/docs/v2-archive/FRAMEWORK-INTEGRATIONS.md +0 -300
  42. package/docs/v2-archive/MCP-MANUAL-SETUP.md +0 -775
  43. package/docs/v2-archive/MCP-TROUBLESHOOTING.md +0 -787
  44. package/docs/v2-archive/PATTERN-LEARNING.md +0 -228
  45. package/docs/v2-archive/PROFILES-GUIDE.md +0 -453
  46. package/docs/v2-archive/RESET-GUIDE.md +0 -353
  47. package/docs/v2-archive/SEARCH-ENGINE-V2.2.0.md +0 -749
  48. package/docs/v2-archive/SEARCH-INTEGRATION-GUIDE.md +0 -502
  49. package/docs/v2-archive/UI-SERVER.md +0 -262
  50. package/docs/v2-archive/UNIVERSAL-INTEGRATION.md +0 -488
  51. package/docs/v2-archive/V2.2.0-OPTIONAL-SEARCH.md +0 -666
  52. package/docs/v2-archive/WINDOWS-INSTALL-README.txt +0 -34
  53. package/docs/v2-archive/WINDOWS-POST-INSTALL.txt +0 -45
  54. package/docs/v2-archive/example_graph_usage.py +0 -146
  55. package/ui/index.html +0 -1879
  56. package/ui/js/agents.js +0 -192
  57. package/ui/js/auto-settings.js +0 -399
  58. package/ui/js/behavioral.js +0 -276
  59. package/ui/js/clusters.js +0 -206
  60. package/ui/js/compliance.js +0 -252
  61. package/ui/js/core.js +0 -246
  62. package/ui/js/dashboard.js +0 -110
  63. package/ui/js/events.js +0 -178
  64. package/ui/js/fact-detail.js +0 -92
  65. package/ui/js/feedback.js +0 -333
  66. package/ui/js/graph-core.js +0 -447
  67. package/ui/js/graph-filters.js +0 -220
  68. package/ui/js/graph-interactions.js +0 -351
  69. package/ui/js/graph-ui.js +0 -214
  70. package/ui/js/ide-status.js +0 -102
  71. package/ui/js/init.js +0 -45
  72. package/ui/js/learning.js +0 -435
  73. package/ui/js/lifecycle.js +0 -298
  74. package/ui/js/math-health.js +0 -98
  75. package/ui/js/memories.js +0 -264
  76. package/ui/js/modal.js +0 -357
  77. package/ui/js/patterns.js +0 -93
  78. package/ui/js/profiles.js +0 -236
  79. package/ui/js/recall-lab.js +0 -292
  80. package/ui/js/search.js +0 -59
  81. package/ui/js/settings.js +0 -224
  82. package/ui/js/timeline.js +0 -32
  83. 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
- });