Spaces:
Paused
Paused
icebear0828 Claude Opus 4.6 commited on
Commit Β·
bbf1e14
1
Parent(s): 87cb62c
fix: make dashboard config readonly, fix copy buttons for HTTP
Browse files- Config fields (Base URL, API Key, Model) now readonly, driven by backend
- Copy buttons use fallback (textarea+execCommand) for HTTP contexts
- Added visual feedback for all copy buttons (success/fail states)
- Removed localStorage config override system (saveOverrides, restoreOverrides)
- Removed "Reset to defaults" button
- Added i18n keys for copy feedback (copied/copyFailed)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- public/dashboard.html +56 -78
public/dashboard.html
CHANGED
|
@@ -119,14 +119,13 @@
|
|
| 119 |
<svg class="size-5 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 010 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 010-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
| 120 |
<h2 class="text-[0.95rem] font-bold" data-i18n="apiConfig">API Configuration</h2>
|
| 121 |
</div>
|
| 122 |
-
<button onclick="resetConfigDefaults()" class="text-xs text-slate-400 dark:text-text-dim hover:text-slate-600 dark:hover:text-text-main transition-colors" data-i18n="resetDefaults">Reset to defaults</button>
|
| 123 |
</div>
|
| 124 |
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
| 125 |
<!-- Base URL -->
|
| 126 |
<div class="space-y-1.5">
|
| 127 |
<label class="text-xs font-semibold text-slate-700 dark:text-text-main" data-i18n="baseProxyUrl">Base Proxy URL</label>
|
| 128 |
<div class="relative flex items-center">
|
| 129 |
-
<input id="baseUrl" class="w-full pl-3 pr-10 py-2.5 bg-slate-
|
| 130 |
<button onclick="copyField('baseUrl', this)" class="absolute right-2 p-1.5 text-slate-400 dark:text-text-dim hover:text-primary transition-colors rounded-md hover:bg-slate-100 dark:hover:bg-border-dark" data-i18n-title="copyUrl" title="Copy URL">
|
| 131 |
<svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
|
| 132 |
</button>
|
|
@@ -136,12 +135,7 @@
|
|
| 136 |
<div class="space-y-1.5">
|
| 137 |
<label class="text-xs font-semibold text-slate-700 dark:text-text-main" data-i18n="defaultModel">Default Model</label>
|
| 138 |
<div class="relative">
|
| 139 |
-
<
|
| 140 |
-
<option value="codex">codex</option>
|
| 141 |
-
</select>
|
| 142 |
-
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-slate-500 dark:text-text-dim">
|
| 143 |
-
<svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"/></svg>
|
| 144 |
-
</div>
|
| 145 |
</div>
|
| 146 |
</div>
|
| 147 |
<!-- API Key -->
|
|
@@ -151,7 +145,7 @@
|
|
| 151 |
<div class="absolute left-3 text-slate-400 dark:text-text-dim">
|
| 152 |
<svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"/></svg>
|
| 153 |
</div>
|
| 154 |
-
<input id="apiKey" class="w-full pl-10 pr-10 py-2.5 bg-slate-
|
| 155 |
<button onclick="copyField('apiKey', this)" class="absolute right-2 p-1.5 text-slate-400 dark:text-text-dim hover:text-primary transition-colors rounded-md hover:bg-slate-100 dark:hover:bg-border-dark" data-i18n-title="copyApiKey" title="Copy API Key">
|
| 156 |
<svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
|
| 157 |
</button>
|
|
@@ -231,7 +225,6 @@ const LANG = {
|
|
| 231 |
ok: "OK",
|
| 232 |
resetsAt: "Resets at",
|
| 233 |
apiConfig: "API Configuration",
|
| 234 |
-
resetDefaults: "Reset to defaults",
|
| 235 |
baseProxyUrl: "Base Proxy URL",
|
| 236 |
defaultModel: "Default Model",
|
| 237 |
yourApiKey: "Your API Key",
|
|
@@ -249,6 +242,8 @@ const LANG = {
|
|
| 249 |
failedExchangeCode: "Failed to exchange code",
|
| 250 |
failedDeleteAccount: "Failed to delete account.",
|
| 251 |
networkError: "Network error: ",
|
|
|
|
|
|
|
| 252 |
footer: "\u00a9 2025 Codex Proxy. All rights reserved.",
|
| 253 |
},
|
| 254 |
zh: {
|
|
@@ -276,7 +271,6 @@ const LANG = {
|
|
| 276 |
ok: "\u6b63\u5e38",
|
| 277 |
resetsAt: "\u91cd\u7f6e\u65f6\u95f4",
|
| 278 |
apiConfig: "API \u914d\u7f6e",
|
| 279 |
-
resetDefaults: "\u6062\u590d\u9ed8\u8ba4",
|
| 280 |
baseProxyUrl: "\u4ee3\u7406 URL",
|
| 281 |
defaultModel: "\u9ed8\u8ba4\u6a21\u578b",
|
| 282 |
yourApiKey: "API \u5bc6\u94a5",
|
|
@@ -294,6 +288,8 @@ const LANG = {
|
|
| 294 |
failedExchangeCode: "\u6388\u6743\u7801\u4ea4\u6362\u5931\u8d25",
|
| 295 |
failedDeleteAccount: "\u5220\u9664\u8d26\u6237\u5931\u8d25\u3002",
|
| 296 |
networkError: "\u7f51\u7edc\u9519\u8bef\uff1a",
|
|
|
|
|
|
|
| 297 |
footer: "\u00a9 2025 Codex Proxy\u3002\u4fdd\u7559\u6240\u6709\u6743\u5229\u3002",
|
| 298 |
},
|
| 299 |
};
|
|
@@ -622,7 +618,6 @@ async function loadStatus() {
|
|
| 622 |
document.getElementById('apiKey').value = serverApiKey;
|
| 623 |
|
| 624 |
await populateModelDropdown();
|
| 625 |
-
restoreOverrides();
|
| 626 |
updateCodeExamples();
|
| 627 |
} catch (err) {
|
| 628 |
console.error('Status load error:', err);
|
|
@@ -630,24 +625,17 @@ async function loadStatus() {
|
|
| 630 |
}
|
| 631 |
|
| 632 |
async function populateModelDropdown() {
|
| 633 |
-
const
|
| 634 |
try {
|
| 635 |
const resp = await fetch('/v1/models');
|
| 636 |
const data = await resp.json();
|
| 637 |
const models = data.data.map(m => m.id);
|
| 638 |
if (models.length > 0) {
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
const opt = document.createElement('option');
|
| 642 |
-
opt.value = id;
|
| 643 |
-
opt.textContent = id;
|
| 644 |
-
sel.appendChild(opt);
|
| 645 |
-
});
|
| 646 |
-
const preferred = models.find(n => n.includes('5.3-codex'));
|
| 647 |
-
if (preferred) sel.value = preferred;
|
| 648 |
}
|
| 649 |
} catch {
|
| 650 |
-
|
| 651 |
}
|
| 652 |
}
|
| 653 |
|
|
@@ -797,73 +785,63 @@ function switchLang(lang, el) {
|
|
| 797 |
}
|
| 798 |
|
| 799 |
// ββ Copy βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 800 |
-
function
|
| 801 |
-
const
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
}
|
| 811 |
|
| 812 |
-
function
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
// ββ Config Overrides (localStorage) ββββββββββββββββββββββββββββ
|
| 819 |
-
function saveOverrides() {
|
| 820 |
-
try {
|
| 821 |
-
localStorage.setItem('codex-proxy-config-overrides', JSON.stringify({
|
| 822 |
-
baseUrl: document.getElementById('baseUrl').value,
|
| 823 |
-
apiKey: document.getElementById('apiKey').value,
|
| 824 |
-
model: document.getElementById('defaultModel').value,
|
| 825 |
-
}));
|
| 826 |
-
} catch {}
|
| 827 |
-
}
|
| 828 |
-
|
| 829 |
-
function restoreOverrides() {
|
| 830 |
-
try {
|
| 831 |
-
const raw = localStorage.getItem('codex-proxy-config-overrides');
|
| 832 |
-
if (!raw) return;
|
| 833 |
-
const o = JSON.parse(raw);
|
| 834 |
-
if (o.baseUrl) document.getElementById('baseUrl').value = o.baseUrl;
|
| 835 |
-
if (o.apiKey) document.getElementById('apiKey').value = o.apiKey;
|
| 836 |
-
if (o.model) {
|
| 837 |
-
const sel = document.getElementById('defaultModel');
|
| 838 |
-
const opts = Array.from(sel.options).map(op => op.value);
|
| 839 |
-
if (opts.includes(o.model)) sel.value = o.model;
|
| 840 |
-
}
|
| 841 |
-
} catch {}
|
| 842 |
}
|
| 843 |
|
| 844 |
-
function
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
else if (sel.options.length) sel.selectedIndex = 0;
|
| 852 |
-
updateCodeExamples();
|
| 853 |
}
|
| 854 |
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 860 |
}
|
| 861 |
|
| 862 |
// ββ Init βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 863 |
applyI18n();
|
| 864 |
updateLangToggle();
|
| 865 |
loadStatus();
|
| 866 |
-
setupReactiveBindings();
|
| 867 |
loadAccounts();
|
| 868 |
</script>
|
| 869 |
</body></html>
|
|
|
|
| 119 |
<svg class="size-5 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 010 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 010-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
| 120 |
<h2 class="text-[0.95rem] font-bold" data-i18n="apiConfig">API Configuration</h2>
|
| 121 |
</div>
|
|
|
|
| 122 |
</div>
|
| 123 |
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
| 124 |
<!-- Base URL -->
|
| 125 |
<div class="space-y-1.5">
|
| 126 |
<label class="text-xs font-semibold text-slate-700 dark:text-text-main" data-i18n="baseProxyUrl">Base Proxy URL</label>
|
| 127 |
<div class="relative flex items-center">
|
| 128 |
+
<input id="baseUrl" class="w-full pl-3 pr-10 py-2.5 bg-slate-100 dark:bg-bg-dark border border-gray-200 dark:border-border-dark rounded-lg text-[0.78rem] font-mono text-slate-500 dark:text-text-dim outline-none cursor-default select-all" type="text" value="Loading..." readonly/>
|
| 129 |
<button onclick="copyField('baseUrl', this)" class="absolute right-2 p-1.5 text-slate-400 dark:text-text-dim hover:text-primary transition-colors rounded-md hover:bg-slate-100 dark:hover:bg-border-dark" data-i18n-title="copyUrl" title="Copy URL">
|
| 130 |
<svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
|
| 131 |
</button>
|
|
|
|
| 135 |
<div class="space-y-1.5">
|
| 136 |
<label class="text-xs font-semibold text-slate-700 dark:text-text-main" data-i18n="defaultModel">Default Model</label>
|
| 137 |
<div class="relative">
|
| 138 |
+
<input id="defaultModel" class="w-full pl-3 pr-3 py-2.5 bg-slate-100 dark:bg-bg-dark border border-gray-200 dark:border-border-dark rounded-lg text-[0.78rem] text-slate-500 dark:text-text-dim font-medium outline-none cursor-default" type="text" value="codex" readonly/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
</div>
|
| 140 |
</div>
|
| 141 |
<!-- API Key -->
|
|
|
|
| 145 |
<div class="absolute left-3 text-slate-400 dark:text-text-dim">
|
| 146 |
<svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"/></svg>
|
| 147 |
</div>
|
| 148 |
+
<input id="apiKey" class="w-full pl-10 pr-10 py-2.5 bg-slate-100 dark:bg-bg-dark border border-gray-200 dark:border-border-dark rounded-lg text-[0.78rem] font-mono text-slate-500 dark:text-text-dim outline-none cursor-default select-all tracking-wider" type="text" value="Loading..." readonly/>
|
| 149 |
<button onclick="copyField('apiKey', this)" class="absolute right-2 p-1.5 text-slate-400 dark:text-text-dim hover:text-primary transition-colors rounded-md hover:bg-slate-100 dark:hover:bg-border-dark" data-i18n-title="copyApiKey" title="Copy API Key">
|
| 150 |
<svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
|
| 151 |
</button>
|
|
|
|
| 225 |
ok: "OK",
|
| 226 |
resetsAt: "Resets at",
|
| 227 |
apiConfig: "API Configuration",
|
|
|
|
| 228 |
baseProxyUrl: "Base Proxy URL",
|
| 229 |
defaultModel: "Default Model",
|
| 230 |
yourApiKey: "Your API Key",
|
|
|
|
| 242 |
failedExchangeCode: "Failed to exchange code",
|
| 243 |
failedDeleteAccount: "Failed to delete account.",
|
| 244 |
networkError: "Network error: ",
|
| 245 |
+
copied: "Copied!",
|
| 246 |
+
copyFailed: "Failed",
|
| 247 |
footer: "\u00a9 2025 Codex Proxy. All rights reserved.",
|
| 248 |
},
|
| 249 |
zh: {
|
|
|
|
| 271 |
ok: "\u6b63\u5e38",
|
| 272 |
resetsAt: "\u91cd\u7f6e\u65f6\u95f4",
|
| 273 |
apiConfig: "API \u914d\u7f6e",
|
|
|
|
| 274 |
baseProxyUrl: "\u4ee3\u7406 URL",
|
| 275 |
defaultModel: "\u9ed8\u8ba4\u6a21\u578b",
|
| 276 |
yourApiKey: "API \u5bc6\u94a5",
|
|
|
|
| 288 |
failedExchangeCode: "\u6388\u6743\u7801\u4ea4\u6362\u5931\u8d25",
|
| 289 |
failedDeleteAccount: "\u5220\u9664\u8d26\u6237\u5931\u8d25\u3002",
|
| 290 |
networkError: "\u7f51\u7edc\u9519\u8bef\uff1a",
|
| 291 |
+
copied: "\u5df2\u590d\u5236\uff01",
|
| 292 |
+
copyFailed: "\u5931\u8d25",
|
| 293 |
footer: "\u00a9 2025 Codex Proxy\u3002\u4fdd\u7559\u6240\u6709\u6743\u5229\u3002",
|
| 294 |
},
|
| 295 |
};
|
|
|
|
| 618 |
document.getElementById('apiKey').value = serverApiKey;
|
| 619 |
|
| 620 |
await populateModelDropdown();
|
|
|
|
| 621 |
updateCodeExamples();
|
| 622 |
} catch (err) {
|
| 623 |
console.error('Status load error:', err);
|
|
|
|
| 625 |
}
|
| 626 |
|
| 627 |
async function populateModelDropdown() {
|
| 628 |
+
const el = document.getElementById('defaultModel');
|
| 629 |
try {
|
| 630 |
const resp = await fetch('/v1/models');
|
| 631 |
const data = await resp.json();
|
| 632 |
const models = data.data.map(m => m.id);
|
| 633 |
if (models.length > 0) {
|
| 634 |
+
const preferred = models.find(n => n.includes('5.3-codex')) || models[0];
|
| 635 |
+
el.value = preferred;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 636 |
}
|
| 637 |
} catch {
|
| 638 |
+
el.value = 'codex';
|
| 639 |
}
|
| 640 |
}
|
| 641 |
|
|
|
|
| 785 |
}
|
| 786 |
|
| 787 |
// ββ Copy βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 788 |
+
function fallbackCopy(text) {
|
| 789 |
+
const ta = document.createElement('textarea');
|
| 790 |
+
ta.value = text;
|
| 791 |
+
ta.style.cssText = 'position:fixed;left:-9999px;opacity:0';
|
| 792 |
+
document.body.appendChild(ta);
|
| 793 |
+
ta.select();
|
| 794 |
+
let ok = false;
|
| 795 |
+
try { ok = document.execCommand('copy'); } catch {}
|
| 796 |
+
document.body.removeChild(ta);
|
| 797 |
+
return ok;
|
| 798 |
}
|
| 799 |
|
| 800 |
+
async function clipboardCopy(text) {
|
| 801 |
+
if (navigator.clipboard?.writeText) {
|
| 802 |
+
try { await navigator.clipboard.writeText(text); return true; } catch {}
|
| 803 |
+
}
|
| 804 |
+
return fallbackCopy(text);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 805 |
}
|
| 806 |
|
| 807 |
+
async function copyField(id, btn) {
|
| 808 |
+
const el = document.getElementById(id);
|
| 809 |
+
const text = el.value !== undefined ? el.value : el.textContent;
|
| 810 |
+
const ok = await clipboardCopy(text);
|
| 811 |
+
btn.innerHTML = ok ? SVG_CHECK : '<svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>';
|
| 812 |
+
btn.classList.add(ok ? 'text-primary' : 'text-red-500');
|
| 813 |
+
setTimeout(() => { btn.innerHTML = SVG_COPY; btn.classList.remove('text-primary', 'text-red-500'); }, 2000);
|
|
|
|
|
|
|
| 814 |
}
|
| 815 |
|
| 816 |
+
async function copyCurrentCode() {
|
| 817 |
+
const key = `${currentProtocol}-${currentLang}`;
|
| 818 |
+
const code = window._codeExamples?.[key] || '';
|
| 819 |
+
const ok = await clipboardCopy(code);
|
| 820 |
+
const btn = document.querySelector('#codeContainer button');
|
| 821 |
+
if (!btn) return;
|
| 822 |
+
const label = btn.querySelector('span');
|
| 823 |
+
const svg = btn.querySelector('svg');
|
| 824 |
+
if (ok) {
|
| 825 |
+
if (label) label.textContent = t('copied');
|
| 826 |
+
btn.classList.add('bg-primary', 'hover:bg-primary-hover');
|
| 827 |
+
btn.classList.remove('bg-slate-700', 'hover:bg-slate-600');
|
| 828 |
+
} else {
|
| 829 |
+
if (label) label.textContent = t('copyFailed');
|
| 830 |
+
btn.classList.add('bg-red-600', 'hover:bg-red-700');
|
| 831 |
+
btn.classList.remove('bg-slate-700', 'hover:bg-slate-600');
|
| 832 |
+
}
|
| 833 |
+
setTimeout(() => {
|
| 834 |
+
if (label) label.textContent = t('copy');
|
| 835 |
+
btn.classList.remove('bg-primary', 'hover:bg-primary-hover', 'bg-red-600', 'hover:bg-red-700');
|
| 836 |
+
btn.classList.add('bg-slate-700', 'hover:bg-slate-600');
|
| 837 |
+
}, 2000);
|
| 838 |
}
|
| 839 |
|
| 840 |
// ββ Init βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 841 |
+
try { localStorage.removeItem('codex-proxy-config-overrides'); } catch {}
|
| 842 |
applyI18n();
|
| 843 |
updateLangToggle();
|
| 844 |
loadStatus();
|
|
|
|
| 845 |
loadAccounts();
|
| 846 |
</script>
|
| 847 |
</body></html>
|