ai-zero-token 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +13 -5
  3. package/README.zh-CN.md +13 -5
  4. package/admin-ui/dist/assets/accounts-DymL4WIa.js +2 -0
  5. package/admin-ui/dist/assets/{docs-CihX3Xsm.js → docs-DtO-AOWU.js} +1 -1
  6. package/admin-ui/dist/assets/{image-bed-BGjlDLks.js → image-bed-yIVQ4dKs.js} +1 -1
  7. package/admin-ui/dist/assets/index-By4r-wy3.css +1 -0
  8. package/admin-ui/dist/assets/index-DRe-tByu.js +10 -0
  9. package/admin-ui/dist/assets/{launch-BWw7Odq7.js → launch-CQXYrl-h.js} +1 -1
  10. package/admin-ui/dist/assets/{logs-DDdgDVwo.js → logs-awABDg1C.js} +1 -1
  11. package/admin-ui/dist/assets/{network-detect-cUdjg4zk.js → network-detect-sSrnwZqf.js} +1 -1
  12. package/admin-ui/dist/assets/{overview-CsjVVcvi.js → overview-BbSON0Jl.js} +1 -1
  13. package/admin-ui/dist/assets/settings-DvRiHS7i.js +1 -0
  14. package/admin-ui/dist/assets/{tester-BIvH_8DY.js → tester-CftPgRE9.js} +1 -1
  15. package/admin-ui/dist/index.html +2 -2
  16. package/build/mac-install-guide.txt +25 -0
  17. package/dist/core/services/auth-service.js +88 -12
  18. package/dist/core/services/config-service.js +87 -23
  19. package/dist/core/services/version-service.js +1 -1
  20. package/dist/core/store/profile-store.js +73 -32
  21. package/dist/core/store/settings-store.js +14 -0
  22. package/dist/server/app.js +63 -16
  23. package/docs/DESKTOP_RELEASE.md +35 -3
  24. package/package.json +31 -2
  25. package/admin-ui/dist/assets/accounts-Ddq82u6R.js +0 -1
  26. package/admin-ui/dist/assets/index-CX8e0-BB.js +0 -10
  27. package/admin-ui/dist/assets/index-ywn2Jwpu.css +0 -1
  28. package/admin-ui/dist/assets/settings-Be99HpDD.js +0 -1
@@ -6,6 +6,7 @@ import {
6
6
  getStorePath
7
7
  } from "./state-paths.js";
8
8
  const PROFILE_CLAIM_PATH = "https://api.openai.com/profile";
