@pheem49/mint 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -38,74 +38,138 @@
38
38
  <div class="settings-content" id="settings-content">
39
39
 
40
40
  <div class="tab-pane active" id="sect-general">
41
- <!-- API Key -->
42
- <section class="setting-section">
43
- <h2 class="section-title">🔑 Gemini API Key</h2>
44
- <div class="setting-row">
45
- <label for="api-key-input">API Key</label>
46
- <div class="input-group">
47
- <input type="password" id="api-key-input" placeholder="Enter your Gemini API Key..."
48
- autocomplete="off">
49
- <button class="toggle-visibility" id="toggle-key" title="Show/Hide">👁</button>
50
- </div>
51
- <div class="api-actions">
52
- <button class="api-save-btn" id="save-api-key">Save API Key</button>
53
- <span class="api-save-status" id="api-save-status" aria-live="polite"></span>
54
- </div>
55
- <p class="hint">Get your API Key at <a href="#" id="ai-studio-link">Google AI Studio</a></p>
56
- </div>
57
- <div class="setting-row" style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; padding: 12px; background: rgba(139, 92, 246, 0.1); border-radius: 12px; border: 1px solid rgba(139, 92, 246, 0.2);">
58
- <div style="display: flex; flex-direction: column; gap: 4px;">
59
- <label for="show-desktop-widget" style="margin-bottom: 0; font-weight: 500;">Show Desktop AI Candidate</label>
60
- <span class="hint" style="font-size: 0.75rem;">Show the mini AI character on your desktop</span>
61
- </div>
62
- <label class="toggle-switch">
63
- <input type="checkbox" id="show-desktop-widget">
64
- <span class="toggle-slider"></span>
65
- </label>
66
- </div>
67
- </section>
68
- <!-- AI Provider & Model -->
69
- <section class="setting-section">
70
- <h2 class="section-title">🧠 AI Engine</h2>
71
- <div class="setting-row">
72
- <label for="ai-provider-select">Provider</label>
73
- <div class="input-group">
74
- <select id="ai-provider-select" style="width: 100%; background: var(--input-bg); border: 1px solid var(--border); border-radius: 10px; padding: 10px 14px; color: var(--text-main); font-family: inherit; font-size: 0.9rem; outline: none; transition: border-color 0.2s; cursor: pointer;">
75
- <option value="gemini" style="background: var(--bg-color)">Google Gemini (Cloud)</option>
76
- <option value="ollama" style="background: var(--bg-color)">Ollama (Local / Private)</option>
77
- </select>
78
- </div>
79
- </div>
41
+ <!-- API Keys & Hosts -->
42
+ <section class="setting-section">
43
+ <h2 class="section-title">🔑 API Keys & Hosts</h2>
44
+ <div class="setting-row">
45
+ <label for="api-key-input">Gemini API Key</label>
46
+ <div class="input-group">
47
+ <input type="password" id="api-key-input" placeholder="Enter Gemini API Key..." autocomplete="off">
48
+ <button class="toggle-visibility" onclick="const i = document.getElementById('api-key-input'); i.type = i.type === 'password' ? 'text' : 'password';" title="Show/Hide">👁</button>
49
+ </div>
50
+ </div>
51
+ <div class="setting-row" style="margin-top: 10px;">
52
+ <label for="openai-api-key-input">OpenAI API Key</label>
53
+ <div class="input-group">
54
+ <input type="password" id="openai-api-key-input" placeholder="Enter OpenAI API Key..." autocomplete="off">
55
+ <button class="toggle-visibility" onclick="const i = document.getElementById('openai-api-key-input'); i.type = i.type === 'password' ? 'text' : 'password';" title="Show/Hide">👁</button>
56
+ </div>
57
+ </div>
58
+ <div class="setting-row" style="margin-top: 10px;">
59
+ <label for="anthropic-api-key-input">Anthropic API Key</label>
60
+ <div class="input-group">
61
+ <input type="password" id="anthropic-api-key-input" placeholder="Enter Anthropic API Key..." autocomplete="off">
62
+ <button class="toggle-visibility" onclick="const i = document.getElementById('anthropic-api-key-input'); i.type = i.type === 'password' ? 'text' : 'password';" title="Show/Hide">👁</button>
63
+ </div>
64
+ </div>
65
+ <div class="setting-row" style="margin-top: 10px;">
66
+ <label for="hf-api-key">Hugging Face API Key</label>
67
+ <div class="input-group">
68
+ <input type="password" id="hf-api-key" placeholder="Enter Hugging Face API Key..." autocomplete="off">
69
+ <button class="toggle-visibility" onclick="const i = document.getElementById('hf-api-key'); i.type = i.type === 'password' ? 'text' : 'password';" title="Show/Hide">👁</button>
70
+ </div>
71
+ </div>
72
+ <div class="setting-row" style="margin-top: 10px;">
73
+ <label for="local-api-base-url">LM Studio Base URL</label>
74
+ <input type="text" id="local-api-base-url" placeholder="e.g. http://localhost:1234/v1">
75
+ </div>
76
+ <div class="setting-row" style="margin-top: 10px;">
77
+ <label for="ollama-host-input">Ollama Host</label>
78
+ <input type="text" id="ollama-host-input" placeholder="e.g. http://localhost:11434">
79
+ </div>
80
+ <div class="setting-row" style="display: flex; align-items: center; justify-content: space-between; margin-top: 20px; padding: 12px; background: rgba(139, 92, 246, 0.1); border-radius: 12px; border: 1px solid rgba(139, 92, 246, 0.2);">
81
+ <div style="display: flex; flex-direction: column; gap: 4px;">
82
+ <label for="show-desktop-widget" style="margin-bottom: 0; font-weight: 500;">Show Desktop AI Candidate</label>
83
+ <span class="hint" style="font-size: 0.75rem;">Show the mini AI character on your desktop</span>
84
+ </div>
85
+ <label class="toggle-switch">
86
+ <input type="checkbox" id="show-desktop-widget">
87
+ <span class="toggle-slider"></span>
88
+ </label>
89
+ </div>
90
+ </section>
80
91
 
