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.
- package/CHANGELOG.md +11 -0
- package/README.md +13 -5
- package/README.zh-CN.md +13 -5
- package/admin-ui/dist/assets/accounts-DymL4WIa.js +2 -0
- package/admin-ui/dist/assets/{docs-CihX3Xsm.js → docs-DtO-AOWU.js} +1 -1
- package/admin-ui/dist/assets/{image-bed-BGjlDLks.js → image-bed-yIVQ4dKs.js} +1 -1
- package/admin-ui/dist/assets/index-By4r-wy3.css +1 -0
- package/admin-ui/dist/assets/index-DRe-tByu.js +10 -0
- package/admin-ui/dist/assets/{launch-BWw7Odq7.js → launch-CQXYrl-h.js} +1 -1
- package/admin-ui/dist/assets/{logs-DDdgDVwo.js → logs-awABDg1C.js} +1 -1
- package/admin-ui/dist/assets/{network-detect-cUdjg4zk.js → network-detect-sSrnwZqf.js} +1 -1
- package/admin-ui/dist/assets/{overview-CsjVVcvi.js → overview-BbSON0Jl.js} +1 -1
- package/admin-ui/dist/assets/settings-DvRiHS7i.js +1 -0
- package/admin-ui/dist/assets/{tester-BIvH_8DY.js → tester-CftPgRE9.js} +1 -1
- package/admin-ui/dist/index.html +2 -2
- package/build/mac-install-guide.txt +25 -0
- package/dist/core/services/auth-service.js +88 -12
- package/dist/core/services/config-service.js +87 -23
- package/dist/core/services/version-service.js +1 -1
- package/dist/core/store/profile-store.js +73 -32
- package/dist/core/store/settings-store.js +14 -0
- package/dist/server/app.js +63 -16
- package/docs/DESKTOP_RELEASE.md +35 -3
- package/package.json +31 -2
- package/admin-ui/dist/assets/accounts-Ddq82u6R.js +0 -1
- package/admin-ui/dist/assets/index-CX8e0-BB.js +0 -10
- package/admin-ui/dist/assets/index-ywn2Jwpu.css +0 -1
- 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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
store.activeProfileId
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
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
|
};
|
package/dist/server/app.js
CHANGED
|
@@ -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
|
-
|
|
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) => {
|
package/docs/DESKTOP_RELEASE.md
CHANGED
|
@@ -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:
|
|
112
|
+
npm run dist:mac
|
|
113
|
+
npm run dist:win
|
|
84
114
|
```
|
|
85
115
|
|
|
86
|
-
2.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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};
|