ai-zero-token 1.0.5 → 1.0.7

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.
@@ -647,6 +647,19 @@ function renderAdminPage() {
647
647
  min-width: 156px;
648
648
  }
649
649
 
650
+ .account-selected-count {
651
+ min-height: 40px;
652
+ display: inline-flex;
653
+ align-items: center;
654
+ color: var(--text-muted);
655
+ font-size: 12px;
656
+ font-weight: 600;
657
+ }
658
+
659
+ .account-modal-body .textarea {
660
+ min-height: 280px;
661
+ }
662
+
650
663
  .account-grid {
651
664
  display: grid;
652
665
  grid-auto-rows: 1fr;
@@ -691,6 +704,30 @@ function renderAdminPage() {
691
704
  display: grid;
692
705
  gap: 6px;
693
706
  min-width: 0;
707
+ flex: 1;
708
+ }
709
+
710
+ .account-select {
711
+ display: inline-flex;
712
+ align-items: center;
713
+ gap: 6px;
714
+ min-height: 28px;
715
+ padding: 0 8px;
716
+ border: 1px solid var(--line);
717
+ border-radius: 8px;
718
+ background: #fff;
719
+ color: var(--text-muted);
720
+ font-size: 12px;
721
+ font-weight: 600;
722
+ cursor: pointer;
723
+ user-select: none;
724
+ white-space: nowrap;
725
+ }
726
+
727
+ .account-select input {
728
+ width: 14px;
729
+ height: 14px;
730
+ margin: 0;
694
731
  }
695
732
 
696
733
  .account-name {
@@ -1365,7 +1402,7 @@ function renderAdminPage() {
1365
1402
  <strong id="updatePanelTitle">\u53D1\u73B0\u65B0\u7248\u672C</strong>
1366
1403
  <span id="updatePanelDetail"></span>
1367
1404
  </div>
1368
- <code class="update-command" id="updatePanelCommand">npm install -g ai-zero-token@latest</code>
1405
+ <code class="update-command" id="updatePanelCommand">npm install -g ai-zero-token</code>
1369
1406
  </section>
1370
1407
 
1371
1408
  <section class="summary-grid" id="summaryGrid"></section>
@@ -1380,6 +1417,7 @@ function renderAdminPage() {
1380
1417
  </div>
1381
1418
  <div class="actions">
1382
1419
  <button class="btn-secondary" type="button" id="activateCurrentBtn">\u5B9A\u4F4D\u5F53\u524D\u8D26\u53F7</button>
1420
+ <button class="btn-secondary" type="button" id="exportSelectedProfilesBtn">\u5BFC\u51FA\u6240\u9009</button>
1383
1421
  </div>
1384
1422
  </div>
1385
1423
 
@@ -1398,8 +1436,8 @@ function renderAdminPage() {
1398
1436
  <option value="expiry-asc">\u6309\u8FC7\u671F\u65F6\u95F4</option>
1399
1437
  <option value="name-asc">\u6309\u90AE\u7BB1\u6392\u5E8F</option>
1400
1438
  </select>
1439
+ <span class="account-selected-count" id="selectedProfileCount">\u5DF2\u9009\u62E9 0 \u4E2A</span>
1401
1440
  </div>
1402
-
1403
1441
  <div class="account-grid" id="profileList"></div>
1404
1442
  </section>
1405
1443
 
@@ -1460,6 +1498,7 @@ function renderAdminPage() {
1460
1498
  <div class="tester-tabs" id="testerTabs">
1461
1499
  <button class="tab-btn is-active" type="button" data-endpoint="/v1/chat/completions">Chat</button>
1462
1500
  <button class="tab-btn" type="button" data-endpoint="/v1/images/generations">Images</button>
1501
+ <button class="tab-btn" type="button" data-endpoint="/v1/images/edits">Edits</button>
1463
1502
  <button class="tab-btn" type="button" data-endpoint="/v1/responses">Responses</button>
1464
1503
  <button class="tab-btn" type="button" data-endpoint="/v1/models">Models</button>
1465
1504
  </div>
@@ -1507,6 +1546,7 @@ function renderAdminPage() {
1507
1546
  <button class="btn-secondary" type="button" data-example="/v1/responses">\u793A\u4F8B Responses</button>
1508
1547
  <button class="btn-secondary" type="button" data-example="/v1/chat/completions">\u793A\u4F8B Chat</button>
1509
1548
  <button class="btn-secondary" type="button" data-example="/v1/images/generations">\u793A\u4F8B Images</button>
1549
+ <button class="btn-secondary" type="button" data-example="/v1/images/edits">\u793A\u4F8B Edits</button>
1510
1550
  <button class="btn-primary" id="runTestBtn" type="button">\u53D1\u9001\u8BF7\u6C42</button>
1511
1551
  </div>
1512
1552
 
@@ -1578,6 +1618,41 @@ function renderAdminPage() {
1578
1618
  </section>
1579
1619
  </div>
1580
1620
 
1621
+ <div class="modal-backdrop" id="accountModal" aria-hidden="true">
1622
+ <section class="modal-card" role="dialog" aria-modal="true" aria-labelledby="accountModalTitle">
1623
+ <div class="modal-head">
1624
+ <div>
1625
+ <h3 id="accountModalTitle">\u65B0\u589E\u8D26\u53F7</h3>
1626
+ <p>\u9009\u62E9 OAuth \u767B\u5F55\uFF0C\u6216\u7C98\u8D34\u5355\u4E2A/\u6279\u91CF\u8D26\u53F7 JSON \u5BFC\u5165\u3002</p>
1627
+ </div>
1628
+ <div class="actions">
1629
+ <button class="btn-secondary" id="closeAccountModalBtn" type="button">\u5173\u95ED</button>
1630
+ </div>
1631
+ </div>
1632
+ <div class="modal-body account-modal-body">
1633
+ <div class="contact-notes">
1634
+ <div class="contact-note">
1635
+ <strong>\u767B\u5F55\u65B0\u589E</strong>
1636
+ <span>\u6253\u5F00 OpenAI OAuth \u6388\u6743\u6D41\u7A0B\uFF0C\u767B\u5F55\u6210\u529F\u540E\u81EA\u52A8\u4FDD\u5B58\u5E76\u5207\u6362\u4E3A\u5F53\u524D\u8D26\u53F7\u3002</span>
1637
+ <button class="btn-primary" id="oauthLoginBtn" type="button">\u767B\u5F55</button>
1638
+ </div>
1639
+ <div class="contact-note">
1640
+ <strong>\u6279\u91CF\u5BFC\u5165</strong>
1641
+ <span>\u652F\u6301\u5355\u4E2A\u5BF9\u8C61\u3001\u5BF9\u8C61\u6570\u7EC4\uFF0C\u6216\u5305\u542B profiles \u6570\u7EC4\u7684\u5BF9\u8C61\u3002\u5BFC\u5165\u540E\u6700\u540E\u4E00\u4E2A\u8D26\u53F7\u4F1A\u6210\u4E3A\u5F53\u524D\u8D26\u53F7\u3002</span>
1642
+ <div class="actions">
1643
+ <button class="btn-secondary" id="loadImportTemplateBtn" type="button">\u586B\u5165\u53C2\u8003\u683C\u5F0F</button>
1644
+ <button class="btn-primary" id="importProfileBtn" type="button">\u5BFC\u5165</button>
1645
+ </div>
1646
+ </div>
1647
+ </div>
1648
+ <div>
1649
+ <textarea class="textarea" id="profileImportJson" spellcheck="false" placeholder='\u7C98\u8D34\u8D26\u53F7 JSON\uFF0C\u652F\u6301 { "profiles": [...] } \u6279\u91CF\u5BFC\u5165'></textarea>
1650
+ <p class="hint">\u5BFC\u5165\u548C\u5BFC\u51FA\u7684 JSON \u90FD\u5305\u542B\u5B8C\u6574 access token \u548C refresh token\uFF0C\u8BF7\u53EA\u5728\u53EF\u4FE1\u73AF\u5883\u4E2D\u5904\u7406\u3002</p>
1651
+ </div>
1652
+ </div>
1653
+ </section>
1654
+ </div>
1655
+
1581
1656
  <div class="modal-backdrop" id="contactModal" aria-hidden="true">
1582
1657
  <section class="modal-card" role="dialog" aria-modal="true" aria-labelledby="contactModalTitle">
1583
1658
  <div class="modal-head">
@@ -1622,6 +1697,7 @@ function renderAdminPage() {
1622
1697
  status: "all",
1623
1698
  sort: "quota-desc",
1624
1699
  },
1700
+ selectedProfileIds: {},
1625
1701
  testerResultTab: "response",
1626
1702
  };
1627
1703
 
@@ -1646,6 +1722,11 @@ function renderAdminPage() {
1646
1722
  tab: "Images",
1647
1723
  description: "\u517C\u5BB9 OpenAI images.generations \u63A5\u53E3\u3002",
1648
1724
  },
1725
+ "/v1/images/edits": {
1726
+ method: "POST",
1727
+ tab: "Edits",
1728
+ description: "\u517C\u5BB9 OpenAI images.edits JSON \u63A5\u53E3\u3002",
1729
+ },
1649
1730
  };
1650
1731
 
1651
1732
  const endpointSelect = document.getElementById("endpointSelect");
@@ -1659,6 +1740,7 @@ function renderAdminPage() {
1659
1740
  const imageCapabilityHint = document.getElementById("imageCapabilityHint");
1660
1741
  const runTestBtn = document.getElementById("runTestBtn");
1661
1742
  const toggleEmailBtn = document.getElementById("toggleEmailBtn");
1743
+ const accountModal = document.getElementById("accountModal");
1662
1744
  const contactModal = document.getElementById("contactModal");
1663
1745
  const imagePreviewModal = document.getElementById("imagePreviewModal");
1664
1746
  const contactBtn = document.getElementById("contactBtn");
@@ -1668,6 +1750,12 @@ function renderAdminPage() {
1668
1750
  const profileSearch = document.getElementById("profileSearch");
1669
1751
  const profileStatusFilter = document.getElementById("profileStatusFilter");
1670
1752
  const profileSort = document.getElementById("profileSort");
1753
+ const profileImportJson = document.getElementById("profileImportJson");
1754
+ const importProfileBtn = document.getElementById("importProfileBtn");
1755
+ const oauthLoginBtn = document.getElementById("oauthLoginBtn");
1756
+ const loadImportTemplateBtn = document.getElementById("loadImportTemplateBtn");
1757
+ const exportSelectedProfilesBtn = document.getElementById("exportSelectedProfilesBtn");
1758
+ const selectedProfileCount = document.getElementById("selectedProfileCount");
1671
1759
  const proxyEnabled = document.getElementById("proxyEnabled");
1672
1760
  const proxyUrl = document.getElementById("proxyUrl");
1673
1761
  const proxyNoProxy = document.getElementById("proxyNoProxy");
@@ -2244,6 +2332,21 @@ function renderAdminPage() {
2244
2332
  });
2245
2333
  }
2246
2334
 
2335
+ if (endpoint === "/v1/images/edits") {
2336
+ return formatJson({
2337
+ model: "gpt-image-2",
2338
+ prompt: "\u53C2\u8003\u8FD9\u5F20\u56FE\u7247\uFF0C\u751F\u6210\u4E00\u5F20\u66F4\u9002\u5408\u79D1\u6280\u4EA7\u54C1\u5E7F\u544A\u7684\u7248\u672C\uFF0C\u4FDD\u7559\u4E3B\u4F53\u6784\u56FE\uFF0C\u589E\u5F3A\u5149\u7EBF\u548C\u8D28\u611F\u3002",
2339
+ images: [
2340
+ {
2341
+ image_url: "data:image/png;base64,\u66FF\u6362\u4E3A\u4F60\u7684\u56FE\u7247base64",
2342
+ },
2343
+ ],
2344
+ size: "1024x1024",
2345
+ quality: "low",
2346
+ response_format: "b64_json",
2347
+ });
2348
+ }
2349
+
2247
2350
  return formatJson({
2248
2351
  model: model,
2249
2352
  input: "\u8BF7\u53EA\u56DE\u590D OK",
@@ -2255,6 +2358,10 @@ function renderAdminPage() {
2255
2358
  const avg = requests.length
2256
2359
  ? requests.reduce(function (sum, item) { return sum + (item.durationMs || 0); }, 0) / requests.length
2257
2360
  : 0;
2361
+ const codexAccountId = config.codex && config.codex.accountId ? config.codex.accountId : "";
2362
+ const codexProfile = codexAccountId && Array.isArray(config.profiles)
2363
+ ? config.profiles.find(function (profile) { return profile.accountId === codexAccountId; })
2364
+ : null;
2258
2365
 
2259
2366
  return [
2260
2367
  {
@@ -2276,6 +2383,12 @@ function renderAdminPage() {
2276
2383
  detail: "\u672A\u663E\u5F0F\u6307\u5B9A model \u65F6\u751F\u6548",
2277
2384
  compact: true,
2278
2385
  },
2386
+ {
2387
+ label: "Codex \u5F53\u524D\u8D26\u53F7",
2388
+ value: codexProfile ? getProfileDisplayLabel(codexProfile) : (codexAccountId ? maskIdentifier(codexAccountId) : "\u672A\u68C0\u6D4B\u5230"),
2389
+ detail: config.codex && config.codex.exists ? "\u6765\u81EA ~/.codex/auth.json" : "\u5C1A\u672A\u5E94\u7528\u5230 Codex",
2390
+ compact: true,
2391
+ },
2279
2392
  {
2280
2393
  label: "\u5F53\u524D\u7248\u672C",
2281
2394
  value: getVersionValue(config),
@@ -2369,8 +2482,35 @@ function renderAdminPage() {
2369
2482
  return filtered;
2370
2483
  }
2371
2484
 
2485
+ function getSelectedProfileIds() {
2486
+ return Object.keys(state.selectedProfileIds).filter(function (profileId) {
2487
+ return !!state.selectedProfileIds[profileId];
2488
+ });
2489
+ }
2490
+
2491
+ function syncSelectedProfiles(config) {
2492
+ const profiles = Array.isArray(config.profiles) ? config.profiles : [];
2493
+ const availableIds = profiles.reduce(function (result, profile) {
2494
+ result[profile.profileId] = true;
2495
+ return result;
2496
+ }, {});
2497
+ getSelectedProfileIds().forEach(function (profileId) {
2498
+ if (!availableIds[profileId]) {
2499
+ delete state.selectedProfileIds[profileId];
2500
+ }
2501
+ });
2502
+ }
2503
+
2504
+ function updateSelectedProfileControls() {
2505
+ const count = getSelectedProfileIds().length;
2506
+ selectedProfileCount.textContent = "\u5DF2\u9009\u62E9 " + String(count) + " \u4E2A";
2507
+ exportSelectedProfilesBtn.disabled = count === 0;
2508
+ }
2509
+
2372
2510
  function renderProfiles(config) {
2373
2511
  const container = document.getElementById("profileList");
2512
+ syncSelectedProfiles(config);
2513
+ updateSelectedProfileControls();
2374
2514
  const profiles = getFilteredProfiles(config);
2375
2515
  const gridClass = profiles.length <= 0
2376
2516
  ? ""
@@ -2389,6 +2529,7 @@ function renderAdminPage() {
2389
2529
  }
2390
2530
 
2391
2531
  container.innerHTML = profiles.map(function (profile) {
2532
+ const selected = !!state.selectedProfileIds[profile.profileId];
2392
2533
  const isSingleProfile = profiles.length === 1;
2393
2534
  const health = getProfileHealth(profile);
2394
2535
  const planType = getPlanType(profile);
@@ -2418,6 +2559,7 @@ function renderAdminPage() {
2418
2559
  + '<span class="badge ' + escapeHtml(imageCapability.badgeClass) + '">' + escapeHtml(imageCapability.label) + "</span>"
2419
2560
  + "</div>"
2420
2561
  + "</div>"
2562
+ + '<label class="account-select"><input type="checkbox" data-profile-select data-profile-id="' + escapeHtml(profile.profileId) + '"' + (selected ? " checked" : "") + " /><span>\u9009\u62E9</span></label>"
2421
2563
  + "</div>"
2422
2564
  + '<div class="account-metrics">'
2423
2565
  + '<div class="quota-row">'
@@ -2440,6 +2582,8 @@ function renderAdminPage() {
2440
2582
  + "</div>"
2441
2583
  + '<div class="account-actions">'
2442
2584
  + actionButton
2585
+ + '<button class="btn-secondary" type="button" data-profile-action="apply-codex" data-profile-id="' + escapeHtml(profile.profileId) + '">\u5E94\u7528\u5230 Codex</button>'
2586
+ + '<button class="btn-secondary" type="button" data-profile-action="export" data-profile-id="' + escapeHtml(profile.profileId) + '">\u5BFC\u51FA</button>'
2443
2587
  + '<button class="btn-danger" type="button" data-profile-action="remove" data-profile-id="' + escapeHtml(profile.profileId) + '">\u5220\u9664</button>'
2444
2588
  + "</div>"
2445
2589
  + "</article>";
@@ -2450,13 +2594,15 @@ function renderAdminPage() {
2450
2594
  const profiles = Array.isArray(config.profiles) ? config.profiles : [];
2451
2595
  const now = Date.now();
2452
2596
  const seeds = profiles.slice(0, 5).map(function (profile, index) {
2453
- const endpoint = index % 4 === 0
2597
+ const endpoint = index % 5 === 0
2454
2598
  ? "/v1/chat/completions"
2455
- : index % 4 === 1
2599
+ : index % 5 === 1
2456
2600
  ? "/v1/responses"
2457
- : index % 4 === 2
2601
+ : index % 5 === 2
2458
2602
  ? "/v1/models"
2459
- : "/v1/images/generations";
2603
+ : index % 5 === 3
2604
+ ? "/v1/images/generations"
2605
+ : "/v1/images/edits";
2460
2606
  const method = endpointMeta[endpoint].method;
2461
2607
  return {
2462
2608
  time: now - index * 15 * 60 * 1000,
@@ -2464,7 +2610,7 @@ function renderAdminPage() {
2464
2610
  endpoint: endpoint,
2465
2611
  accountEmail: profile.email || "",
2466
2612
  accountFallback: profile.accountId || profile.profileId || "\u672A\u547D\u540D\u8D26\u53F7",
2467
- model: endpoint === "/v1/images/generations" ? "gpt-image-2" : config.settings.defaultModel,
2613
+ model: endpoint.indexOf("/v1/images/") === 0 ? "gpt-image-2" : config.settings.defaultModel,
2468
2614
  statusCode: 200,
2469
2615
  durationMs: 860 + index * 230 + getPrimaryUsage(profile) * 8,
2470
2616
  source: index % 2 === 0 ? "\u7BA1\u7406\u9875" : "CLI",
@@ -2695,7 +2841,7 @@ function renderAdminPage() {
2695
2841
 
2696
2842
  function syncImageCapabilityHint(config) {
2697
2843
  const capability = getImageCapability(config ? config.profile : null);
2698
- const isImageEndpoint = endpointSelect.value === "/v1/images/generations";
2844
+ const isImageEndpoint = endpointSelect.value === "/v1/images/generations" || endpointSelect.value === "/v1/images/edits";
2699
2845
  imageCapabilityHint.textContent = capability.detail;
2700
2846
  imageCapabilityHint.className = capability.supported && !isImageEndpoint ? "hint" : "hint warn";
2701
2847
  runTestBtn.disabled = isImageEndpoint && !capability.supported;
@@ -2832,7 +2978,7 @@ function renderAdminPage() {
2832
2978
  }
2833
2979
 
2834
2980
  async function login() {
2835
- const button = document.getElementById("loginBtn");
2981
+ const button = oauthLoginBtn;
2836
2982
  setBusy(button, true);
2837
2983
  authStatus.textContent = "\u6B63\u5728\u65B0\u589E\u8D26\u53F7\u3001\u7B49\u5F85 OAuth \u5B8C\u6210\uFF0C\u5E76\u540C\u6B65\u989D\u5EA6\u4FE1\u606F...";
2838
2984
  try {
@@ -2845,6 +2991,7 @@ function renderAdminPage() {
2845
2991
  if (config.profile && config.profile.quota) {
2846
2992
  authStatus.textContent = "\u8D26\u53F7\u5DF2\u4FDD\u5B58\uFF0C\u5DF2\u5207\u6362\u4E3A\u5F53\u524D\u4F7F\u7528\u8D26\u53F7\u5E76\u540C\u6B65\u989D\u5EA6\u4FE1\u606F: " + getProfileDisplayLabel(config.profile);
2847
2993
  }
2994
+ closeAccountModal();
2848
2995
  } catch (error) {
2849
2996
  authStatus.textContent = error.message;
2850
2997
  } finally {
@@ -2871,6 +3018,15 @@ function renderAdminPage() {
2871
3018
  }
2872
3019
 
2873
3020
  async function runProfileAction(action, profileId, button) {
3021
+ if (action === "export") {
3022
+ await exportProfile(profileId, button);
3023
+ return;
3024
+ }
3025
+ if (action === "apply-codex") {
3026
+ await applyProfileToCodex(profileId, button);
3027
+ return;
3028
+ }
3029
+
2874
3030
  setBusy(button, true);
2875
3031
  authStatus.textContent = action === "activate" ? "\u6B63\u5728\u5207\u6362\u5F53\u524D\u8D26\u53F7..." : "\u6B63\u5728\u5220\u9664\u8D26\u53F7...";
2876
3032
  try {
@@ -2903,6 +3059,141 @@ function renderAdminPage() {
2903
3059
  }
2904
3060
  }
2905
3061
 
3062
+ async function applyProfileToCodex(profileId, button) {
3063
+ setBusy(button, true);
3064
+ authStatus.textContent = "\u6B63\u5728\u5E94\u7528\u8D26\u53F7\u5230 Codex...";
3065
+ try {
3066
+ const result = await fetchJson("/_gateway/admin/codex/apply", {
3067
+ method: "POST",
3068
+ headers: {
3069
+ "Content-Type": "application/json",
3070
+ },
3071
+ body: formatJson({
3072
+ profileId: profileId,
3073
+ }),
3074
+ });
3075
+ const config = result.config || await fetchJson("/_gateway/admin/config");
3076
+ renderConfig(config);
3077
+ const codex = result.codex || config.codex || {};
3078
+ authStatus.textContent = "\u5DF2\u5E94\u7528\u5230 Codex\u3002\u65B0\u5F00\u7684 Codex \u4F1A\u8BDD\u5C06\u4F7F\u7528\u8BE5\u8D26\u53F7\u3002"
3079
+ + (codex.backupPath ? " \u5DF2\u5907\u4EFD\u539F auth.json\u3002" : "");
3080
+ } catch (error) {
3081
+ authStatus.textContent = error.message;
3082
+ } finally {
3083
+ setBusy(button, false);
3084
+ }
3085
+ }
3086
+
3087
+ function downloadJsonFile(fileName, value) {
3088
+ const blob = new Blob([formatJson(value) + "\\n"], { type: "application/json" });
3089
+ const url = URL.createObjectURL(blob);
3090
+ const link = document.createElement("a");
3091
+ link.href = url;
3092
+ link.download = fileName;
3093
+ document.body.appendChild(link);
3094
+ link.click();
3095
+ link.remove();
3096
+ URL.revokeObjectURL(url);
3097
+ }
3098
+
3099
+ async function importProfile() {
3100
+ const raw = profileImportJson.value.trim();
3101
+ if (!raw) {
3102
+ authStatus.textContent = "\u8BF7\u5148\u7C98\u8D34\u8981\u5BFC\u5165\u7684\u8D26\u53F7 JSON\u3002";
3103
+ return;
3104
+ }
3105
+
3106
+ let payload;
3107
+ try {
3108
+ payload = JSON.parse(raw);
3109
+ } catch (_error) {
3110
+ authStatus.textContent = "\u5BFC\u5165\u5931\u8D25: JSON \u683C\u5F0F\u4E0D\u6B63\u786E\u3002";
3111
+ return;
3112
+ }
3113
+
3114
+ setBusy(importProfileBtn, true);
3115
+ authStatus.textContent = "\u6B63\u5728\u5BFC\u5165\u8D26\u53F7\u5E76\u540C\u6B65\u989D\u5EA6\u4FE1\u606F...";
3116
+ try {
3117
+ let config = await fetchJson("/_gateway/admin/profiles/import", {
3118
+ method: "POST",
3119
+ headers: {
3120
+ "Content-Type": "application/json",
3121
+ },
3122
+ body: formatJson({
3123
+ profile: payload,
3124
+ }),
3125
+ });
3126
+ renderConfig(config);
3127
+ const count = config.importedProfileCount || 1;
3128
+ const baseMessage = "\u5DF2\u5BFC\u5165 " + String(count) + " \u4E2A\u8D26\u53F7\uFF0C\u5E76\u5DF2\u5207\u6362\u4E3A\u5F53\u524D\u4F7F\u7528\u8D26\u53F7: " + getProfileDisplayLabel(config.profile);
3129
+ config = await syncQuotaAfterProfileChange(config, baseMessage);
3130
+ if (config.profile && config.profile.quota) {
3131
+ authStatus.textContent = "\u5DF2\u5BFC\u5165 " + String(count) + " \u4E2A\u8D26\u53F7\uFF0C\u989D\u5EA6\u4FE1\u606F\u5DF2\u540C\u6B65: " + getProfileDisplayLabel(config.profile);
3132
+ }
3133
+ profileImportJson.value = "";
3134
+ closeAccountModal();
3135
+ } catch (error) {
3136
+ authStatus.textContent = error.message;
3137
+ } finally {
3138
+ setBusy(importProfileBtn, false);
3139
+ }
3140
+ }
3141
+
3142
+ async function loadImportTemplate() {
3143
+ setBusy(loadImportTemplateBtn, true);
3144
+ authStatus.textContent = "\u6B63\u5728\u8BFB\u53D6\u5BFC\u5165\u53C2\u8003\u683C\u5F0F...";
3145
+ try {
3146
+ const result = await fetchJson("/_gateway/admin/profiles/import-template");
3147
+ profileImportJson.value = formatJson(result.profile);
3148
+ authStatus.textContent = "\u5DF2\u586B\u5165\u53C2\u8003\u683C\u5F0F\uFF0C\u53EF\u66FF\u6362\u5176\u4E2D\u7684 token \u540E\u5BFC\u5165\u3002";
3149
+ } catch (error) {
3150
+ authStatus.textContent = error.message;
3151
+ } finally {
3152
+ setBusy(loadImportTemplateBtn, false);
3153
+ }
3154
+ }
3155
+
3156
+ async function exportProfile(profileId, button, options) {
3157
+ setBusy(button, true);
3158
+ authStatus.textContent = "\u6B63\u5728\u5BFC\u51FA\u8D26\u53F7\u914D\u7F6E...";
3159
+ try {
3160
+ const exportAll = !!(options && options.all);
3161
+ const profileIds = options && Array.isArray(options.profileIds) ? options.profileIds : null;
3162
+ const result = await fetchJson("/_gateway/admin/profiles/export", {
3163
+ method: "POST",
3164
+ headers: {
3165
+ "Content-Type": "application/json",
3166
+ },
3167
+ body: formatJson(profileIds ? { profileIds: profileIds } : exportAll ? { all: true } : { profileId: profileId }),
3168
+ });
3169
+ const profile = result.profile;
3170
+ const isBundle = profile && Array.isArray(profile.profiles);
3171
+ const suffix = isBundle
3172
+ ? "profiles-" + String(profile.profiles.length)
3173
+ : profile && profile.account_id ? profile.account_id : "active";
3174
+ downloadJsonFile("ai-zero-token-" + suffix + ".json", profile);
3175
+ authStatus.textContent = isBundle
3176
+ ? "\u5DF2\u6279\u91CF\u5BFC\u51FA " + String(profile.profiles.length) + " \u4E2A\u8D26\u53F7\u3002\u8BF7\u59A5\u5584\u4FDD\u7BA1\u5BFC\u51FA\u7684 refresh token\u3002"
3177
+ : "\u8D26\u53F7\u914D\u7F6E\u5DF2\u5BFC\u51FA\u3002\u8BF7\u59A5\u5584\u4FDD\u7BA1\u5BFC\u51FA\u7684 refresh token\u3002";
3178
+ } catch (error) {
3179
+ authStatus.textContent = error.message;
3180
+ } finally {
3181
+ setBusy(button, false);
3182
+ }
3183
+ }
3184
+
3185
+ async function exportSelectedProfiles() {
3186
+ const profileIds = getSelectedProfileIds();
3187
+ if (profileIds.length === 0) {
3188
+ authStatus.textContent = "\u8BF7\u5148\u52FE\u9009\u8981\u5BFC\u51FA\u7684\u8D26\u53F7\u3002";
3189
+ return;
3190
+ }
3191
+
3192
+ await exportProfile(null, exportSelectedProfilesBtn, {
3193
+ profileIds: profileIds,
3194
+ });
3195
+ }
3196
+
2906
3197
  async function saveModel() {
2907
3198
  const button = document.getElementById("saveModelBtn");
2908
3199
  const select = document.getElementById("defaultModel");
@@ -3081,6 +3372,16 @@ function renderAdminPage() {
3081
3372
  }
3082
3373
  }
3083
3374
 
3375
+ function openAccountModal() {
3376
+ accountModal.classList.add("is-open");
3377
+ accountModal.setAttribute("aria-hidden", "false");
3378
+ }
3379
+
3380
+ function closeAccountModal() {
3381
+ accountModal.classList.remove("is-open");
3382
+ accountModal.setAttribute("aria-hidden", "true");
3383
+ }
3384
+
3084
3385
  function openContactModal() {
3085
3386
  contactModal.classList.add("is-open");
3086
3387
  contactModal.setAttribute("aria-hidden", "false");
@@ -3091,7 +3392,7 @@ function renderAdminPage() {
3091
3392
  contactModal.setAttribute("aria-hidden", "true");
3092
3393
  }
3093
3394
 
3094
- document.getElementById("loginBtn").addEventListener("click", login);
3395
+ document.getElementById("loginBtn").addEventListener("click", openAccountModal);
3095
3396
  document.getElementById("refreshBtn").addEventListener("click", function () {
3096
3397
  authStatus.textContent = "\u6B63\u5728\u540C\u6B65\u989D\u5EA6\u4E0E\u7248\u672C\u72B6\u6001...";
3097
3398
  refreshConfig({
@@ -3103,6 +3404,11 @@ function renderAdminPage() {
3103
3404
  });
3104
3405
  });
3105
3406
  document.getElementById("logoutBtn").addEventListener("click", logout);
3407
+ oauthLoginBtn.addEventListener("click", login);
3408
+ importProfileBtn.addEventListener("click", importProfile);
3409
+ loadImportTemplateBtn.addEventListener("click", loadImportTemplate);
3410
+ exportSelectedProfilesBtn.addEventListener("click", exportSelectedProfiles);
3411
+ document.getElementById("closeAccountModalBtn").addEventListener("click", closeAccountModal);
3106
3412
  contactBtn.addEventListener("click", openContactModal);
3107
3413
  document.getElementById("closeContactBtn").addEventListener("click", closeContactModal);
3108
3414
  document.getElementById("closeImagePreviewBtn").addEventListener("click", closeImagePreviewModal);
@@ -3124,6 +3430,10 @@ function renderAdminPage() {
3124
3430
  });
3125
3431
 
3126
3432
  document.getElementById("profileList").addEventListener("click", function (event) {
3433
+ if (event.target.closest("[data-profile-select]")) {
3434
+ return;
3435
+ }
3436
+
3127
3437
  const button = event.target.closest("[data-profile-action]");
3128
3438
  if (!button) {
3129
3439
  return;
@@ -3138,6 +3448,25 @@ function renderAdminPage() {
3138
3448
  runProfileAction(action, profileId, button);
3139
3449
  });
3140
3450
 
3451
+ document.getElementById("profileList").addEventListener("change", function (event) {
3452
+ const checkbox = event.target.closest("[data-profile-select]");
3453
+ if (!checkbox) {
3454
+ return;
3455
+ }
3456
+
3457
+ const profileId = checkbox.getAttribute("data-profile-id");
3458
+ if (!profileId) {
3459
+ return;
3460
+ }
3461
+
3462
+ if (checkbox.checked) {
3463
+ state.selectedProfileIds[profileId] = true;
3464
+ } else {
3465
+ delete state.selectedProfileIds[profileId];
3466
+ }
3467
+ updateSelectedProfileControls();
3468
+ });
3469
+
3141
3470
  document.getElementById("activateCurrentBtn").addEventListener("click", function () {
3142
3471
  if (!state.config || !state.config.profile || !state.config.profile.profileId) {
3143
3472
  return;
@@ -3209,6 +3538,12 @@ function renderAdminPage() {
3209
3538
  }
3210
3539
  });
3211
3540
 
3541
+ accountModal.addEventListener("click", function (event) {
3542
+ if (event.target === accountModal) {
3543
+ closeAccountModal();
3544
+ }
3545
+ });
3546
+
3212
3547
  imagePreviewModal.addEventListener("click", function (event) {
3213
3548
  if (event.target === imagePreviewModal) {
3214
3549
  closeImagePreviewModal();
@@ -3222,6 +3557,9 @@ function renderAdminPage() {
3222
3557
  if (event.key === "Escape" && contactModal.classList.contains("is-open")) {
3223
3558
  closeContactModal();
3224
3559
  }
3560
+ if (event.key === "Escape" && accountModal.classList.contains("is-open")) {
3561
+ closeAccountModal();
3562
+ }
3225
3563
  });
3226
3564
 
3227
3565
  setTesterResultTab(state.testerResultTab);