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