81
- <!-- Gemini Options -->
82
- <div id="gemini-options" style="margin-top: 14px; padding: 12px; border: 1px solid var(--border); border-radius: 8px; background: rgba(0,0,0,0.1);">
83
- <div class="setting-row">
84
- <label for="gemini-model-select">Gemini Model</label>
85
- <div class="input-group">
86
- <select id="gemini-model-select" style="width: 100%; background: var(--input-bg); border: 1px solid var(--border); border-radius: 10px; padding: 10px 14px; color: var(--text-main); font-family: inherit; font-size: 0.9rem; outline: none; transition: border-color 0.2s; cursor: pointer;">
87
- <option value="gemini-2.5-flash" style="background: var(--bg-color)">gemini-2.5-flash</option>
88
- <option value="gemini-3.1-flash-lite" style="background: var(--bg-color)">gemini-3.1-flash-lite</option>
89
- <option value="gemini-3.1-flash-lite-preview" style="background: var(--bg-color)">gemini-3.1-flash-lite-preview</option>
90
- <option value="custom" style="background: var(--bg-color)">Custom...</option>
91
- </select>
92
+ <!-- Models Configuration -->
93
+ <section class="setting-section">
94
+ <h2 class="section-title">🧠 AI Engine & Default Models</h2>
95
+ <div class="setting-row">
96
+ <label for="ai-provider-select">Active Provider</label>
97
+ <div class="input-group">
98
+ <select id="ai-provider-select" style="width: 100%; background: var(--input-bg); border: 1px solid var(--border); border-radius: 10px; padding: 10px 14px; color: var(--text-main); font-family: inherit; font-size: 0.9rem; outline: none; transition: border-color 0.2s; cursor: pointer;">
99
+ <option value="gemini" style="background: var(--bg-color)">Google Gemini (Cloud)</option>
100
+ <option value="anthropic" style="background: var(--bg-color)">Anthropic Claude</option>
101
+ <option value="openai" style="background: var(--bg-color)">OpenAI</option>
102
+ <option value="ollama" style="background: var(--bg-color)">Ollama (Local / Private)</option>
103
+ <option value="huggingface" style="background: var(--bg-color)">Hugging Face (Inference API)</option>
104
+ <option value="local_openai" style="background: var(--bg-color)">Local (LM Studio / OpenAI Compatible)</option>
105
+ </select>
106
+ </div>
92
107
  </div>
