openclacky 1.3.2 → 1.3.4
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +54 -0
- data/Dockerfile +3 -0
- data/README.md +1 -1
- data/README_JA.md +237 -0
- data/docs/rich_ui_guide.md +277 -0
- data/docs/rich_ui_refactor_plan.md +396 -0
- data/lib/clacky/agent/llm_caller.rb +10 -4
- data/lib/clacky/agent/session_serializer.rb +52 -7
- data/lib/clacky/agent/time_machine.rb +247 -26
- data/lib/clacky/agent.rb +15 -3
- data/lib/clacky/api_extension.rb +262 -0
- data/lib/clacky/api_extension_loader.rb +156 -0
- data/lib/clacky/cli.rb +93 -3
- data/lib/clacky/client.rb +38 -13
- data/lib/clacky/default_agents/_panels/git/panel.js +201 -0
- data/lib/clacky/default_agents/_panels/time_machine/panel.js +640 -0
- data/lib/clacky/default_agents/coding/profile.yml +3 -0
- data/lib/clacky/default_agents/coding/webui/.gitkeep +0 -0
- data/lib/clacky/default_skills/cron-task-creator/SKILL.md +1 -1
- data/lib/clacky/default_skills/extend-openclacky/SKILL.md +6 -4
- data/lib/clacky/default_skills/media-gen/SKILL.md +37 -10
- data/lib/clacky/idle_compression_timer.rb +3 -1
- data/lib/clacky/locales/en.rb +26 -0
- data/lib/clacky/locales/i18n.rb +26 -0
- data/lib/clacky/locales/zh.rb +26 -0
- data/lib/clacky/media/openai_compat.rb +64 -1
- data/lib/clacky/message_history.rb +9 -0
- data/lib/clacky/rich_ui/components/base_component.rb +50 -0
- data/lib/clacky/rich_ui/components/dialogs/approval_dialog.rb +142 -0
- data/lib/clacky/rich_ui/components/dialogs/config_menu_dialog.rb +106 -0
- data/lib/clacky/rich_ui/components/dialogs/form_dialog.rb +128 -0
- data/lib/clacky/rich_ui/components/sidebar.rb +119 -0
- data/lib/clacky/rich_ui/components/sidebar_panels.rb +134 -0
- data/lib/clacky/rich_ui/components/status_view.rb +58 -0
- data/lib/clacky/rich_ui/components/thinking_live_view.rb +79 -0
- data/lib/clacky/rich_ui/entry_tracker.rb +56 -0
- data/lib/clacky/rich_ui/layout_adapter.rb +16 -0
- data/lib/clacky/rich_ui/progress_handle_adapter.rb +24 -0
- data/lib/clacky/rich_ui/rich_ui_controller.rb +868 -0
- data/lib/clacky/rich_ui/shell/rich_agent_shell.rb +184 -0
- data/lib/clacky/rich_ui/view_renderer.rb +291 -0
- data/lib/clacky/rich_ui.rb +57 -0
- data/lib/clacky/rich_ui_controller.rb +3 -1549
- data/lib/clacky/server/api_extension_dispatcher.rb +120 -0
- data/lib/clacky/server/channel/channel_manager.rb +26 -0
- data/lib/clacky/server/git_panel.rb +115 -0
- data/lib/clacky/server/http_server.rb +547 -15
- data/lib/clacky/server/server_master.rb +6 -4
- data/lib/clacky/server/session_registry.rb +1 -1
- data/lib/clacky/shell_hook_loader.rb +1 -1
- data/lib/clacky/tools/edit.rb +14 -2
- data/lib/clacky/ui2/ui_controller.rb +7 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +511 -101
- data/lib/clacky/web/app.js +95 -14
- data/lib/clacky/web/components/code-editor.js +197 -0
- data/lib/clacky/web/{notify.js → components/notify.js} +1 -1
- data/lib/clacky/web/{onboard.js → components/onboard.js} +18 -2
- data/lib/clacky/web/core/aside.js +117 -0
- data/lib/clacky/web/core/ext.js +387 -0
- data/lib/clacky/web/features/backup/store.js +92 -0
- data/lib/clacky/web/features/backup/view.js +94 -0
- data/lib/clacky/web/features/billing/store.js +163 -0
- data/lib/clacky/web/{billing.js → features/billing/view.js} +132 -240
- data/lib/clacky/web/features/brand/store.js +110 -0
- data/lib/clacky/web/{brand.js → features/brand/view.js} +49 -199
- data/lib/clacky/web/features/channels/store.js +103 -0
- data/lib/clacky/web/{channels.js → features/channels/view.js} +50 -127
- data/lib/clacky/web/features/creator/store.js +81 -0
- data/lib/clacky/web/{creator.js → features/creator/view.js} +53 -102
- data/lib/clacky/web/features/mcp/store.js +158 -0
- data/lib/clacky/web/{mcp.js → features/mcp/view.js} +57 -134
- data/lib/clacky/web/features/model-tester/store.js +77 -0
- data/lib/clacky/web/features/model-tester/view.js +7 -0
- data/lib/clacky/web/features/profile/store.js +170 -0
- data/lib/clacky/web/{profile.js → features/profile/view.js} +94 -144
- data/lib/clacky/web/features/share/store.js +145 -0
- data/lib/clacky/web/{share.js → features/share/view.js} +66 -202
- data/lib/clacky/web/features/skills/store.js +331 -0
- data/lib/clacky/web/features/skills/view.js +581 -0
- data/lib/clacky/web/features/tasks/store.js +135 -0
- data/lib/clacky/web/features/tasks/view.js +241 -0
- data/lib/clacky/web/features/trash/store.js +242 -0
- data/lib/clacky/web/{trash.js → features/trash/view.js} +102 -293
- data/lib/clacky/web/features/version/store.js +165 -0
- data/lib/clacky/web/features/version/view.js +323 -0
- data/lib/clacky/web/features/workspace/store.js +99 -0
- data/lib/clacky/web/features/workspace/view.js +305 -0
- data/lib/clacky/web/i18n.js +68 -6
- data/lib/clacky/web/index.html +113 -62
- data/lib/clacky/web/sessions.js +493 -39
- data/lib/clacky/web/settings.js +143 -49
- data/lib/clacky/web/skills.js +3 -863
- data/lib/clacky/web/vendor/codemirror/codemirror.min.js +29 -0
- data/lib/clacky/web/ws-dispatcher.js +7 -3
- data/lib/clacky.rb +17 -1
- metadata +81 -20
- data/lib/clacky/web/backup.js +0 -119
- data/lib/clacky/web/model-tester.js +0 -66
- data/lib/clacky/web/tasks.js +0 -373
- data/lib/clacky/web/version.js +0 -449
- data/lib/clacky/web/workspace.js +0 -316
- /data/lib/clacky/web/{notify.mp3 → assets/notify.mp3} +0 -0
- /data/lib/clacky/web/{datepicker.js → components/datepicker.js} +0 -0
- /data/lib/clacky/web/{sidebar.js → components/sidebar.js} +0 -0
- /data/lib/clacky/web/{marked.min.js → vendor/marked/marked.min.js} +0 -0
data/lib/clacky/web/settings.js
CHANGED
|
@@ -96,6 +96,10 @@ const Settings = (() => {
|
|
|
96
96
|
<span class="model-card-grid-name">${_esc(displayName)}</span>
|
|
97
97
|
${isDefault ? `<span class="badge badge-default">${I18n.t("settings.models.badge.default")}</span>` : ""}
|
|
98
98
|
${isLite ? `<span class="badge badge-lite">${I18n.t("settings.models.badge.lite")}</span>` : ""}
|
|
99
|
+
${!isDefault ? `<button class="btn-card-grid-action btn-card-grid-action-primary" data-index="${index}" data-action="default" style="margin-left:auto">
|
|
100
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
|
|
101
|
+
<span>${I18n.t("settings.models.btn.setDefault")}</span>
|
|
102
|
+
</button>` : ""}
|
|
99
103
|
</div>
|
|
100
104
|
<div class="model-card-grid-provider">${_esc(providerName)}</div>
|
|
101
105
|
${model.api_key_masked ? `<div class="model-card-grid-model">${_esc(model.api_key_masked)}</div>` : ""}
|
|
@@ -113,6 +117,10 @@ const Settings = (() => {
|
|
|
113
117
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
|
114
118
|
<span>${I18n.t("settings.models.btn.edit")}</span>
|
|
115
119
|
</button>
|
|
120
|
+
<button class="btn-card-grid-action" data-index="${index}" data-action="duplicate">
|
|
121
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
122
|
+
<span>${I18n.t("settings.models.btn.duplicate")}</span>
|
|
123
|
+
</button>
|
|
116
124
|
${_models.length > 1 ? `<button class="btn-card-grid-action btn-card-grid-action-danger" data-index="${index}" data-action="delete">
|
|
117
125
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
|
118
126
|
<span>${I18n.t("settings.models.btn.delete")}</span>
|
|
@@ -120,11 +128,7 @@ const Settings = (() => {
|
|
|
120
128
|
</div>
|
|
121
129
|
</div>
|
|
122
130
|
<div class="model-card-grid-footer">
|
|
123
|
-
${
|
|
124
|
-
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
|
|
125
|
-
<span>${I18n.t("settings.models.btn.setDefault")}</span>
|
|
126
|
-
</button>` : `<span></span>`}
|
|
127
|
-
${websiteUrl ? `<a class="model-card-grid-link" href="${_esc(websiteUrl)}" target="_blank" rel="noopener noreferrer">
|
|
131
|
+
${websiteUrl ? `<a class="model-card-grid-link" href="${_esc(websiteUrl)}" target="_blank" rel="noopener noreferrer" style="margin-left:auto">
|
|
128
132
|
${I18n.t("settings.models.link.topUp")}
|
|
129
133
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17L17 7"/><path d="M8 7h9v9"/></svg>
|
|
130
134
|
</a>` : ""}
|
|
@@ -140,10 +144,11 @@ const Settings = (() => {
|
|
|
140
144
|
btn.addEventListener("click", () => {
|
|
141
145
|
const action = btn.dataset.action;
|
|
142
146
|
switch (action) {
|
|
143
|
-
case "edit":
|
|
144
|
-
case "test":
|
|
145
|
-
case "delete":
|
|
146
|
-
case "default":
|
|
147
|
+
case "edit": _openModal(index); break;
|
|
148
|
+
case "test": _testModel(index); break;
|
|
149
|
+
case "delete": _removeModel(index); break;
|
|
150
|
+
case "default": _setAsDefault(index); break;
|
|
151
|
+
case "duplicate": _openModalDuplicate(index); break;
|
|
147
152
|
}
|
|
148
153
|
});
|
|
149
154
|
});
|
|
@@ -157,6 +162,7 @@ const Settings = (() => {
|
|
|
157
162
|
const indexInput = document.getElementById("model-modal-index");
|
|
158
163
|
|
|
159
164
|
indexInput.value = index;
|
|
165
|
+
document.getElementById("model-modal-source-id").value = "";
|
|
160
166
|
|
|
161
167
|
// Populate provider dropdown
|
|
162
168
|
_populateModalProviderDropdown();
|
|
@@ -169,7 +175,14 @@ const Settings = (() => {
|
|
|
169
175
|
document.getElementById("model-modal-baseurl").value = model.base_url || "";
|
|
170
176
|
document.getElementById("model-modal-apikey").value = model.api_key_masked || "";
|
|
171
177
|
document.getElementById("model-modal-default-field").style.display = "";
|
|
172
|
-
|
|
178
|
+
// Lock the checkbox when this is the only configured model: the system
|
|
179
|
+
// must always have one default (backend re-promotes on save), so
|
|
180
|
+
// unchecking would be a silent no-op. Force-checked + disabled makes
|
|
181
|
+
// the constraint visible without any extra copy.
|
|
182
|
+
const setDefaultCb = document.getElementById("model-modal-set-default");
|
|
183
|
+
const isOnlyModel = _models.length === 1;
|
|
184
|
+
setDefaultCb.checked = isOnlyModel ? true : (model.type === "default");
|
|
185
|
+
setDefaultCb.disabled = isOnlyModel;
|
|
173
186
|
|
|
174
187
|
// Set provider dropdown value
|
|
175
188
|
const matched = _findProviderByBaseUrl(model.base_url);
|
|
@@ -190,6 +203,9 @@ const Settings = (() => {
|
|
|
190
203
|
// Default to checked for new models — most users want their first/new
|
|
191
204
|
// model to take over as the default.
|
|
192
205
|
document.getElementById("model-modal-set-default").checked = true;
|
|
206
|
+
// Reset disabled flag in case the previous open was edit-mode on the
|
|
207
|
+
// sole-model lock path.
|
|
208
|
+
document.getElementById("model-modal-set-default").disabled = false;
|
|
193
209
|
|
|
194
210
|
// Reset provider dropdown
|
|
195
211
|
_modalSelectedProviderId = null;
|
|
@@ -222,6 +238,30 @@ const Settings = (() => {
|
|
|
222
238
|
document.body.style.overflow = "";
|
|
223
239
|
}
|
|
224
240
|
|
|
241
|
+
function _openModalDuplicate(index) {
|
|
242
|
+
const source = _models[index];
|
|
243
|
+
if (!source) return;
|
|
244
|
+
|
|
245
|
+
_openModal(-1);
|
|
246
|
+
|
|
247
|
+
document.getElementById("model-modal-source-id").value = source.id || "";
|
|
248
|
+
document.getElementById("model-modal-title").textContent = I18n.t("settings.models.modal.duplicate");
|
|
249
|
+
document.getElementById("model-modal-model").value = source.model || "";
|
|
250
|
+
document.getElementById("model-modal-baseurl").value = source.base_url || "";
|
|
251
|
+
document.getElementById("model-modal-apikey").value = source.api_key_masked || "";
|
|
252
|
+
|
|
253
|
+
const matched = _findProviderByBaseUrl(source.base_url);
|
|
254
|
+
_modalSelectedProviderId = matched ? matched.id : (source.anthropic_format ? "anthropic" : null);
|
|
255
|
+
const providerValue = document.getElementById("model-modal-provider-value");
|
|
256
|
+
if (matched) {
|
|
257
|
+
providerValue.textContent = matched.name;
|
|
258
|
+
providerValue.classList.remove("placeholder");
|
|
259
|
+
|
|
260
|
+
const promoHint = document.getElementById("model-modal-promo-hint");
|
|
261
|
+
if (matched.id !== "openclacky") promoHint.classList.remove("visible");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
225
265
|
function _populateModalProviderDropdown() {
|
|
226
266
|
const dropdown = document.getElementById("model-modal-provider-dropdown");
|
|
227
267
|
dropdown.innerHTML = `
|
|
@@ -323,13 +363,14 @@ const Settings = (() => {
|
|
|
323
363
|
const isNew = index < 0;
|
|
324
364
|
const existing = isNew ? {} : (_models[index] || {});
|
|
325
365
|
const existingId = existing.id || null;
|
|
366
|
+
const sourceId = document.getElementById("model-modal-source-id").value || null;
|
|
326
367
|
|
|
327
368
|
// Step 1: Test first
|
|
328
369
|
saveBtn.textContent = I18n.t("settings.models.btn.testing");
|
|
329
370
|
_showModalTestResult(null, "");
|
|
330
371
|
|
|
331
372
|
const result = await ModelTester.testConnection({
|
|
332
|
-
model, base_url, api_key, index, id: existingId, anthropic_format
|
|
373
|
+
model, base_url, api_key, index, id: existingId || sourceId, anthropic_format
|
|
333
374
|
});
|
|
334
375
|
|
|
335
376
|
if (result.rewrote) {
|
|
@@ -365,10 +406,14 @@ const Settings = (() => {
|
|
|
365
406
|
}
|
|
366
407
|
|
|
367
408
|
if (!hasId && !payload.api_key) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
409
|
+
if (sourceId) {
|
|
410
|
+
payload.source_id = sourceId;
|
|
411
|
+
} else {
|
|
412
|
+
saveBtn.textContent = I18n.t("settings.models.btn.save");
|
|
413
|
+
saveBtn.disabled = false;
|
|
414
|
+
_showModalTestResult(false, I18n.t("settings.models.placeholder.apikey"));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
372
417
|
}
|
|
373
418
|
|
|
374
419
|
const saveResult = await ModelTester.saveModel(payload, { existingId: hasId ? existingId : null });
|
|
@@ -395,6 +440,25 @@ const Settings = (() => {
|
|
|
395
440
|
el.className = `model-test-result ${ok ? "result-ok" : "result-fail"}`;
|
|
396
441
|
}
|
|
397
442
|
|
|
443
|
+
function _positionDropdownFixed(dropdown, anchor) {
|
|
444
|
+
const rect = anchor.getBoundingClientRect();
|
|
445
|
+
dropdown.style.position = "fixed";
|
|
446
|
+
dropdown.style.top = (rect.bottom + 4) + "px";
|
|
447
|
+
dropdown.style.left = rect.left + "px";
|
|
448
|
+
dropdown.style.width = rect.width + "px";
|
|
449
|
+
dropdown.style.right = "auto";
|
|
450
|
+
dropdown.style.zIndex = "9999";
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function _resetDropdownPosition(dropdown) {
|
|
454
|
+
dropdown.style.position = "";
|
|
455
|
+
dropdown.style.top = "";
|
|
456
|
+
dropdown.style.left = "";
|
|
457
|
+
dropdown.style.width = "";
|
|
458
|
+
dropdown.style.right = "";
|
|
459
|
+
dropdown.style.zIndex = "";
|
|
460
|
+
}
|
|
461
|
+
|
|
398
462
|
function _initModal() {
|
|
399
463
|
// Close button
|
|
400
464
|
document.getElementById("model-modal-close").addEventListener("click", _closeModal);
|
|
@@ -448,34 +512,58 @@ const Settings = (() => {
|
|
|
448
512
|
// Model dropdown functionality
|
|
449
513
|
const modelDropdownBtn = document.getElementById("model-modal-model-dropdown-btn");
|
|
450
514
|
const modelDropdown = document.getElementById("model-modal-model-dropdown");
|
|
515
|
+
const modelCombobox = document.getElementById("model-modal-model-combobox");
|
|
451
516
|
const modelInput = document.getElementById("model-modal-model");
|
|
452
517
|
|
|
518
|
+
function _openModelDropdown() {
|
|
519
|
+
_closeBaseUrlDropdown();
|
|
520
|
+
_updateModalModelDropdown();
|
|
521
|
+
_positionDropdownFixed(modelDropdown, modelCombobox);
|
|
522
|
+
modelDropdown.style.display = "block";
|
|
523
|
+
document.body.appendChild(modelDropdown);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function _closeModelDropdown() {
|
|
527
|
+
modelDropdown.style.display = "none";
|
|
528
|
+
_resetDropdownPosition(modelDropdown);
|
|
529
|
+
modelCombobox.appendChild(modelDropdown);
|
|
530
|
+
}
|
|
531
|
+
|
|
453
532
|
modelDropdownBtn.addEventListener("click", (e) => {
|
|
454
533
|
e.stopPropagation();
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
if (!isOpen) {
|
|
460
|
-
_updateModalModelDropdown();
|
|
461
|
-
modelDropdown.style.display = "block";
|
|
534
|
+
if (modelDropdown.style.display === "block") {
|
|
535
|
+
_closeModelDropdown();
|
|
536
|
+
} else {
|
|
537
|
+
_openModelDropdown();
|
|
462
538
|
}
|
|
463
539
|
});
|
|
464
540
|
|
|
465
541
|
// Base URL dropdown functionality
|
|
466
542
|
const baseUrlDropdownBtn = document.getElementById("model-modal-baseurl-dropdown-btn");
|
|
467
543
|
const baseUrlDropdown = document.getElementById("model-modal-baseurl-dropdown");
|
|
544
|
+
const baseUrlCombobox = document.getElementById("model-modal-baseurl-combobox");
|
|
468
545
|
const baseUrlInput = document.getElementById("model-modal-baseurl");
|
|
469
546
|
|
|
547
|
+
function _openBaseUrlDropdown() {
|
|
548
|
+
_closeModelDropdown();
|
|
549
|
+
_updateModalBaseUrlDropdown();
|
|
550
|
+
_positionDropdownFixed(baseUrlDropdown, baseUrlCombobox);
|
|
551
|
+
baseUrlDropdown.style.display = "block";
|
|
552
|
+
document.body.appendChild(baseUrlDropdown);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function _closeBaseUrlDropdown() {
|
|
556
|
+
baseUrlDropdown.style.display = "none";
|
|
557
|
+
_resetDropdownPosition(baseUrlDropdown);
|
|
558
|
+
baseUrlCombobox.appendChild(baseUrlDropdown);
|
|
559
|
+
}
|
|
560
|
+
|
|
470
561
|
baseUrlDropdownBtn.addEventListener("click", (e) => {
|
|
471
562
|
e.stopPropagation();
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (!isOpen) {
|
|
477
|
-
_updateModalBaseUrlDropdown();
|
|
478
|
-
baseUrlDropdown.style.display = "block";
|
|
563
|
+
if (baseUrlDropdown.style.display === "block") {
|
|
564
|
+
_closeBaseUrlDropdown();
|
|
565
|
+
} else {
|
|
566
|
+
_openBaseUrlDropdown();
|
|
479
567
|
}
|
|
480
568
|
});
|
|
481
569
|
|
|
@@ -484,10 +572,14 @@ const Settings = (() => {
|
|
|
484
572
|
_updateModalModelDropdown();
|
|
485
573
|
});
|
|
486
574
|
|
|
487
|
-
// Close
|
|
488
|
-
document.addEventListener("
|
|
489
|
-
modelDropdown.
|
|
490
|
-
|
|
575
|
+
// Close dropdowns on outside click
|
|
576
|
+
document.addEventListener("mousedown", (e) => {
|
|
577
|
+
if (!modelCombobox.contains(e.target) && !modelDropdown.contains(e.target)) {
|
|
578
|
+
_closeModelDropdown();
|
|
579
|
+
}
|
|
580
|
+
if (!baseUrlCombobox.contains(e.target) && !baseUrlDropdown.contains(e.target)) {
|
|
581
|
+
_closeBaseUrlDropdown();
|
|
582
|
+
}
|
|
491
583
|
});
|
|
492
584
|
}
|
|
493
585
|
|
|
@@ -1463,22 +1555,24 @@ const Settings = (() => {
|
|
|
1463
1555
|
// ── Init ──────────────────────────────────────────────────────────────────────
|
|
1464
1556
|
|
|
1465
1557
|
function _initTabs() {
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
tabs
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1558
|
+
const bar = document.getElementById("settings-tabs");
|
|
1559
|
+
if (!bar) return;
|
|
1560
|
+
|
|
1561
|
+
// Delegated so extension tabs (mounted into the settings.tabs slot after
|
|
1562
|
+
// this runs) switch correctly without re-binding.
|
|
1563
|
+
bar.addEventListener("click", (e) => {
|
|
1564
|
+
const tab = e.target.closest(".settings-tab");
|
|
1565
|
+
if (!tab || !bar.contains(tab)) return;
|
|
1566
|
+
const targetTab = tab.dataset.tab;
|
|
1567
|
+
if (!targetTab) return;
|
|
1568
|
+
|
|
1569
|
+
document.querySelectorAll("#settings-tabs .settings-tab").forEach(t =>
|
|
1570
|
+
t.classList.toggle("active", t.dataset.tab === targetTab));
|
|
1571
|
+
|
|
1572
|
+
document.querySelectorAll("#settings-body .settings-tab-content").forEach(c => {
|
|
1573
|
+
const isActive = c.dataset.tabContent === targetTab;
|
|
1574
|
+
c.classList.toggle("active", isActive);
|
|
1575
|
+
c.style.display = isActive ? "" : "none";
|
|
1482
1576
|
});
|
|
1483
1577
|
});
|
|
1484
1578
|
}
|