9
+ let storeMutationQueue = Promise.resolve();
9
10
  function createEmptyStore() {
10
11
  return {
11
12
  version: 1,
@@ -73,36 +74,50 @@ async function saveStore(store) {
73
74
  await fs.writeFile(getStorePath(), `${JSON.stringify(store, null, 2)}
74
75
  `, "utf8");
75
76
  }
77
+ async function withStoreMutation(operation) {
78
+ const run = storeMutationQueue.then(operation, operation);
79
+ storeMutationQueue = run.then(
80
+ () => void 0,
81
+ () => void 0
82
+ );
83
+ return run;
84
+ }
76
85
  async function saveProfile(profile) {
77
- const store = await loadStore();
78
- store.profiles[profile.profileId] = profile;
79
- store.activeProfileId = profile.profileId;
80
- await saveStore(store);
86
+ await withStoreMutation(async () => {
87
+ const store = await loadStore();
88
+ store.profiles[profile.profileId] = profile;
89
+ store.activeProfileId = profile.profileId;
90
+ await saveStore(store);
91
+ });
81
92
  }
82
93
  async function updateProfile(profileId, updater) {
83
- const store = await loadStore();
84
- const profile = store.profiles[profileId];
85
- if (!profile) {
86
- return null;
87
- }
88
- const updated = updater(profile);
89
- store.profiles[profileId] = updated;
90
- await saveStore(store);
91
- return updated;
94
+ return withStoreMutation(async () => {
95
+ const store = await loadStore();
96
+ const profile = store.profiles[profileId];
97
+ if (!profile) {
98
+ return null;
99
+ }
100
+ const updated = updater(profile);
101
+ store.profiles[profileId] = updated;
102
+ await saveStore(store);
103
+ return updated;
104
+ });
92
105
  }
93
106
  async function listProfiles() {
94
107
  const store = await loadStore();
95
108
  return Object.values(store.profiles);
96
109
  }
97
110
  async function setActiveProfile(profileId) {
98
- const store = await loadStore();
99
- const profile = store.profiles[profileId];
100
- if (!profile) {
101
- return null;
102
- }
103
- store.activeProfileId = profileId;
104
- await saveStore(store);
105
- return profile;
111
+ return withStoreMutation(async () => {
112
+ const store = await loadStore();
113
+ const profile = store.profiles[profileId];
114
+ if (!profile) {
115
+ return null;
116
+ }
117
+ store.activeProfileId = profileId;
118
+ await saveStore(store);
119
+ return profile;
120
+ });
106
121
  }
107
122
  async function getActiveProfile() {
108
123
  const store = await loadStore();
@@ -114,19 +129,44 @@ async function getActiveProfile() {
114
129
  return first ?? null;
115
130
  }
116
131
  async function removeProfile(profileId) {
117
- const store = await loadStore();
118
- if (!store.profiles[profileId]) {
119
- return null;
120
- }
121
- delete store.profiles[profileId];
122
- if (store.activeProfileId === profileId) {
123
- store.activeProfileId = Object.keys(store.profiles)[0];
124
- }
125
- await saveStore(store);
126
- return store.activeProfileId ? store.profiles[store.activeProfileId] ?? null : null;
132
+ return withStoreMutation(async () => {
133
+ const store = await loadStore();
134
+ if (!store.profiles[profileId]) {
135
+ return null;
136
+ }
137
+ delete store.profiles[profileId];
138
+ if (store.activeProfileId === profileId) {
139
+ store.activeProfileId = Object.keys(store.profiles)[0];
140
+ }
141
+ await saveStore(store);
142
+ return store.activeProfileId ? store.profiles[store.activeProfileId] ?? null : null;
143
+ });
144
+ }
145
+ async function removeProfiles(profileIds) {
146
+ return withStoreMutation(async () => {
147
+ const idSet = new Set(profileIds.map((id) => id.trim()).filter(Boolean));
148
+ if (idSet.size === 0) {
149
+ return 0;
150
+ }
151
+ const store = await loadStore();
152
+ let removed = 0;
153
+ for (const profileId of idSet) {
154
+ if (store.profiles[profileId]) {
155
+ delete store.profiles[profileId];
156
+ removed += 1;
157
+ }
158
+ }
159
+ if (store.activeProfileId && !store.profiles[store.activeProfileId]) {
160
+ store.activeProfileId = Object.keys(store.profiles)[0];
161
+ }
162
+ await saveStore(store);
163
+ return removed;
164
+ });
127
165
  }
128
166
  async function clearStore() {
129
- await fs.rm(getStateDir(), { recursive: true, force: true });
167
+ await withStoreMutation(async () => {
168
+ await fs.rm(getStateDir(), { recursive: true, force: true });
169
+ });
130
170
  }
131
171
  export {
132
172
  clearStore,
@@ -136,6 +176,7 @@ export {
136
176
  listProfiles,
137
177
  loadStore,
138
178
  removeProfile,
179
+ removeProfiles,
139
180
  saveProfile,
140
181
  saveStore,
141
182
  setActiveProfile,
@@ -18,6 +18,9 @@ function createDefaultSettings() {
18
18
  autoSwitch: {
19
19
  enabled: false
20
20
  },
21
+ runtime: {
22
+ quotaSyncConcurrency: 16
23
+ },
21
24
  server: {
22
25
  host: "0.0.0.0",
23
26
  port: 8787
@@ -42,6 +45,9 @@ async function loadSettings() {
42
45
  autoSwitch: {
43
46
  enabled: parsed.autoSwitch?.enabled ?? defaults.autoSwitch.enabled
44
47
  },
48
+ runtime: {
49
+ quotaSyncConcurrency: normalizeQuotaSyncConcurrency(parsed.runtime?.quotaSyncConcurrency, defaults.runtime.quotaSyncConcurrency)
50
+ },
45
51
  server: {
46
52
  host: parsed.server?.host ?? defaults.server.host,
47
53
  port: parsed.server?.port ?? defaults.server.port
@@ -51,6 +57,13 @@ async function loadSettings() {
51
57
  return createDefaultSettings();
52
58
  }
53
59
  }
60
+ function normalizeQuotaSyncConcurrency(value, fallback = 16) {
61
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : fallback;
62
+ if (!Number.isFinite(parsed)) {
63
+ return fallback;
64
+ }
65
+ return Math.min(32, Math.max(1, Math.trunc(parsed)));
66
+ }
54
67
  async function saveSettings(settings) {
55
68
  await ensureStateMigrated();
56
69
  await fs.mkdir(getStateDir(), { recursive: true });
@@ -61,5 +74,6 @@ export {
61
74
  createDefaultSettings,
62
75
  getSettingsPath,
63
76
  loadSettings,
77
+ normalizeQuotaSyncConcurrency,
64
78
  saveSettings
65
79
  };
@@ -115,6 +115,9 @@ const settingsUpdateSchema = z.object({
115
115
  autoSwitch: z.object({
116
116
  enabled: z.boolean()
117
117
  }).optional(),
118
+ runtime: z.object({
119
+ quotaSyncConcurrency: z.number().int().min(1).max(32).optional()
120
+ }).optional(),
118
121
  server: z.object({
119
122
  port: z.number().int().min(1).max(65535)
120
123
  }).optional()
@@ -129,9 +132,15 @@ const proxyTestSchema = z.object({
129
132
  const profileActionSchema = z.object({
130
133
  profileId: z.string().min(1)
131
134
  });
135
+ const profileRemoveBatchSchema = z.object({
136
+ profileIds: z.array(z.string().min(1)).min(1)
137
+ });
132
138
  const profileImportSchema = z.object({
133
139
  profile: z.unknown()
134
140
  });
141
+ const runtimeRefreshSchema = z.object({
142
+ staleOnly: z.boolean().optional()
143
+ });
135
144
  const profileExportSchema = z.object({
136
145
  profileId: z.string().min(1).optional(),
137
146
  profileIds: z.array(z.string().min(1)).optional(),
@@ -438,6 +447,7 @@ function serializeProfile(profile) {
438
447
  email: profile.email,
439
448
  quota: profile.quota,
440
449
  authStatus: profile.authStatus,
450
+ exportAudit: profile.exportAudit,
441
451
  expiresAt: profile.expires,
442
452
  accessTokenPreview: maskSecret(profile.access),
443
453
  refreshTokenPreview: maskSecret(profile.refresh)
@@ -451,6 +461,7 @@ function serializeManagedProfile(profile) {
451
461
  email: profile.email,
452
462
  quota: profile.quota,
453
463
  authStatus: profile.authStatus,
464
+ exportAudit: profile.exportAudit,
454
465
  expiresAt: profile.expiresAt,
455
466
  accessTokenPreview: profile.accessTokenPreview,
456
467
  refreshTokenPreview: profile.refreshTokenPreview,
@@ -612,10 +623,21 @@ function createApp(params) {
612
623
  catalog: result.catalog
613
624
  };
614
625
  });
615
- app.post("/_gateway/admin/runtime-refresh", async (request) => {
626
+ app.post("/_gateway/admin/runtime-refresh", async (request, reply) => {
627
+ const parsed = runtimeRefreshSchema.safeParse(request.body ?? {});
628
+ if (!parsed.success) {
629
+ reply.code(400);
630
+ return {
631
+ error: {
632
+ type: "validation_error",
633
+ message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
634
+ }
635
+ };
636
+ }
616
637
  const [quotaSync] = await Promise.all([
617
638
  ctx.authService.syncAllProfileQuotas("openai-codex", {
618
- suppressErrors: true
639
+ suppressErrors: true,
640
+ staleAfterMs: parsed.data.staleOnly ? 30 * 60 * 1e3 : void 0
619
641
  }),
620
642
  ctx.versionService.getVersionStatus({
621
643
  force: true
@@ -688,6 +710,23 @@ function createApp(params) {
688
710
  await ctx.authService.removeProfile(parsed.data.profileId);
689
711
  return buildAdminConfig(request);
690
712
  });
713
+ app.post("/_gateway/admin/profiles/remove-batch", async (request, reply) => {
714
+ const parsed = profileRemoveBatchSchema.safeParse(request.body);
715
+ if (!parsed.success) {
716
+ reply.code(400);
717
+ return {
718
+ error: {
719
+ type: "validation_error",
720
+ message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
721
+ }
722
+ };
723
+ }
724
+ const removedProfileCount = await ctx.authService.removeProfiles(parsed.data.profileIds);
725
+ return {
726
+ ...await buildAdminConfig(request),
727
+ removedProfileCount
728
+ };
729
+ });
691
730
  app.post("/_gateway/admin/profiles/import", async (request, reply) => {
692
731
  const parsed = profileImportSchema.safeParse(request.body);
693
732
  if (!parsed.success) {
@@ -708,6 +747,23 @@ function createApp(params) {
708
747
  importedProfileCount: importedProfiles.length
709
748
  };
710
749
  });
750
+ app.post("/_gateway/admin/profiles/import/validate", async (request, reply) => {
751
+ const parsed = profileImportSchema.safeParse(request.body);
752
+ if (!parsed.success) {
753
+ reply.code(400);
754
+ return {
755
+ error: {
756
+ type: "validation_error",
757
+ message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
758
+ }
759
+ };
760
+ }
761
+ const profiles = ctx.authService.validateProfilesImport(parsed.data.profile);
762
+ return {
763
+ valid: true,
764
+ profileCount: profiles.length
765
+ };
766
+ });
711
767
  app.get("/_gateway/admin/profiles/import-template", async () => ({
712
768
  profile: ctx.authService.getProfileImportTemplate()
713
769
  }));
@@ -724,11 +780,13 @@ function createApp(params) {
724
780
  }
725
781
  if (parsed.data.all || parsed.data.profileIds) {
726
782
  return {
727
- profile: await ctx.authService.exportProfiles(parsed.data.profileIds)
783
+ profile: await ctx.authService.exportProfiles(parsed.data.profileIds, "openai-codex", parsed.data.all ? "all" : "batch"),
784
+ config: await buildAdminConfig(request)
728
785
  };
729
786
  }
730
787
  return {
731
- profile: await ctx.authService.exportProfile(parsed.data.profileId)
788
+ profile: await ctx.authService.exportProfile(parsed.data.profileId),
789
+ config: await buildAdminConfig(request)
732
790
  };
733
791
  });
734
792
  app.post("/_gateway/admin/codex/apply", async (request, reply) => {
@@ -758,18 +816,7 @@ function createApp(params) {
758
816
  }
759
817
  };
760
818
  }
761
- if (parsed.data.defaultModel) {
762
- await ctx.configService.setDefaultModel(parsed.data.defaultModel);
763
- }
764
- if (parsed.data.networkProxy) {
765
- await ctx.configService.setNetworkProxy(parsed.data.networkProxy);
766
- }
767
- if (parsed.data.autoSwitch) {
768
- await ctx.configService.setAutoSwitch(parsed.data.autoSwitch);
769
- }
770
- if (parsed.data.server) {
771
- await ctx.configService.setServerConfig({ port: parsed.data.server.port });
772
- }
819
+ await ctx.configService.updateSettings(parsed.data);
773
820
  return buildAdminConfig(request);
774
821
  });
775
822
  app.post("/_gateway/admin/restart", async (_request, reply) => {
@@ -41,6 +41,13 @@ npm run dist:win
41
41
 
42
42
  Creates macOS and Windows distributables. macOS builds should be produced on macOS. Windows builds are best produced on Windows CI or a runner with a complete Windows packaging environment.
43
43
 
44
+ `npm run dist:mac` must build both Apple Silicon and Intel macOS packages. It runs:
45
+
46
+ ```bash
47
+ npm run dist:mac:arm64
48
+ npm run dist:mac:x64
49
+ ```
50
+
44
51
  ## UI Engineering Standards
45
52
 
46
53
  Before building release artifacts, the desktop React UI should follow:
@@ -65,6 +72,8 @@ Unsigned builds are suitable for internal testing only. Public commercial distri
65
72
 
66
73
  `electron-builder` reads the standard signing environment variables. Configure these in CI instead of committing credentials to the repository.
67
74
 
75
+ Until macOS Developer ID signing and notarization are configured, each macOS DMG must include `build/mac-install-guide.txt` so users can handle the Gatekeeper verification prompt.
76
+
68
77
  ## Release Artifacts
69
78
 
70
79
  The packaged output is written to:
@@ -75,17 +84,40 @@ release/
75
84
 
76
85
  The folder is intentionally ignored by git.
77
86
 
87
+ Every desktop GitHub Release must upload exactly these user-facing artifacts:
88
+
89
+ ```text
90
+ AI Zero Token-{version}-mac-arm64.dmg
91
+ AI Zero Token-{version}-mac-x64.dmg
92
+ AI Zero Token Setup {version}.exe
93
+ AI Zero Token-{version}-win.zip
94
+ ```
95
+
96
+ Artifact purpose:
97
+
98
+ - `AI Zero Token-{version}-mac-arm64.dmg`: macOS Apple Silicon builds for M1/M2/M3/M4 devices.
99
+ - `AI Zero Token-{version}-mac-x64.dmg`: macOS Intel builds.
100
+ - `AI Zero Token Setup {version}.exe`: Windows installer and primary Windows download.
101
+ - `AI Zero Token-{version}-win.zip`: Windows portable zip.
102
+
103
+ Do not upload unpacked app directories, debug metadata, universal macOS builds, or auto-update metadata unless the release explicitly enables an auto-update channel.
104
+
105
+ macOS DMG files should include the in-package `mac-install-guide.txt` helper document. Do not upload this text file as a standalone GitHub Release asset.
106
+
78
107
  ### Publish Flow
79
108
 
80
109
  1. Build the desktop package:
81
110
 
82
111
  ```bash
83
- npm run dist:dir
112
+ npm run dist:mac
113
+ npm run dist:win
84
114
  ```
85
115
 
86
- 2. Upload the generated files from `release/` to the matching GitHub Release tag.
116
+ 2. Rename the generated files, if needed, so the GitHub Release uses the standard artifact names listed above.
117
+
118
+ 3. Upload only the standard artifact files from `release/` to the matching GitHub Release tag.
87
119
 
88
- 3. Publish the npm package after confirming `package.json` and `package-lock.json` both point at the new version:
120
+ 4. Publish the npm package after confirming `package.json` and `package-lock.json` both point at the new version:
89
121
 
90
122
  ```bash
91
123
  npm publish
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-zero-token",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Local-first OpenAI-compatible AI CLI and gateway with Codex OAuth, multi-account management, and gpt-image-2 image generation/editing.",
5
5
  "license": "MIT",
6
6
  "author": "AI Zero Token Contributors",
@@ -50,7 +50,7 @@
50
50
  "desktop:dev": "node scripts/dev.mjs desktop",
51
51
  "dist": "npm run build && electron-builder",
52
52
  "dist:dir": "npm run build && electron-builder --dir",
53
- "dist:mac": "npm run build && electron-builder --mac --universal",
53
+ "dist:mac": "npm run dist:mac:arm64 && npm run dist:mac:x64",
54
54
  "dist:mac:arm64": "npm run build && electron-builder --mac --arm64",
55
55
  "dist:mac:x64": "npm run build && electron-builder --mac --x64",
56
56
  "dist:win": "npm run build && electron-builder --win",
@@ -63,6 +63,7 @@
63
63
  "dependencies": {
64
64
  "@fastify/cors": "^11.1.0",
65
65
  "fastify": "^5.8.2",
66
+ "fflate": "^0.8.2",
66
67
  "zod": "^4.3.6"
67
68
  },
68
69
  "devDependencies": {
@@ -94,6 +95,12 @@
94
95
  "README.zh-CN.md",
95
96
  "LICENSE"
96
97
  ],
98
+ "extraResources": [
99
+ {
100
+ "from": "build/mac-install-guide.txt",
101
+ "to": "mac-install-guide.txt"
102
+ }
103
+ ],
97
104
  "extraMetadata": {
98
105
  "main": "dist/desktop/main.js"
99
106
  },
@@ -106,6 +113,27 @@
106
113
  "zip"
107
114
  ]
108
115
  },
116
+ "dmg": {
117
+ "contents": [
118
+ {
119
+ "x": 130,
120
+ "y": 160,
121
+ "type": "file"
122
+ },
123
+ {
124
+ "x": 410,
125
+ "y": 160,
126
+ "type": "link",
127
+ "path": "/Applications"
128
+ },
129
+ {
130
+ "x": 270,
131
+ "y": 300,
132
+ "type": "file",
133
+ "path": "build/mac-install-guide.txt"
134
+ }
135
+ ]
136
+ },
109
137
  "win": {
110
138
  "icon": "build/icon.ico",
111
139
  "target": [
@@ -125,6 +153,7 @@
125
153
  "build/icon.png",
126
154
  "build/icon.icns",
127
155
  "build/icon.ico",
156
+ "build/mac-install-guide.txt",
128
157
  "CHANGELOG.md",
129
158
  "docs/API_USAGE.md",
130
159
  "docs/DESKTOP_RELEASE.md",
@@ -1 +0,0 @@
1
- import{a as e,r as t,t as n}from"./jsx-runtime-DqpGtLhh.js";import{t as r}from"./earth-DFdZaQIi.js";import{t as i}from"./refresh-cw-CAAH2rqe.js";import{t as a}from"./search-B2hz41D3.js";import{_ as o,a as s,b as c,d as l,f as u,g as d,h as f,i as p,m,n as h,o as g,p as _,r as v,s as y,t as b,u as x,v as S,w as C,y as w}from"./profiles-DMOjJORP.js";import{_ as T,d as E,p as D,r as O,x as k}from"./index-CX8e0-BB.js";import{t as A}from"./InfoRow-0ULI9iI3.js";var j=e(t(),1),M=n();function N(e){let t=e.config?.codex?.accountId,n=e.profiles.length<=0?``:e.profiles.length===1?`profile-count-1`:e.profiles.length===2?`profile-count-2`:e.profiles.length===3?`profile-count-3`:`profile-count-many`;return(0,M.jsxs)(`section`,{className:`card`,id:`accounts`,children:[(0,M.jsxs)(`div`,{className:`section-head`,children:[(0,M.jsxs)(`div`,{children:[(0,M.jsx)(`h2`,{children:`账号额度预览`}),(0,M.jsx)(`p`,{children:`账号信息采用卡片式布局展示,支持搜索、状态筛选和额度排序。`})]}),(0,M.jsxs)(`div`,{className:`section-actions`,children:[(0,M.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onLocate,children:`定位当前账号`}),(0,M.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onExportSelected,children:`导出所选`}),(0,M.jsx)(`button`,{className:`btn-primary`,type:`button`,onClick:e.onAddAccount,children:`新增账号`}),(0,M.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onRefreshStatus,children:`刷新状态`}),(0,M.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:e.onClearAccounts,children:`清空账号`})]})]}),(0,M.jsxs)(`div`,{className:`filter-row`,children:[(0,M.jsxs)(`label`,{className:`search-box`,children:[(0,M.jsx)(a,{size:16}),(0,M.jsx)(`input`,{value:e.filter.search,onChange:t=>e.onFilter({...e.filter,search:t.target.value}),placeholder:`搜索邮箱、账号 ID 或 Profile ID`})]}),(0,M.jsxs)(`select`,{className:`control`,value:e.filter.status,onChange:t=>e.onFilter({...e.filter,status:t.target.value}),children:[(0,M.jsx)(`option`,{value:`all`,children:`全部状态`}),(0,M.jsx)(`option`,{value:`active`,children:`使用中`}),(0,M.jsx)(`option`,{value:`healthy`,children:`健康`}),(0,M.jsx)(`option`,{value:`warning`,children:`即将耗尽`}),(0,M.jsx)(`option`,{value:`exhausted`,children:`额度耗尽`}),(0,M.jsx)(`option`,{value:`invalid`,children:`登录失效`}),(0,M.jsx)(`option`,{value:`expired`,children:`已过期`})]}),(0,M.jsxs)(`select`,{className:`control`,value:e.filter.sort,onChange:t=>e.onFilter({...e.filter,sort:t.target.value}),children:[(0,M.jsx)(`option`,{value:`quota-desc`,children:`默认排序`}),(0,M.jsx)(`option`,{value:`latency-asc`,children:`按额度更新时间`}),(0,M.jsx)(`option`,{value:`expiry-asc`,children:`按过期时间`}),(0,M.jsx)(`option`,{value:`name-asc`,children:`按名称排序`}),(0,M.jsx)(`option`,{value:`quota-asc`,children:`按剩余额度升序`}),(0,M.jsx)(`option`,{value:`plan-desc`,children:`按套餐排序`}),(0,M.jsx)(`option`,{value:`email-asc`,children:`按邮箱排序`})]}),(0,M.jsxs)(`span`,{className:`account-selected-count`,children:[`已选择 `,e.selectedCount,` 个`]})]}),(0,M.jsx)(`div`,{className:`account-grid ${n}`,children:e.profiles.length===0?(0,M.jsx)(`div`,{className:`empty-state`,children:`还没有匹配的账号。可以新增账号或调整筛选条件。`}):e.profiles.map(n=>{let a=u(n),f=l(n),v=w(n),y=!!e.expandedProfiles[n.profileId],x=!!(t&&n.accountId===t),C=c(n,x),E=g(n),D=s(n),O=typeof e.busy==`string`&&e.busy.startsWith(`profile:`)&&e.busy.endsWith(n.profileId),j=e.busy===`profile:sync-quota:${n.profileId}`;return(0,M.jsxs)(`article`,{className:`account-card plan-${h(n)} ${E?`is-auth-invalid`:``}`,"data-profile-card":n.profileId,title:E?b(n):void 0,children:[C&&(0,M.jsx)(`span`,{className:`usage-corner ${C.className}`,children:(0,M.jsx)(`span`,{children:C.label})}),(0,M.jsxs)(`div`,{className:`account-head`,children:[(0,M.jsxs)(`div`,{className:`account-title`,children:[(0,M.jsxs)(`div`,{className:`account-name`,children:[(0,M.jsx)(`span`,{className:`avatar`,children:_(n)}),(0,M.jsx)(`strong`,{children:m(n,e.showEmails)}),(0,M.jsx)(`button`,{"aria-label":`刷新额度`,className:`account-icon-btn`,disabled:O,onClick:()=>e.onAction(`sync-quota`,n),title:`刷新额度`,type:`button`,children:j?(0,M.jsx)(T,{className:`spin`,size:14}):(0,M.jsx)(i,{size:14})})]}),(0,M.jsxs)(`div`,{className:`badge-row`,children:[(0,M.jsx)(`span`,{className:`badge brand`,children:p(n)}),(0,M.jsx)(`span`,{className:`badge ${a.tone}`,children:a.label}),(0,M.jsx)(`span`,{className:`badge ${D.ok?`green`:`orange`}`,children:`gpt-image-2`})]})]}),(0,M.jsxs)(`label`,{className:`account-select`,children:[(0,M.jsx)(`input`,{type:`checkbox`,checked:!!e.selectedProfiles[n.profileId],onChange:t=>e.onSelect(n.profileId,t.target.checked)}),(0,M.jsx)(`span`,{children:`选择`})]})]}),(0,M.jsxs)(`div`,{className:`account-metrics`,children:[(0,M.jsx)(F,{label:o(n,`primary`),value:f,tone:d(f)}),(0,M.jsx)(F,{label:o(n,`secondary`),value:v,tone:d(v)})]}),(0,M.jsxs)(`div`,{className:`usage-status-row`,children:[(0,M.jsxs)(`span`,{className:`usage-status ${n.isActive?`is-active`:``}`,children:[(0,M.jsx)(r,{size:14}),(0,M.jsx)(`span`,{children:`API`}),(0,M.jsx)(`span`,{className:`usage-dot ${n.isActive?`active`:``}`}),(0,M.jsx)(`span`,{className:`usage-state-text`,children:n.isActive?`使用中`:`未使用`})]}),(0,M.jsxs)(`span`,{className:`usage-status ${x?`is-active`:``}`,children:[(0,M.jsx)(k,{size:14}),(0,M.jsx)(`span`,{children:`Codex`}),(0,M.jsx)(`span`,{className:`usage-dot ${x?`active`:``}`}),(0,M.jsx)(`span`,{className:`usage-state-text`,children:x?`使用中`:`未使用`})]})]}),(0,M.jsxs)(`div`,{className:`compact-meta-row`,children:[(0,M.jsxs)(`div`,{className:`compact-reset-list`,children:[(0,M.jsxs)(`div`,{className:`compact-meta-item`,children:[(0,M.jsx)(`label`,{children:o(n,`primary`)}),(0,M.jsx)(`strong`,{children:S(n,`primary`)})]}),(0,M.jsxs)(`div`,{className:`compact-meta-item`,children:[(0,M.jsx)(`label`,{children:o(n,`secondary`)}),(0,M.jsx)(`strong`,{children:S(n,`secondary`)})]})]}),(0,M.jsx)(`div`,{className:`compact-meta-actions`,children:(0,M.jsxs)(`button`,{className:`details-toggle ${y?`is-expanded`:``}`,type:`button`,onClick:()=>e.onToggle(n.profileId),children:[(0,M.jsx)(`span`,{children:y?`收起详情`:`查看详情`}),(0,M.jsx)(P,{})]})})]}),y&&(0,M.jsxs)(`div`,{className:`meta-grid`,children:[(0,M.jsx)(A,{label:`套餐`,value:p(n)}),(0,M.jsx)(A,{label:`Account ID`,value:(e.showEmails,n.accountId),code:!0}),(0,M.jsx)(A,{label:`Profile ID`,value:(e.showEmails,n.profileId),code:!0}),(0,M.jsx)(A,{label:`认证状态`,value:b(n)}),(0,M.jsx)(A,{label:`生图能力`,value:D.ok?`gpt-image-2 可用`:D.detail}),(0,M.jsx)(A,{label:`过期时间`,value:n.expiresAt?new Date(n.expiresAt).toLocaleString(`zh-CN`):`-`}),(0,M.jsx)(A,{label:`额度快照`,value:n.quota?.capturedAt?new Date(n.quota.capturedAt).toLocaleString(`zh-CN`):`-`})]}),(0,M.jsxs)(`div`,{className:`account-actions`,children:[(0,M.jsx)(`button`,{className:`btn-secondary ${n.isActive?`is-current`:``}`,type:`button`,onClick:()=>e.onAction(`activate`,n),disabled:n.isActive||O||E,children:E?`网关不可用`:n.isActive?`网关使用中`:`应用网关`}),(0,M.jsx)(`button`,{className:`btn-secondary ${x?`is-current codex`:``}`,type:`button`,onClick:()=>e.onAction(`apply-codex`,n),disabled:x||O||E,children:E?`Codex 不可用`:x?`Codex 使用中`:`应用 Codex`}),(0,M.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>e.onAction(`export`,n),disabled:O,children:`导出`}),(0,M.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:()=>e.onAction(`remove`,n),disabled:O,children:`删除`})]})]},n.profileId)})})]})}function P(){return(0,M.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,"aria-hidden":`true`,children:(0,M.jsx)(`path`,{d:`m6 9 6 6 6-6`})})}function F(e){return(0,M.jsxs)(`div`,{className:`quota-row`,children:[(0,M.jsxs)(`div`,{className:`quota-line`,children:[(0,M.jsxs)(`span`,{children:[e.label,` · 已用 `,e.value,`% / 剩余 `,100-e.value,`%`]}),(0,M.jsxs)(`strong`,{children:[`剩余 `,100-e.value,`%`]})]}),(0,M.jsx)(`div`,{className:`progress-track`,children:(0,M.jsx)(`div`,{className:`progress-bar ${e.tone}`,style:{width:`${e.value}%`}})})]})}function I(e){let[t,n]=(0,j.useState)({}),[r,i]=(0,j.useState)({}),[a,o]=(0,j.useState)({search:``,status:`all`,sort:`quota-desc`}),s=(0,j.useMemo)(()=>{let t=e.config?.profiles?[...e.config.profiles]:[],n=a.search.trim().toLowerCase(),r=t.filter(t=>{let r=[m(t,!0).toLowerCase(),t.accountId,t.profileId,t.email||``].join(` `).toLowerCase(),i=u(t),o=!!(e.codexAccountId&&t.accountId===e.codexAccountId);return n&&!r.includes(n)?!1:a.status===`active`?t.isActive||o:a.status===`healthy`?i.key===`healthy`:a.status===`warning`?i.key===`warning`:a.status===`exhausted`?i.key===`exhausted`:a.status===`expired`?i.key===`expired`:a.status===`invalid`?i.key===`invalid`:!0});return r.sort((t,n)=>{let r=f(t,e.codexAccountId)-f(n,e.codexAccountId);if(r!==0)return r;let i=v(n)-v(t);if(i!==0)return i;let o=x(n)-x(t);return o===0?a.sort===`latency-asc`?(n.quota?.capturedAt||0)-(t.quota?.capturedAt||0):a.sort===`expiry-asc`?(t.expiresAt||2**53-1)-(n.expiresAt||2**53-1):a.sort===`name-asc`?m(t,!0).localeCompare(m(n,!0),`zh-CN`):a.sort===`quota-asc`?100-l(n)-(100-l(t)):a.sort===`plan-desc`?v(n)-v(t):a.sort===`email-asc`?m(t,!0).localeCompare(m(n,!0)):l(n)-l(t):o}),r},[a,e.codexAccountId,e.config?.profiles]),c=Object.values(t).filter(Boolean).length;async function d(t,n){let r=await D(`/_gateway/admin/profiles/export`,{method:`POST`,headers:{"Content-Type":`application/json`},body:C(n?{profileIds:n}:{profileId:t})});E(`ai-zero-token-${n?`profiles-${n.length}`:t||`active`}.json`,r.profile),e.setStatus(n?`已导出 ${n.length} 个账号。`:`账号配置已导出。`)}async function p(t,n){if(!(t===`remove`&&!window.confirm(`确认删除 ${m(n,e.showEmails)}?`))){if((t===`activate`||t===`apply-codex`)&&g(n)){e.setStatus(`${m(n,e.showEmails)} 登录已失效,不能应用到${t===`activate`?`网关`:`Codex`}。`);return}if((t===`activate`||t===`apply-codex`)&&y(n)){let r=t===`activate`?`网关`:`Codex`;if(!window.confirm(`${m(n,e.showEmails)} 的额度看起来已耗尽,仍要应用到${r}吗?`))return}if(t===`export`){await d(n.profileId);return}e.setBusy(`profile:${t}:${n.profileId}`);try{let r=await D({activate:`/_gateway/admin/profiles/activate`,"apply-codex":`/_gateway/admin/codex/apply`,"sync-quota":`/_gateway/admin/profiles/sync-quota`,remove:`/_gateway/admin/profiles/remove`}[t],{method:`POST`,headers:{"Content-Type":`application/json`},body:C({profileId:n.profileId})});e.setConfig(`config`in r?r.config:r),e.setStatus(t===`activate`?`已应用到网关。`:t===`apply-codex`?`已应用到本机 Codex。`:t===`sync-quota`?`额度信息已同步。`:`账号已删除。`)}catch(t){e.setStatus(O(t))}finally{e.setBusy(null)}}}return(0,M.jsx)(N,{config:e.config,profiles:s,showEmails:e.showEmails,filter:a,selectedProfiles:t,expandedProfiles:r,selectedCount:c,busy:e.busy,onFilter:o,onSelect:(e,t)=>n(n=>({...n,[e]:t})),onToggle:e=>i(t=>({...t,[e]:!t[e]})),onAction:p,onLocate:()=>e.activeProfile&&document.querySelector(`[data-profile-card="${e.activeProfile.profileId}"]`)?.scrollIntoView({behavior:`smooth`,block:`center`}),onExportSelected:()=>{let n=Object.keys(t).filter(e=>t[e]);if(n.length===0){e.setStatus(`请先勾选要导出的账号。`);return}d(void 0,n).catch(t=>e.setStatus(t instanceof Error?t.message:String(t)))},onAddAccount:()=>e.setAccountModalOpen(!0),onRefreshStatus:()=>e.refreshConfig({runtime:!0}),onClearAccounts:()=>e.logout()})}export{I as AccountsPage};