93
- </div>
94
- <div class="setting-row" id="gemini-model-custom-row" style="display: none; margin-top: 10px;">
95
- <label for="gemini-model-custom">Custom Model</label>
96
- <input type="text" id="gemini-model-custom" placeholder="e.g. gemini-3.1-flash-lite-preview">
97
- </div>
98
- </div>
99
108
 
100
- <!-- Ollama Options -->
101
- <div id="ollama-options" style="display: none; margin-top: 14px; padding: 12px; border: 1px solid var(--border); border-radius: 8px; background: rgba(0,0,0,0.1);">
102
- <div class="setting-row">
103
- <label for="ollama-model-input">Ollama Model</label>
104
- <input type="text" id="ollama-model-input" placeholder="e.g. llama3:latest">
105
- <p class="hint" style="margin-top: 8px; color: #fbbf24;">Make sure Ollama is running at http://localhost:11434</p>
106
- </div>
107
- </div>
108
- </section>
109
+ <div class="setting-row" style="margin-top: 14px;">
110
+ <label for="gemini-model-select">Gemini Model</label>
111
+ <div class="input-group">
112
+ <select id="gemini-model-select" style="width: 100%; background: var(--input-bg); border: 1px solid var(--border); border-radius: 10px; padding: 10px 14px; color: var(--text-main); font-family: inherit; font-size: 0.9rem; outline: none; transition: border-color 0.2s; cursor: pointer;">
113
+ <option value="gemini-2.5-flash" style="background: var(--bg-color)">gemini-2.5-flash</option>
114
+ <option value="gemini-3.1-flash-lite" style="background: var(--bg-color)">gemini-3.1-flash-lite</option>
115
+ <option value="gemini-3.1-flash-lite-preview" style="background: var(--bg-color)">gemini-3.1-flash-lite-preview</option>
116
+ <option value="custom" style="background: var(--bg-color)">Custom...</option>
117
+ </select>
118
+ </div>
119
+ </div>
120
+ <div class="setting-row" id="gemini-model-custom-row" style="display: none; margin-top: 10px;">
121
+ <label for="gemini-model-custom">Custom Gemini Model</label>
122
+ <input type="text" id="gemini-model-custom" placeholder="e.g. gemini-3.1-flash-lite-preview">
123
+ </div>
124
+
125
+ <div class="setting-row" style="margin-top: 14px;">
126
+ <label for="openai-model-select">OpenAI Model</label>
127
+ <div class="input-group">
128
+ <select id="openai-model-select" style="width: 100%; background: var(--input-bg); border: 1px solid var(--border); border-radius: 10px; padding: 10px 14px; color: var(--text-main); font-family: inherit; font-size: 0.9rem; outline: none; transition: border-color 0.2s; cursor: pointer;">
129
+ <option value="gpt-4o" style="background: var(--bg-color)">gpt-4o</option>
130
+ <option value="gpt-4o-mini" style="background: var(--bg-color)">gpt-4o-mini</option>
131
+ <option value="o1-preview" style="background: var(--bg-color)">o1-preview</option>
132
+ <option value="o1-mini" style="background: var(--bg-color)">o1-mini</option>
133
+ <option value="custom" style="background: var(--bg-color)">Custom...</option>
134
+ </select>
135
+ </div>
136
+ </div>
137
+ <div class="setting-row" id="openai-model-custom-row" style="display: none; margin-top: 10px;">
138
+ <label for="openai-model-custom">Custom OpenAI Model</label>
139
+ <input type="text" id="openai-model-custom" placeholder="e.g. gpt-4o">
140
+ </div>
141
+
142
+ <div class="setting-row" style="margin-top: 14px;">
143
+ <label for="anthropic-model-select">Anthropic Model</label>
144
+ <div class="input-group">
145
+ <select id="anthropic-model-select" style="width: 100%; background: var(--input-bg); border: 1px solid var(--border); border-radius: 10px; padding: 10px 14px; color: var(--text-main); font-family: inherit; font-size: 0.9rem; outline: none; transition: border-color 0.2s; cursor: pointer;">
146
+ <option value="claude-3-5-sonnet-latest" style="background: var(--bg-color)">claude-3-5-sonnet-latest</option>
147
+ <option value="claude-3-opus-latest" style="background: var(--bg-color)">claude-3-opus-latest</option>
148
+ <option value="claude-3-5-haiku-latest" style="background: var(--bg-color)">claude-3-5-haiku-latest</option>
149
+ <option value="custom" style="background: var(--bg-color)">Custom...</option>
150
+ </select>
151
+ </div>
152
+ </div>
153
+ <div class="setting-row" id="anthropic-model-custom-row" style="display: none; margin-top: 10px;">
154
+ <label for="anthropic-model-custom">Custom Anthropic Model</label>
155
+ <input type="text" id="anthropic-model-custom" placeholder="e.g. claude-3-5-sonnet-latest">
156
+ </div>
157
+
158
+ <div class="setting-row" style="margin-top: 14px;">
159
+ <label for="hf-model-name">Hugging Face Model</label>
160
+ <input type="text" id="hf-model-name" placeholder="e.g. meta-llama/Meta-Llama-3-8B-Instruct">
161
+ </div>
162
+
163
+ <div class="setting-row" style="margin-top: 14px;">
164
+ <label for="local-model-name">Local (LM Studio) Model</label>
165
+ <input type="text" id="local-model-name" placeholder="e.g. local-model">
166
+ </div>
167
+
168
+ <div class="setting-row" style="margin-top: 14px;">
169
+ <label for="ollama-model-input">Ollama Model</label>
170
+ <input type="text" id="ollama-model-input" placeholder="e.g. llama3:latest">
171
+ </div>
172
+ </section>
109
173
  </div>
110
174
  <div class="tab-pane" id="sect-audio">
111
175
  <!-- Audio & Voice -->
@@ -212,6 +276,20 @@
212
276
  <button class="btn-secondary" id="open-workflows-btn">Open workflows.json</button>
213
277
  <button class="btn-primary" id="reload-workflows-btn">Reload Rules</button>
214
278
  </div>
279
+ </section>
280
+ <!-- Agent Collaboration -->
281
+ <section class="setting-section">
282
+ <h2 class="section-title">🤝 Agent Collaboration</h2>
283
+ <div class="setting-row" style="display: flex; align-items: center; justify-content: space-between;">
284
+ <label for="enable-agent-collaboration" style="margin-bottom: 0;">Enable Multi-Agent Review</label>
285
+ <label class="toggle-switch">
286
+ <input type="checkbox" id="enable-agent-collaboration">
287
+ <span class="toggle-slider"></span>
288
+ </label>
289
+ </div>
290
+ <div class="setting-row" style="margin-top: 10px;">
291
+ <p class="hint">When in Code Mode, allow a secondary AI model to automatically review the code written by the primary model. (Requires multiple API keys. Costs API credits for both models.)</p>
292
+ </div>
215
293
  </section>
216
294
  </div>
217
295
  <div class="tab-pane" id="sect-theme">
@@ -16,6 +16,7 @@ const DEFAULT_CONFIG = {
16
16
  ollamaModel: 'llama3:latest',
17
17
  enableVoiceReply: true,
18
18
  enableCustomWorkflows: true,
19
+ enableAgentCollaboration: true,
19
20
  ttsProvider: 'google',
20
21
  ttsVolume: 1.0,
21
22
  ttsSpeed: 1.0,
@@ -57,6 +58,18 @@ function applyConfig(config) {
57
58
  // Apply API key
58
59
  document.getElementById('api-key-input').value = config.apiKey || '';
59
60
 
61
+ const anthropicInput = document.getElementById('anthropic-api-key-input');
62
+ if (anthropicInput) anthropicInput.value = config.anthropicApiKey || '';
63
+
64
+ const openaiInput = document.getElementById('openai-api-key-input');
65
+ if (openaiInput) openaiInput.value = config.openaiApiKey || '';
66
+
67
+ const hfInput = document.getElementById('hf-api-key');
68
+ if (hfInput) hfInput.value = config.hfApiKey || '';
69
+
70
+ const ollamaHostInput = document.getElementById('ollama-host-input');
71
+ if (ollamaHostInput) ollamaHostInput.value = config.ollamaHost || 'http://localhost:11434';
72
+
60
73
  // Apply Gemini model
61
74
  applyModelSelection(config.geminiModel);
62
75
 
@@ -72,6 +85,24 @@ function applyConfig(config) {
72
85
  ollamaInput.value = config.ollamaModel || 'llama3:latest';
73
86
  }
74
87
 
88
+ applyModelSelectionGeneric('openai-model-select', 'openai-model-custom-row', 'openai-model-custom', config.openaiModel || 'gpt-4o');
89
+ applyModelSelectionGeneric('anthropic-model-select', 'anthropic-model-custom-row', 'anthropic-model-custom', config.anthropicModel || 'claude-3-5-sonnet-latest');
90
+
91
+ const hfModelInput = document.getElementById('hf-model-name');
92
+ if (hfModelInput) {
93
+ hfModelInput.value = config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct';
94
+ }
95
+
96
+ const localApiBaseUrlInput = document.getElementById('local-api-base-url');
97
+ if (localApiBaseUrlInput) {
98
+ localApiBaseUrlInput.value = config.localApiBaseUrl || 'http://localhost:1234/v1';
99
+ }
100
+
101
+ const localModelNameInput = document.getElementById('local-model-name');
102
+ if (localModelNameInput) {
103
+ localModelNameInput.value = config.localModelName || 'local-model';
104
+ }
105
+
75
106
  const voiceReplyToggle = document.getElementById('enable-voice-reply');
76
107
  if (voiceReplyToggle) {
77
108
  voiceReplyToggle.checked = config.enableVoiceReply !== false;
@@ -103,6 +134,11 @@ function applyConfig(config) {
103
134
  enableWorkflowsToggle.checked = config.enableCustomWorkflows !== false;
104
135
  }
105
136
 
137
+ const enableAgentCollaborationToggle = document.getElementById('enable-agent-collaboration');
138
+ if (enableAgentCollaborationToggle) {
139
+ enableAgentCollaborationToggle.checked = config.enableAgentCollaboration !== false;
140
+ }
141
+
106
142
  // Plugins logic
107
143
  updatePluginButton('spotify', config.pluginSpotifyEnabled);
108
144
  updatePluginButton('calendar', config.pluginCalendarEnabled);
@@ -168,9 +204,15 @@ function lightenColor(hex, amount) {
168
204
  }
169
205
 
170
206
  function applyModelSelection(model) {
171
- const select = document.getElementById('gemini-model-select');
172
- const customRow = document.getElementById('gemini-model-custom-row');
173
- const customInput = document.getElementById('gemini-model-custom');
207
+ applyModelSelectionGeneric('gemini-model-select', 'gemini-model-custom-row', 'gemini-model-custom', model);
208
+ }
209
+
210
+ function applyModelSelectionGeneric(selectId, customRowId, customInputId, model) {
211
+ const select = document.getElementById(selectId);
212
+ const customRow = document.getElementById(customRowId);
213
+ const customInput = document.getElementById(customInputId);
214
+ if (!select || !customRow || !customInput) return;
215
+
174
216
  const normalized = (model || '').trim();
175
217
  const optionValues = Array.from(select.options).map(opt => opt.value);
176
218
 
@@ -186,11 +228,16 @@ function applyModelSelection(model) {
186
228
  }
187
229
 
188
230
  function getSelectedModel() {
189
- const select = document.getElementById('gemini-model-select');
190
- const customInput = document.getElementById('gemini-model-custom');
231
+ return getSelectedModelGeneric('gemini-model-select', 'gemini-model-custom', DEFAULT_CONFIG.geminiModel);
232
+ }
233
+
234
+ function getSelectedModelGeneric(selectId, customInputId, defaultModel) {
235
+ const select = document.getElementById(selectId);
236
+ const customInput = document.getElementById(customInputId);
237
+ if (!select || !customInput) return defaultModel;
191
238
  if (select.value === 'custom') {
192
239
  const custom = (customInput.value || '').trim();
193
- return custom || DEFAULT_CONFIG.geminiModel;
240
+ return custom || defaultModel;
194
241
  }
195
242
  return select.value;
196
243
  }
@@ -198,21 +245,28 @@ function getSelectedModel() {
198
245
  // --- Event Listeners ---
199
246
 
200
247
  // Close button
201
- document.getElementById('close-btn').addEventListener('click', () => {
202
- window.settingsApi.closeSettings();
203
- });
248
+ // Close button
249
+ const closeBtn = document.getElementById('close-btn');
250
+ if (closeBtn) {
251
+ closeBtn.addEventListener('click', () => {
252
+ window.settingsApi.closeSettings();
253
+ });
254
+ }
204
255
 
205
256
  // Toggle API key visibility
206
- document.getElementById('toggle-key').addEventListener('click', () => {
207
- const input = document.getElementById('api-key-input');
208
- input.type = input.type === 'password' ? 'text' : 'password';
209
- });
257
+ const toggleKey = document.getElementById('toggle-key');
258
+ if (toggleKey) {
259
+ toggleKey.addEventListener('click', () => {
260
+ const input = document.getElementById('api-key-input');
261
+ input.type = input.type === 'password' ? 'text' : 'password';
262
+ });
263
+ }
210
264
 
211
265
  async function saveApiKeyOnly() {
212
266
  const input = document.getElementById('api-key-input');
213
267
  const status = document.getElementById('api-save-status');
214
268
  const btn = document.getElementById('save-api-key');
215
- const apiKey = input.value.trim();
269
+ const apiKey = input ? input.value.trim() : '';
216
270
 
217
271
  try {
218
272
  const baseConfig = await window.settingsApi.getSettings();
@@ -220,26 +274,31 @@ async function saveApiKeyOnly() {
220
274
  await window.settingsApi.saveSettings(nextConfig);
221
275
  currentConfig.apiKey = apiKey;
222
276
 
223
- btn.textContent = 'Saved!';
224
- status.textContent = 'API key saved';
277
+ if (btn) btn.textContent = 'Saved!';
278
+ if (status) status.textContent = 'API key saved';
225
279
  setTimeout(() => {
226
- btn.textContent = 'Save API Key';
227
- status.textContent = '';
280
+ if (btn) btn.textContent = 'Save API Key';
281
+ if (status) status.textContent = '';
228
282
  }, 1500);
229
283
  } catch (err) {
230
284
  console.error('Failed to save API key:', err);
231
- status.textContent = 'Save failed';
232
- setTimeout(() => { status.textContent = ''; }, 1500);
285
+ if (status) status.textContent = 'Save failed';
286
+ setTimeout(() => { if (status) status.textContent = ''; }, 1500);
233
287
  }
234
288
  }
235
289
 
236
- document.getElementById('save-api-key').addEventListener('click', saveApiKeyOnly);
237
- document.getElementById('api-key-input').addEventListener('keydown', (e) => {
238
- if (e.key === 'Enter') {
239
- e.preventDefault();
240
- saveApiKeyOnly();
241
- }
242
- });
290
+ const saveApiKey = document.getElementById('save-api-key');
291
+ if (saveApiKey) saveApiKey.addEventListener('click', saveApiKeyOnly);
292
+
293
+ const apiKeyInput = document.getElementById('api-key-input');
294
+ if (apiKeyInput) {
295
+ apiKeyInput.addEventListener('keydown', (e) => {
296
+ if (e.key === 'Enter') {
297
+ e.preventDefault();
298
+ saveApiKeyOnly();
299
+ }
300
+ });
301
+ }
243
302
 
244
303
  // Gemini model select
245
304
  document.getElementById('gemini-model-select').addEventListener('change', (e) => {
@@ -257,23 +316,45 @@ document.getElementById('gemini-model-custom').addEventListener('input', (e) =>
257
316
  currentConfig.geminiModel = e.target.value.trim();
258
317
  });
259
318
 
260
- // AI Provider toggle
261
- function toggleProviderOptions(provider) {
262
- const geminiOptions = document.getElementById('gemini-options');
263
- const ollamaOptions = document.getElementById('ollama-options');
264
-
265
- if (provider === 'ollama') {
266
- geminiOptions.style.display = 'none';
267
- ollamaOptions.style.display = 'block';
319
+ // OpenAI model select
320
+ document.getElementById('openai-model-select').addEventListener('change', (e) => {
321
+ const customRow = document.getElementById('openai-model-custom-row');
322
+ if (e.target.value === 'custom') {
323
+ customRow.style.display = 'block';
324
+ currentConfig.openaiModel = (document.getElementById('openai-model-custom').value || '').trim();
268
325
  } else {
269
- geminiOptions.style.display = 'block';
270
- ollamaOptions.style.display = 'none';
326
+ customRow.style.display = 'none';
327
+ currentConfig.openaiModel = e.target.value;
271
328
  }
329
+ });
330
+
331
+ document.getElementById('openai-model-custom').addEventListener('input', (e) => {
332
+ currentConfig.openaiModel = e.target.value.trim();
333
+ });
334
+
335
+ // Anthropic model select
336
+ document.getElementById('anthropic-model-select').addEventListener('change', (e) => {
337
+ const customRow = document.getElementById('anthropic-model-custom-row');
338
+ if (e.target.value === 'custom') {
339
+ customRow.style.display = 'block';
340
+ currentConfig.anthropicModel = (document.getElementById('anthropic-model-custom').value || '').trim();
341
+ } else {
342
+ customRow.style.display = 'none';
343
+ currentConfig.anthropicModel = e.target.value;
344
+ }
345
+ });
346
+
347
+ document.getElementById('anthropic-model-custom').addEventListener('input', (e) => {
348
+ currentConfig.anthropicModel = e.target.value.trim();
349
+ });
350
+
351
+ // AI Provider toggle (No-op since all sections stay visible)
352
+ function toggleProviderOptions(provider) {
353
+ // No-op
272
354
  }
273
355
 
274
356
  document.getElementById('ai-provider-select').addEventListener('change', (e) => {
275
357
  currentConfig.aiProvider = e.target.value;
276
- toggleProviderOptions(e.target.value);
277
358
  });
278
359
 
279
360
  document.getElementById('ollama-model-input').addEventListener('input', (e) => {
@@ -281,9 +362,12 @@ document.getElementById('ollama-model-input').addEventListener('input', (e) => {
281
362
  });
282
363
 
283
364
  // AI Studio link
284
- document.getElementById('ai-studio-link').addEventListener('click', () => {
285
- window.settingsApi.openExternal('https://aistudio.google.com/');
286
- });
365
+ const aiStudioLink = document.getElementById('ai-studio-link');
366
+ if (aiStudioLink) {
367
+ aiStudioLink.addEventListener('click', () => {
368
+ window.settingsApi.openExternal('https://aistudio.google.com/');
369
+ });
370
+ }
287
371
 
288
372
  // Theme cards
289
373
  document.querySelectorAll('.theme-card').forEach(card => {
@@ -400,9 +484,34 @@ if (document.getElementById('tts-pitch')) {
400
484
  // Save
401
485
  document.getElementById('save-btn').addEventListener('click', async () => {
402
486
  currentConfig.apiKey = document.getElementById('api-key-input').value.trim();
487
+
488
+ const anthropicInput = document.getElementById('anthropic-api-key-input');
489
+ if (anthropicInput) currentConfig.anthropicApiKey = anthropicInput.value.trim();
490
+
491
+ const openaiInput = document.getElementById('openai-api-key-input');
492
+ if (openaiInput) currentConfig.openaiApiKey = openaiInput.value.trim();
493
+
494
+ const hfInput = document.getElementById('hf-api-key');
495
+ if (hfInput) currentConfig.hfApiKey = hfInput.value.trim();
496
+
497
+ const ollamaHostInput = document.getElementById('ollama-host-input');
498
+ if (ollamaHostInput) currentConfig.ollamaHost = ollamaHostInput.value.trim();
499
+
403
500
  currentConfig.geminiModel = getSelectedModel();
404
501
  currentConfig.aiProvider = document.getElementById('ai-provider-select').value;
405
502
  currentConfig.ollamaModel = document.getElementById('ollama-model-input').value.trim();
503
+
504
+ currentConfig.openaiModel = getSelectedModelGeneric('openai-model-select', 'openai-model-custom', DEFAULT_CONFIG.openaiModel || 'gpt-4o');
505
+ currentConfig.anthropicModel = getSelectedModelGeneric('anthropic-model-select', 'anthropic-model-custom', DEFAULT_CONFIG.anthropicModel || 'claude-3-5-sonnet-latest');
506
+
507
+ const hfModelInput = document.getElementById('hf-model-name');
508
+ if (hfModelInput) currentConfig.hfModel = hfModelInput.value.trim();
509
+
510
+ const localApiBaseUrlInput = document.getElementById('local-api-base-url');
511
+ if (localApiBaseUrlInput) currentConfig.localApiBaseUrl = localApiBaseUrlInput.value.trim();
512
+
513
+ const localModelNameInput = document.getElementById('local-model-name');
514
+ if (localModelNameInput) currentConfig.localModelName = localModelNameInput.value.trim();
406
515
 
407
516
  const voiceReplyToggle = document.getElementById('enable-voice-reply');
408
517
  if (voiceReplyToggle) {
@@ -421,6 +530,11 @@ document.getElementById('save-btn').addEventListener('click', async () => {
421
530
  currentConfig.enableCustomWorkflows = enableWorkflowsToggle.checked;
422
531
  }
423
532
 
533
+ const enableAgentCollaborationToggle = document.getElementById('enable-agent-collaboration');
534
+ if (enableAgentCollaborationToggle) {
535
+ currentConfig.enableAgentCollaboration = enableAgentCollaborationToggle.checked;
536
+ }
537
+
424
538
  const showWidgetToggle = document.getElementById('show-desktop-widget');
425
539
  if (showWidgetToggle) {
426
540
  currentConfig.showDesktopWidget = showWidgetToggle.checked;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Tests: agent_orchestrator.js
3
+ */
4
+
5
+ const orchestrator = require('../src/AI_Brain/agent_orchestrator');
6
+
7
+ describe('Agent Orchestrator', () => {
8
+ beforeEach(() => {
9
+ orchestrator.resetAgent();
10
+ });
11
+
12
+ test('starts with general agent', () => {
13
+ const agent = orchestrator.getCurrentAgent();
14
+ expect(agent.name).toBe('Mint Default');
15
+ });
16
+
17
+ test('can switch to coder agent', () => {
18
+ orchestrator.setAgent('coder');
19
+ const agent = orchestrator.getCurrentAgent();
20
+ expect(agent.name).toBe('Mint Coder');
21
+ });
22
+
23
+ test('can switch to researcher agent', () => {
24
+ orchestrator.setAgent('researcher');
25
+ const agent = orchestrator.getCurrentAgent();
26
+ expect(agent.name).toBe('Mint Researcher');
27
+ });
28
+
29
+ test('falls back to general for invalid agent', () => {
30
+ orchestrator.setAgent('nonexistent');
31
+ const agent = orchestrator.getCurrentAgent();
32
+ expect(agent.name).toBe('Mint Default');
33
+ });
34
+
35
+ test('lists available agents', () => {
36
+ const agents = orchestrator.listAgents();
37
+ expect(agents).toContain('general');
38
+ expect(agents).toContain('coder');
39
+ expect(agents).toContain('researcher');
40
+ });
41
+ });