ai-zero-token 1.0.4 → 1.0.6
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 +15 -0
- package/README.md +16 -1
- package/dist/cli/commands/help.js +4 -0
- package/dist/cli/commands/profiles.js +63 -0
- package/dist/cli/index.js +4 -0
- package/dist/core/providers/http-client.js +23 -2
- package/dist/core/services/auth-service.js +47 -0
- package/dist/core/services/config-service.js +30 -0
- package/dist/core/services/image-service.js +4 -9
- package/dist/core/store/profile-transfer.js +142 -0
- package/dist/core/store/settings-store.js +10 -0
- package/dist/server/admin-page.js +353 -4
- package/dist/server/app.js +64 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.6 - 2026-04-28
|
|
4
|
+
|
|
5
|
+
- Added account JSON import/export support in the management page.
|
|
6
|
+
- Added batch account import from a single object, an array, or a `profiles` bundle.
|
|
7
|
+
- Added selectable batch export for checked accounts in the management page.
|
|
8
|
+
- Added `azt profiles import/export` CLI commands for account transfer workflows.
|
|
9
|
+
- Added an account import template endpoint for quick JSON format reference.
|
|
10
|
+
|
|
11
|
+
## 1.0.5 - 2026-04-27
|
|
12
|
+
|
|
13
|
+
- Added management-page proxy configuration for upstream requests, persisted in local settings.
|
|
14
|
+
- Routed upstream requests through configured curl proxy settings when enabled.
|
|
15
|
+
- Removed the local fixed-size allowlist for image generation `size`, allowing upstream validation to decide supported values.
|
|
16
|
+
- Documented the proxy configuration workflow without including a specific proxy address.
|
|
17
|
+
|
|
3
18
|
## 1.0.4 - 2026-04-24
|
|
4
19
|
|
|
5
20
|
- Moved persistent account and settings state to the user home directory at `~/.ai-zero-token/.state`.
|
package/README.md
CHANGED
|
@@ -220,6 +220,8 @@ azt start
|
|
|
220
220
|
- 查看当前账号、已保存账号列表、过期时间、token 摘要
|
|
221
221
|
- 查看账号套餐 plan 和当前账号是否支持生图
|
|
222
222
|
- 在多个已保存账号之间切换当前使用账号
|
|
223
|
+
- 在“新增账号”里选择 OAuth 登录,或粘贴外部账号 JSON 批量导入
|
|
224
|
+
- 导出单个账号,或勾选多个账号后批量导出所选账号 JSON
|
|
223
225
|
- 删除单个本地账号,或一键清空全部本地账号
|
|
224
226
|
- 切换默认模型
|
|
225
227
|
- 测试 `models` / `responses` / `chat.completions`
|
|
@@ -227,6 +229,10 @@ azt start
|
|
|
227
229
|
|
|
228
230
|
管理页里邮箱默认脱敏显示,需要手动点击“查看邮箱”才会显示明文。
|
|
229
231
|
|
|
232
|
+
导出的账号 JSON 包含完整 `access_token` 和 `refresh_token`,等同于账号登录凭据,只适合在可信环境中传递。
|
|
233
|
+
|
|
234
|
+
如果当前网络访问海外上游不稳定,可以在管理页的“接口测试 / 系统设置”区域启用“上游代理”,并填写你自己的代理地址。保存后,OAuth 换取 token、模型刷新和接口转发都会通过该代理访问上游;本地管理页和 `127.0.0.1` 默认保持直连。
|
|
235
|
+
|
|
230
236
|
默认监听地址:
|
|
231
237
|
|
|
232
238
|
```text
|
|
@@ -401,7 +407,16 @@ curl http://127.0.0.1:8787/v1/images/generations \
|
|
|
401
407
|
|
|
402
408
|
## 兼容说明
|
|
403
409
|
|
|
404
|
-
代码里仍然保留了 `login`、`status`、`models`、`ask`、`serve`、`clear` 等 CLI 命令,主要用于调试、兼容和后续扩展。
|
|
410
|
+
代码里仍然保留了 `login`、`status`、`models`、`profiles`、`ask`、`serve`、`clear` 等 CLI 命令,主要用于调试、兼容和后续扩展。
|
|
411
|
+
|
|
412
|
+
账号 JSON 也可以通过 CLI 导入/导出:
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
azt profiles import ./profile.json
|
|
416
|
+
azt profiles export active ./profile.json
|
|
417
|
+
azt profiles export all ./profiles.json
|
|
418
|
+
azt profiles export "openai-codex:<accountId>" ./profile.json
|
|
419
|
+
```
|
|
405
420
|
|
|
406
421
|
README 不再把这些命令作为推荐使用方式。默认使用路径就是:
|
|
407
422
|
|
|
@@ -5,6 +5,9 @@ function printHelp() {
|
|
|
5
5
|
azt login
|
|
6
6
|
azt models
|
|
7
7
|
azt models --refresh
|
|
8
|
+
azt profiles import ./profile.json
|
|
9
|
+
azt profiles export active ./profile.json
|
|
10
|
+
azt profiles export all ./profiles.json
|
|
8
11
|
azt status
|
|
9
12
|
azt ask "\u4F60\u597D\uFF0C\u8BF7\u7B80\u5355\u4ECB\u7ECD\u4E00\u4E0B\u81EA\u5DF1"
|
|
10
13
|
azt ask --model gpt-5.3-codex "\u4F60\u597D"
|
|
@@ -18,6 +21,7 @@ function printHelp() {
|
|
|
18
21
|
|
|
19
22
|
login \u8D70\u771F\u5B9E OpenAI Codex OAuth\uFF0C\u65B0\u589E\u5E76\u4FDD\u5B58\u4E00\u4E2A\u8D26\u53F7 profile
|
|
20
23
|
models \u67E5\u770B\u5F53\u524D\u53EF\u7528\u6A21\u578B\u5217\u8868\uFF1B\u4F18\u5148\u8BFB\u53D6 ~/.codex/models_cache.json\uFF0C--refresh \u53EF\u624B\u52A8\u91CD\u8BFB
|
|
24
|
+
profiles \u5BFC\u5165/\u5BFC\u51FA\u8D26\u53F7 JSON\uFF1B\u5BFC\u51FA\u6587\u4EF6\u5305\u542B\u5B8C\u6574 refresh token\uFF0C\u8BF7\u53EA\u5206\u4EAB\u7ED9\u53EF\u4FE1\u5BF9\u8C61
|
|
21
25
|
status \u67E5\u770B\u5F53\u524D demo \u5F53\u524D\u6FC0\u6D3B\u8D26\u53F7\u3001\u8D26\u53F7\u6570\u91CF\u548C\u8FC7\u671F\u65F6\u95F4
|
|
22
26
|
ask \u7528\u4FDD\u5B58\u7684 token \u8C03\u771F\u5B9E Codex Responses API
|
|
23
27
|
\u5B9E\u9A8C\u6A21\u5F0F\u53EF\u7528 --payload-file \u900F\u4F20\u989D\u5916\u8BF7\u6C42\u4F53\uFF0C\u914D\u5408 --dump-raw / --print-raw \u89C2\u5BDF SSE \u539F\u59CB\u4E8B\u4EF6
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { createGatewayContext } from "../../core/context.js";
|
|
4
|
+
import { formatExpiry } from "../shared.js";
|
|
5
|
+
function printProfilesHelp() {
|
|
6
|
+
console.log(`\u7528\u6CD5:
|
|
7
|
+
|
|
8
|
+
azt profiles import ./profile.json
|
|
9
|
+
azt profiles export active ./profile.json
|
|
10
|
+
azt profiles export all ./profiles.json
|
|
11
|
+
azt profiles export <profileId> ./profile.json
|
|
12
|
+
|
|
13
|
+
\u8BF4\u660E:
|
|
14
|
+
|
|
15
|
+
import \u5BFC\u5165\u5916\u90E8\u8D26\u53F7 JSON\uFF0C\u652F\u6301\u5355\u4E2A\u5BF9\u8C61\u3001\u5BF9\u8C61\u6570\u7EC4\u6216 { "profiles": [...] }
|
|
16
|
+
export \u5BFC\u51FA\u5F53\u524D\u8D26\u53F7\u3001\u5168\u90E8\u8D26\u53F7\u6216\u6307\u5B9A profileId \u7684\u5B8C\u6574\u51ED\u636E JSON
|
|
17
|
+
`);
|
|
18
|
+
}
|
|
19
|
+
async function runProfilesCommand(argv) {
|
|
20
|
+
const [action, target, outputPath] = argv;
|
|
21
|
+
const ctx = createGatewayContext();
|
|
22
|
+
if (action === "import") {
|
|
23
|
+
if (!target) {
|
|
24
|
+
throw new Error("\u7F3A\u5C11\u5BFC\u5165\u6587\u4EF6\u8DEF\u5F84\u3002\u7528\u6CD5: azt profiles import ./profile.json");
|
|
25
|
+
}
|
|
26
|
+
const raw = await fs.readFile(target, "utf8");
|
|
27
|
+
const profiles = await ctx.authService.importProfiles(JSON.parse(raw));
|
|
28
|
+
console.log(`\u8D26\u53F7\u5BFC\u5165\u6210\u529F\uFF0C\u5171 ${profiles.length} \u4E2A\u3002`);
|
|
29
|
+
for (const profile of profiles) {
|
|
30
|
+
console.log(`profileId: ${profile.profileId}`);
|
|
31
|
+
console.log(`accountId: ${profile.accountId}`);
|
|
32
|
+
if (profile.email) {
|
|
33
|
+
console.log(`email: ${profile.email}`);
|
|
34
|
+
}
|
|
35
|
+
console.log(`expires: ${formatExpiry(profile.expires)}`);
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (action === "export") {
|
|
40
|
+
const isAll = target === "all";
|
|
41
|
+
const profileId = outputPath && target !== "active" && !isAll ? target : void 0;
|
|
42
|
+
const resolvedOutputPath = outputPath ?? (target !== "active" && !isAll ? target : void 0);
|
|
43
|
+
if (!resolvedOutputPath) {
|
|
44
|
+
throw new Error("\u7F3A\u5C11\u5BFC\u51FA\u6587\u4EF6\u8DEF\u5F84\u3002\u7528\u6CD5: azt profiles export active ./profile.json");
|
|
45
|
+
}
|
|
46
|
+
const exported = isAll ? await ctx.authService.exportProfiles() : await ctx.authService.exportProfile(profileId);
|
|
47
|
+
await fs.writeFile(resolvedOutputPath, `${JSON.stringify(exported, null, 2)}
|
|
48
|
+
`, "utf8");
|
|
49
|
+
console.log("\u8D26\u53F7\u5BFC\u51FA\u6210\u529F\u3002");
|
|
50
|
+
if ("profiles" in exported) {
|
|
51
|
+
console.log(`profileCount: ${exported.profiles.length}`);
|
|
52
|
+
} else {
|
|
53
|
+
console.log(`profileId: ${exported.profile_id}`);
|
|
54
|
+
console.log(`accountId: ${exported.account_id}`);
|
|
55
|
+
}
|
|
56
|
+
console.log(`file: ${resolvedOutputPath}`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
printProfilesHelp();
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
runProfilesCommand
|
|
63
|
+
};
|
package/dist/cli/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { runClearCommand } from "./commands/clear.js";
|
|
|
4
4
|
import { printHelp } from "./commands/help.js";
|
|
5
5
|
import { runLoginCommand } from "./commands/login.js";
|
|
6
6
|
import { runModelsCommand } from "./commands/models.js";
|
|
7
|
+
import { runProfilesCommand } from "./commands/profiles.js";
|
|
7
8
|
import { runServeCommand } from "./commands/serve.js";
|
|
8
9
|
import { runStartCommand } from "./commands/start.js";
|
|
9
10
|
import { runStatusCommand } from "./commands/status.js";
|
|
@@ -19,6 +20,9 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
19
20
|
case "models":
|
|
20
21
|
await runModelsCommand(rest);
|
|
21
22
|
return;
|
|
23
|
+
case "profiles":
|
|
24
|
+
await runProfilesCommand(rest);
|
|
25
|
+
return;
|
|
22
26
|
case "ask":
|
|
23
27
|
await runAskCommand(rest);
|
|
24
28
|
return;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import { loadSettings } from "../store/settings-store.js";
|
|
3
4
|
const CURL_STATUS_MARKER = "\n__CURL_STATUS__:";
|
|
4
5
|
const CURL_HEADERS_MARKER = "\n__CURL_HEADERS__:";
|
|
5
6
|
let requestSequence = 0;
|
|
@@ -68,6 +69,12 @@ async function runCurlRequest(init, params) {
|
|
|
68
69
|
"--write-out",
|
|
69
70
|
`${CURL_STATUS_MARKER}%{http_code}${CURL_HEADERS_MARKER}%{header_json}`
|
|
70
71
|
];
|
|
72
|
+
if (params?.proxy?.enabled && params.proxy.url.trim()) {
|
|
73
|
+
args.push("--proxy", params.proxy.url.trim());
|
|
74
|
+
if (params.proxy.noProxy.trim()) {
|
|
75
|
+
args.push("--noproxy", params.proxy.noProxy.trim());
|
|
76
|
+
}
|
|
77
|
+
}
|
|
71
78
|
for (const [key, value] of Object.entries(init.headers ?? {})) {
|
|
72
79
|
args.push("--header", `${key}: ${value}`);
|
|
73
80
|
}
|
|
@@ -148,12 +155,25 @@ async function runCurlRequest(init, params) {
|
|
|
148
155
|
headers
|
|
149
156
|
};
|
|
150
157
|
}
|
|
158
|
+
async function loadNetworkProxySettings() {
|
|
159
|
+
try {
|
|
160
|
+
const settings = await loadSettings();
|
|
161
|
+
return settings.networkProxy;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.warn("[http] failed to load network proxy settings", {
|
|
164
|
+
error: error instanceof Error ? error.message : String(error)
|
|
165
|
+
});
|
|
166
|
+
return void 0;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
151
169
|
async function requestText(init) {
|
|
152
170
|
const requestId = nextRequestId();
|
|
171
|
+
const proxy = await loadNetworkProxySettings();
|
|
153
172
|
const useCurlOnly = process.env.OAUTH_DEMO_USE_CURL === "1";
|
|
173
|
+
const useConfiguredProxy = !!proxy?.enabled && !!proxy.url.trim();
|
|
154
174
|
const timeoutMs = init.timeoutMs;
|
|
155
175
|
const signal = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : void 0;
|
|
156
|
-
if (!useCurlOnly) {
|
|
176
|
+
if (!useCurlOnly && !useConfiguredProxy) {
|
|
157
177
|
const startedAt = performance.now();
|
|
158
178
|
const phases = {};
|
|
159
179
|
try {
|
|
@@ -199,7 +219,8 @@ async function requestText(init) {
|
|
|
199
219
|
}
|
|
200
220
|
return runCurlRequest(init, {
|
|
201
221
|
requestId,
|
|
202
|
-
fallbackFrom: useCurlOnly ? void 0 : "fetch"
|
|
222
|
+
fallbackFrom: useCurlOnly || useConfiguredProxy ? void 0 : "fetch",
|
|
223
|
+
proxy
|
|
203
224
|
});
|
|
204
225
|
}
|
|
205
226
|
export {
|
|
@@ -13,6 +13,13 @@ import {
|
|
|
13
13
|
refreshOpenAICodexToken
|
|
14
14
|
} from "../providers/openai-codex/oauth.js";
|
|
15
15
|
import { askOpenAICodex } from "../providers/openai-codex/chat.js";
|
|
16
|
+
import {
|
|
17
|
+
exportProfilesToJson,
|
|
18
|
+
exportProfileToJson,
|
|
19
|
+
getProfileImportTemplate,
|
|
20
|
+
importProfileFromJson,
|
|
21
|
+
importProfilesFromJson
|
|
22
|
+
} from "../store/profile-transfer.js";
|
|
16
23
|
class AuthService {
|
|
17
24
|
constructor(configService) {
|
|
18
25
|
this.configService = configService;
|
|
@@ -50,6 +57,46 @@ class AuthService {
|
|
|
50
57
|
await saveProfile(profile);
|
|
51
58
|
return this.toManagedProfile(profile);
|
|
52
59
|
}
|
|
60
|
+
async importProfile(value, provider = "openai-codex") {
|
|
61
|
+
if (provider !== "openai-codex") {
|
|
62
|
+
throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
|
|
63
|
+
}
|
|
64
|
+
const profile = importProfileFromJson(value);
|
|
65
|
+
await saveProfile(profile);
|
|
66
|
+
return this.toManagedProfile(profile);
|
|
67
|
+
}
|
|
68
|
+
async importProfiles(value, provider = "openai-codex") {
|
|
69
|
+
if (provider !== "openai-codex") {
|
|
70
|
+
throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
|
|
71
|
+
}
|
|
72
|
+
const profiles = importProfilesFromJson(value);
|
|
73
|
+
for (const profile of profiles) {
|
|
74
|
+
await saveProfile(profile);
|
|
75
|
+
}
|
|
76
|
+
return profiles.map((profile) => this.toManagedProfile(profile));
|
|
77
|
+
}
|
|
78
|
+
async exportProfile(profileId, provider = "openai-codex") {
|
|
79
|
+
const profiles = await listProfiles();
|
|
80
|
+
const activeProfile = await this.getActiveProfile(provider);
|
|
81
|
+
const targetProfileId = profileId?.trim() || activeProfile?.profileId;
|
|
82
|
+
const profile = profiles.find((item) => item.provider === provider && item.profileId === targetProfileId);
|
|
83
|
+
if (!profile) {
|
|
84
|
+
throw new Error(targetProfileId ? `\u6CA1\u6709\u627E\u5230\u53EF\u5BFC\u51FA\u7684\u8D26\u53F7: ${targetProfileId}` : "\u6CA1\u6709\u53EF\u5BFC\u51FA\u7684\u5F53\u524D\u8D26\u53F7\u3002");
|
|
85
|
+
}
|
|
86
|
+
return exportProfileToJson(profile);
|
|
87
|
+
}
|
|
88
|
+
async exportProfiles(profileIds, provider = "openai-codex") {
|
|
89
|
+
const profiles = await listProfiles();
|
|
90
|
+
const idSet = profileIds && profileIds.length > 0 ? new Set(profileIds.map((item) => item.trim()).filter(Boolean)) : null;
|
|
91
|
+
const selected = profiles.filter((item) => item.provider === provider).filter((item) => !idSet || idSet.has(item.profileId));
|
|
92
|
+
if (selected.length === 0) {
|
|
93
|
+
throw new Error("\u6CA1\u6709\u627E\u5230\u53EF\u5BFC\u51FA\u7684\u8D26\u53F7\u3002");
|
|
94
|
+
}
|
|
95
|
+
return exportProfilesToJson(selected);
|
|
96
|
+
}
|
|
97
|
+
getProfileImportTemplate() {
|
|
98
|
+
return getProfileImportTemplate();
|
|
99
|
+
}
|
|
53
100
|
async getActiveProfile(provider = "openai-codex") {
|
|
54
101
|
const profile = await getActiveProfile();
|
|
55
102
|
if (!profile || profile.provider !== provider) {
|
|
@@ -41,6 +41,36 @@ class ConfigService {
|
|
|
41
41
|
await saveSettings(next);
|
|
42
42
|
return next;
|
|
43
43
|
}
|
|
44
|
+
async setNetworkProxy(params) {
|
|
45
|
+
const url = params.url?.trim() ?? "";
|
|
46
|
+
const noProxy = params.noProxy?.trim() || "localhost,127.0.0.1,::1";
|
|
47
|
+
if (params.enabled) {
|
|
48
|
+
if (!url) {
|
|
49
|
+
throw new Error("\u542F\u7528\u4EE3\u7406\u65F6\u5FC5\u987B\u586B\u5199\u4EE3\u7406\u5730\u5740\u3002");
|
|
50
|
+
}
|
|
51
|
+
let parsed;
|
|
52
|
+
try {
|
|
53
|
+
parsed = new URL(url);
|
|
54
|
+
} catch {
|
|
55
|
+
throw new Error("\u4EE3\u7406\u5730\u5740\u683C\u5F0F\u9519\u8BEF\uFF0C\u8BF7\u586B\u5199\u5B8C\u6574\u7684\u4EE3\u7406 URL\u3002");
|
|
56
|
+
}
|
|
57
|
+
const supportedProtocols = /* @__PURE__ */ new Set(["http:", "https:", "socks4:", "socks4a:", "socks5:", "socks5h:"]);
|
|
58
|
+
if (!supportedProtocols.has(parsed.protocol)) {
|
|
59
|
+
throw new Error("\u4EE3\u7406\u5730\u5740\u4EC5\u652F\u6301 http\u3001https\u3001socks4\u3001socks4a\u3001socks5 \u6216 socks5h\u3002");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const settings = await this.getSettings();
|
|
63
|
+
const next = {
|
|
64
|
+
...settings,
|
|
65
|
+
networkProxy: {
|
|
66
|
+
enabled: params.enabled,
|
|
67
|
+
url,
|
|
68
|
+
noProxy
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
await saveSettings(next);
|
|
72
|
+
return next;
|
|
73
|
+
}
|
|
44
74
|
async getServerConfig() {
|
|
45
75
|
const settings = await this.getSettings();
|
|
46
76
|
return settings.server;
|
|
@@ -6,11 +6,6 @@ const SUPPORTED_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
6
6
|
"gpt-image-1.5",
|
|
7
7
|
"gpt-image-2"
|
|
8
8
|
]);
|
|
9
|
-
const SUPPORTED_IMAGE_SIZES = /* @__PURE__ */ new Set([
|
|
10
|
-
"1024x1024",
|
|
11
|
-
"1024x1536",
|
|
12
|
-
"1536x1024"
|
|
13
|
-
]);
|
|
14
9
|
const SUPPORTED_IMAGE_QUALITIES = /* @__PURE__ */ new Set([
|
|
15
10
|
"low",
|
|
16
11
|
"medium",
|
|
@@ -62,11 +57,11 @@ function toImageGenerationEventOutput(value) {
|
|
|
62
57
|
return null;
|
|
63
58
|
}
|
|
64
59
|
function normalizeReturnedSize(size, fallback) {
|
|
65
|
-
if (typeof size === "string" &&
|
|
66
|
-
return size;
|
|
60
|
+
if (typeof size === "string" && size.trim()) {
|
|
61
|
+
return size.trim();
|
|
67
62
|
}
|
|
68
|
-
if (typeof fallback === "string" &&
|
|
69
|
-
return fallback;
|
|
63
|
+
if (typeof fallback === "string" && fallback.trim()) {
|
|
64
|
+
return fallback.trim();
|
|
70
65
|
}
|
|
71
66
|
return void 0;
|
|
72
67
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const AUTH_CLAIM_PATH = "https://api.openai.com/auth";
|
|
3
|
+
const PROFILE_CLAIM_PATH = "https://api.openai.com/profile";
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function decodeJwtPayload(token) {
|
|
8
|
+
try {
|
|
9
|
+
const parts = token.split(".");
|
|
10
|
+
if (parts.length !== 3) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const payload = parts[1] ?? "";
|
|
14
|
+
const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
15
|
+
const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
|
|
16
|
+
return JSON.parse(Buffer.from(normalized + padding, "base64").toString("utf8"));
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function getString(value) {
|
|
22
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
23
|
+
}
|
|
24
|
+
function getNumber(value) {
|
|
25
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
26
|
+
}
|
|
27
|
+
function extractAccountId(payload, fallback) {
|
|
28
|
+
const authClaim = payload?.[AUTH_CLAIM_PATH];
|
|
29
|
+
const accountId = isRecord(authClaim) ? getString(authClaim.chatgpt_account_id) : void 0;
|
|
30
|
+
const fallbackAccountId = getString(fallback);
|
|
31
|
+
const resolved = accountId ?? fallbackAccountId;
|
|
32
|
+
if (!resolved) {
|
|
33
|
+
throw new Error("\u5BFC\u5165\u5931\u8D25: \u65E0\u6CD5\u4ECE access_token \u4E2D\u63D0\u53D6 accountId\u3002");
|
|
34
|
+
}
|
|
35
|
+
return resolved;
|
|
36
|
+
}
|
|
37
|
+
function extractEmail(payload, fallback) {
|
|
38
|
+
const profileClaim = payload?.[PROFILE_CLAIM_PATH];
|
|
39
|
+
const profileEmail = isRecord(profileClaim) ? getString(profileClaim.email) : void 0;
|
|
40
|
+
return profileEmail ?? getString(payload?.email) ?? getString(fallback);
|
|
41
|
+
}
|
|
42
|
+
function parseExpiry(input, payload) {
|
|
43
|
+
const jwtExp = getNumber(payload?.exp);
|
|
44
|
+
if (jwtExp) {
|
|
45
|
+
return jwtExp * 1e3;
|
|
46
|
+
}
|
|
47
|
+
const directExpires = getNumber(input.expires);
|
|
48
|
+
if (directExpires) {
|
|
49
|
+
return directExpires > 1e10 ? directExpires : directExpires * 1e3;
|
|
50
|
+
}
|
|
51
|
+
const expiresAt = getNumber(input.expires_at);
|
|
52
|
+
if (expiresAt) {
|
|
53
|
+
return expiresAt > 1e10 ? expiresAt : expiresAt * 1e3;
|
|
54
|
+
}
|
|
55
|
+
const expired = getString(input.expired) ?? getString(input.expiresAt);
|
|
56
|
+
if (expired) {
|
|
57
|
+
const parsed = Date.parse(expired);
|
|
58
|
+
if (Number.isFinite(parsed)) {
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
throw new Error("\u5BFC\u5165\u5931\u8D25: \u7F3A\u5C11\u6709\u6548\u7684\u8FC7\u671F\u65F6\u95F4\u3002");
|
|
63
|
+
}
|
|
64
|
+
function importProfileFromJson(value) {
|
|
65
|
+
if (!isRecord(value)) {
|
|
66
|
+
throw new Error("\u5BFC\u5165\u5931\u8D25: JSON \u6839\u8282\u70B9\u5FC5\u987B\u662F\u5BF9\u8C61\u3002");
|
|
67
|
+
}
|
|
68
|
+
const access = getString(value.access_token) ?? getString(value.access);
|
|
69
|
+
const refresh = getString(value.refresh_token) ?? getString(value.refresh);
|
|
70
|
+
if (!access || !refresh) {
|
|
71
|
+
throw new Error("\u5BFC\u5165\u5931\u8D25: \u7F3A\u5C11 access_token/access \u6216 refresh_token/refresh\u3002");
|
|
72
|
+
}
|
|
73
|
+
const payload = decodeJwtPayload(access);
|
|
74
|
+
const accountId = extractAccountId(payload, value.account_id ?? value.accountId);
|
|
75
|
+
const email = extractEmail(payload, value.email);
|
|
76
|
+
const expires = parseExpiry(value, payload);
|
|
77
|
+
return {
|
|
78
|
+
provider: "openai-codex",
|
|
79
|
+
profileId: `openai-codex:${accountId}`,
|
|
80
|
+
mode: "oauth_account",
|
|
81
|
+
access,
|
|
82
|
+
refresh,
|
|
83
|
+
expires,
|
|
84
|
+
accountId,
|
|
85
|
+
email
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function importProfilesFromJson(value) {
|
|
89
|
+
const items = Array.isArray(value) ? value : isRecord(value) && Array.isArray(value.profiles) ? value.profiles : [value];
|
|
90
|
+
return items.map((item, index) => {
|
|
91
|
+
try {
|
|
92
|
+
return importProfileFromJson(item);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
throw new Error(`\u7B2C ${index + 1} \u4E2A\u8D26\u53F7${message.startsWith("\u5BFC\u5165\u5931\u8D25") ? message : `\u5BFC\u5165\u5931\u8D25: ${message}`}`);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function exportProfileToJson(profile) {
|
|
100
|
+
return {
|
|
101
|
+
type: "codex",
|
|
102
|
+
access_token: profile.access,
|
|
103
|
+
refresh_token: profile.refresh,
|
|
104
|
+
expired: new Date(profile.expires).toISOString(),
|
|
105
|
+
email: profile.email,
|
|
106
|
+
account_id: profile.accountId,
|
|
107
|
+
profile_id: profile.profileId,
|
|
108
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function exportProfilesToJson(profiles) {
|
|
112
|
+
return {
|
|
113
|
+
type: "codex_profiles",
|
|
114
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
115
|
+
profiles: profiles.map((profile) => exportProfileToJson(profile))
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function getProfileImportTemplate() {
|
|
119
|
+
return {
|
|
120
|
+
type: "codex_profiles",
|
|
121
|
+
exported_at: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
122
|
+
profiles: [
|
|
123
|
+
{
|
|
124
|
+
type: "codex",
|
|
125
|
+
access_token: "eyJ...access_token",
|
|
126
|
+
refresh_token: "rt_...",
|
|
127
|
+
expired: "2026-05-04T22:13:00.000Z",
|
|
128
|
+
email: "user@example.com",
|
|
129
|
+
account_id: "\u53EF\u9009\uFF0C\u901A\u5E38\u4F1A\u4ECE access_token \u81EA\u52A8\u89E3\u6790",
|
|
130
|
+
profile_id: "\u53EF\u9009\uFF0C\u5BFC\u5165\u65F6\u4F1A\u6309 account_id \u81EA\u52A8\u751F\u6210",
|
|
131
|
+
exported_at: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
export {
|
|
137
|
+
exportProfileToJson,
|
|
138
|
+
exportProfilesToJson,
|
|
139
|
+
getProfileImportTemplate,
|
|
140
|
+
importProfileFromJson,
|
|
141
|
+
importProfilesFromJson
|
|
142
|
+
};
|
|
@@ -10,6 +10,11 @@ function createDefaultSettings() {
|
|
|
10
10
|
version: 1,
|
|
11
11
|
defaultProvider: "openai-codex",
|
|
12
12
|
defaultModel: "gpt-5.4",
|
|
13
|
+
networkProxy: {
|
|
14
|
+
enabled: false,
|
|
15
|
+
url: "",
|
|
16
|
+
noProxy: "localhost,127.0.0.1,::1"
|
|
17
|
+
},
|
|
13
18
|
server: {
|
|
14
19
|
host: "0.0.0.0",
|
|
15
20
|
port: 8787
|
|
@@ -26,6 +31,11 @@ async function loadSettings() {
|
|
|
26
31
|
version: 1,
|
|
27
32
|
defaultProvider: parsed.defaultProvider ?? defaults.defaultProvider,
|
|
28
33
|
defaultModel: parsed.defaultModel ?? defaults.defaultModel,
|
|
34
|
+
networkProxy: {
|
|
35
|
+
enabled: parsed.networkProxy?.enabled ?? defaults.networkProxy.enabled,
|
|
36
|
+
url: parsed.networkProxy?.url ?? defaults.networkProxy.url,
|
|
37
|
+
noProxy: parsed.networkProxy?.noProxy ?? defaults.networkProxy.noProxy
|
|
38
|
+
},
|
|
29
39
|
server: {
|
|
30
40
|
host: parsed.server?.host ?? defaults.server.host,
|
|
31
41
|
port: parsed.server?.port ?? defaults.server.port
|
|
@@ -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 {
|
|
@@ -990,6 +1027,21 @@ function renderAdminPage() {
|
|
|
990
1027
|
color: var(--text-soft);
|
|
991
1028
|
}
|
|
992
1029
|
|
|
1030
|
+
.checkbox-row {
|
|
1031
|
+
display: flex;
|
|
1032
|
+
align-items: center;
|
|
1033
|
+
gap: 10px;
|
|
1034
|
+
color: var(--text-soft);
|
|
1035
|
+
font-size: 13px;
|
|
1036
|
+
font-weight: 600;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
.checkbox-row input {
|
|
1040
|
+
width: 16px;
|
|
1041
|
+
height: 16px;
|
|
1042
|
+
accent-color: var(--brand);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
993
1045
|
.pre {
|
|
994
1046
|
margin: 0;
|
|
995
1047
|
padding: 14px;
|
|
@@ -1350,7 +1402,7 @@ function renderAdminPage() {
|
|
|
1350
1402
|
<strong id="updatePanelTitle">\u53D1\u73B0\u65B0\u7248\u672C</strong>
|
|
1351
1403
|
<span id="updatePanelDetail"></span>
|
|
1352
1404
|
</div>
|
|
1353
|
-
<code class="update-command" id="updatePanelCommand">npm install -g ai-zero-token
|
|
1405
|
+
<code class="update-command" id="updatePanelCommand">npm install -g ai-zero-token</code>
|
|
1354
1406
|
</section>
|
|
1355
1407
|
|
|
1356
1408
|
<section class="summary-grid" id="summaryGrid"></section>
|
|
@@ -1365,6 +1417,7 @@ function renderAdminPage() {
|
|
|
1365
1417
|
</div>
|
|
1366
1418
|
<div class="actions">
|
|
1367
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>
|
|
1368
1421
|
</div>
|
|
1369
1422
|
</div>
|
|
1370
1423
|
|
|
@@ -1383,8 +1436,8 @@ function renderAdminPage() {
|
|
|
1383
1436
|
<option value="expiry-asc">\u6309\u8FC7\u671F\u65F6\u95F4</option>
|
|
1384
1437
|
<option value="name-asc">\u6309\u90AE\u7BB1\u6392\u5E8F</option>
|
|
1385
1438
|
</select>
|
|
1439
|
+
<span class="account-selected-count" id="selectedProfileCount">\u5DF2\u9009\u62E9 0 \u4E2A</span>
|
|
1386
1440
|
</div>
|
|
1387
|
-
|
|
1388
1441
|
<div class="account-grid" id="profileList"></div>
|
|
1389
1442
|
</section>
|
|
1390
1443
|
|
|
@@ -1465,6 +1518,21 @@ function renderAdminPage() {
|
|
|
1465
1518
|
</div>
|
|
1466
1519
|
</div>
|
|
1467
1520
|
|
|
1521
|
+
<div class="field">
|
|
1522
|
+
<label class="checkbox-row" for="proxyEnabled">
|
|
1523
|
+
<input id="proxyEnabled" type="checkbox" />
|
|
1524
|
+
\u542F\u7528\u4E0A\u6E38\u4EE3\u7406
|
|
1525
|
+
</label>
|
|
1526
|
+
<label for="proxyUrl">\u4EE3\u7406\u5730\u5740</label>
|
|
1527
|
+
<input class="input" id="proxyUrl" type="text" placeholder="\u586B\u5199\u4F60\u7684\u4EE3\u7406\u5730\u5740" />
|
|
1528
|
+
<label for="proxyNoProxy">\u76F4\u8FDE\u5730\u5740</label>
|
|
1529
|
+
<input class="input" id="proxyNoProxy" type="text" placeholder="localhost,127.0.0.1,::1" />
|
|
1530
|
+
<p class="hint">\u542F\u7528\u540E\uFF0COAuth \u6362\u53D6 token\u3001\u6A21\u578B\u5237\u65B0\u548C\u63A5\u53E3\u8F6C\u53D1\u4F1A\u901A\u8FC7\u6B64\u4EE3\u7406\u8BBF\u95EE\u6D77\u5916\u4E0A\u6E38\u3002</p>
|
|
1531
|
+
<div class="actions">
|
|
1532
|
+
<button class="btn-primary" id="saveProxyBtn" type="button">\u4FDD\u5B58\u4EE3\u7406\u914D\u7F6E</button>
|
|
1533
|
+
</div>
|
|
1534
|
+
</div>
|
|
1535
|
+
|
|
1468
1536
|
<div class="field">
|
|
1469
1537
|
<label for="requestBody">\u8BF7\u6C42\u4F53 JSON</label>
|
|
1470
1538
|
<textarea class="textarea" id="requestBody" spellcheck="false"></textarea>
|
|
@@ -1548,6 +1616,41 @@ function renderAdminPage() {
|
|
|
1548
1616
|
</section>
|
|
1549
1617
|
</div>
|
|
1550
1618
|
|
|
1619
|
+
<div class="modal-backdrop" id="accountModal" aria-hidden="true">
|
|
1620
|
+
<section class="modal-card" role="dialog" aria-modal="true" aria-labelledby="accountModalTitle">
|
|
1621
|
+
<div class="modal-head">
|
|
1622
|
+
<div>
|
|
1623
|
+
<h3 id="accountModalTitle">\u65B0\u589E\u8D26\u53F7</h3>
|
|
1624
|
+
<p>\u9009\u62E9 OAuth \u767B\u5F55\uFF0C\u6216\u7C98\u8D34\u5355\u4E2A/\u6279\u91CF\u8D26\u53F7 JSON \u5BFC\u5165\u3002</p>
|
|
1625
|
+
</div>
|
|
1626
|
+
<div class="actions">
|
|
1627
|
+
<button class="btn-secondary" id="closeAccountModalBtn" type="button">\u5173\u95ED</button>
|
|
1628
|
+
</div>
|
|
1629
|
+
</div>
|
|
1630
|
+
<div class="modal-body account-modal-body">
|
|
1631
|
+
<div class="contact-notes">
|
|
1632
|
+
<div class="contact-note">
|
|
1633
|
+
<strong>\u767B\u5F55\u65B0\u589E</strong>
|
|
1634
|
+
<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>
|
|
1635
|
+
<button class="btn-primary" id="oauthLoginBtn" type="button">\u767B\u5F55</button>
|
|
1636
|
+
</div>
|
|
1637
|
+
<div class="contact-note">
|
|
1638
|
+
<strong>\u6279\u91CF\u5BFC\u5165</strong>
|
|
1639
|
+
<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>
|
|
1640
|
+
<div class="actions">
|
|
1641
|
+
<button class="btn-secondary" id="loadImportTemplateBtn" type="button">\u586B\u5165\u53C2\u8003\u683C\u5F0F</button>
|
|
1642
|
+
<button class="btn-primary" id="importProfileBtn" type="button">\u5BFC\u5165</button>
|
|
1643
|
+
</div>
|
|
1644
|
+
</div>
|
|
1645
|
+
</div>
|
|
1646
|
+
<div>
|
|
1647
|
+
<textarea class="textarea" id="profileImportJson" spellcheck="false" placeholder='\u7C98\u8D34\u8D26\u53F7 JSON\uFF0C\u652F\u6301 { "profiles": [...] } \u6279\u91CF\u5BFC\u5165'></textarea>
|
|
1648
|
+
<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>
|
|
1649
|
+
</div>
|
|
1650
|
+
</div>
|
|
1651
|
+
</section>
|
|
1652
|
+
</div>
|
|
1653
|
+
|
|
1551
1654
|
<div class="modal-backdrop" id="contactModal" aria-hidden="true">
|
|
1552
1655
|
<section class="modal-card" role="dialog" aria-modal="true" aria-labelledby="contactModalTitle">
|
|
1553
1656
|
<div class="modal-head">
|
|
@@ -1592,6 +1695,7 @@ function renderAdminPage() {
|
|
|
1592
1695
|
status: "all",
|
|
1593
1696
|
sort: "quota-desc",
|
|
1594
1697
|
},
|
|
1698
|
+
selectedProfileIds: {},
|
|
1595
1699
|
testerResultTab: "response",
|
|
1596
1700
|
};
|
|
1597
1701
|
|
|
@@ -1629,6 +1733,7 @@ function renderAdminPage() {
|
|
|
1629
1733
|
const imageCapabilityHint = document.getElementById("imageCapabilityHint");
|
|
1630
1734
|
const runTestBtn = document.getElementById("runTestBtn");
|
|
1631
1735
|
const toggleEmailBtn = document.getElementById("toggleEmailBtn");
|
|
1736
|
+
const accountModal = document.getElementById("accountModal");
|
|
1632
1737
|
const contactModal = document.getElementById("contactModal");
|
|
1633
1738
|
const imagePreviewModal = document.getElementById("imagePreviewModal");
|
|
1634
1739
|
const contactBtn = document.getElementById("contactBtn");
|
|
@@ -1638,6 +1743,15 @@ function renderAdminPage() {
|
|
|
1638
1743
|
const profileSearch = document.getElementById("profileSearch");
|
|
1639
1744
|
const profileStatusFilter = document.getElementById("profileStatusFilter");
|
|
1640
1745
|
const profileSort = document.getElementById("profileSort");
|
|
1746
|
+
const profileImportJson = document.getElementById("profileImportJson");
|
|
1747
|
+
const importProfileBtn = document.getElementById("importProfileBtn");
|
|
1748
|
+
const oauthLoginBtn = document.getElementById("oauthLoginBtn");
|
|
1749
|
+
const loadImportTemplateBtn = document.getElementById("loadImportTemplateBtn");
|
|
1750
|
+
const exportSelectedProfilesBtn = document.getElementById("exportSelectedProfilesBtn");
|
|
1751
|
+
const selectedProfileCount = document.getElementById("selectedProfileCount");
|
|
1752
|
+
const proxyEnabled = document.getElementById("proxyEnabled");
|
|
1753
|
+
const proxyUrl = document.getElementById("proxyUrl");
|
|
1754
|
+
const proxyNoProxy = document.getElementById("proxyNoProxy");
|
|
1641
1755
|
|
|
1642
1756
|
function setBusy(button, busy) {
|
|
1643
1757
|
if (button) {
|
|
@@ -2336,8 +2450,35 @@ function renderAdminPage() {
|
|
|
2336
2450
|
return filtered;
|
|
2337
2451
|
}
|
|
2338
2452
|
|
|
2453
|
+
function getSelectedProfileIds() {
|
|
2454
|
+
return Object.keys(state.selectedProfileIds).filter(function (profileId) {
|
|
2455
|
+
return !!state.selectedProfileIds[profileId];
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
function syncSelectedProfiles(config) {
|
|
2460
|
+
const profiles = Array.isArray(config.profiles) ? config.profiles : [];
|
|
2461
|
+
const availableIds = profiles.reduce(function (result, profile) {
|
|
2462
|
+
result[profile.profileId] = true;
|
|
2463
|
+
return result;
|
|
2464
|
+
}, {});
|
|
2465
|
+
getSelectedProfileIds().forEach(function (profileId) {
|
|
2466
|
+
if (!availableIds[profileId]) {
|
|
2467
|
+
delete state.selectedProfileIds[profileId];
|
|
2468
|
+
}
|
|
2469
|
+
});
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
function updateSelectedProfileControls() {
|
|
2473
|
+
const count = getSelectedProfileIds().length;
|
|
2474
|
+
selectedProfileCount.textContent = "\u5DF2\u9009\u62E9 " + String(count) + " \u4E2A";
|
|
2475
|
+
exportSelectedProfilesBtn.disabled = count === 0;
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2339
2478
|
function renderProfiles(config) {
|
|
2340
2479
|
const container = document.getElementById("profileList");
|
|
2480
|
+
syncSelectedProfiles(config);
|
|
2481
|
+
updateSelectedProfileControls();
|
|
2341
2482
|
const profiles = getFilteredProfiles(config);
|
|
2342
2483
|
const gridClass = profiles.length <= 0
|
|
2343
2484
|
? ""
|
|
@@ -2356,6 +2497,7 @@ function renderAdminPage() {
|
|
|
2356
2497
|
}
|
|
2357
2498
|
|
|
2358
2499
|
container.innerHTML = profiles.map(function (profile) {
|
|
2500
|
+
const selected = !!state.selectedProfileIds[profile.profileId];
|
|
2359
2501
|
const isSingleProfile = profiles.length === 1;
|
|
2360
2502
|
const health = getProfileHealth(profile);
|
|
2361
2503
|
const planType = getPlanType(profile);
|
|
@@ -2385,6 +2527,7 @@ function renderAdminPage() {
|
|
|
2385
2527
|
+ '<span class="badge ' + escapeHtml(imageCapability.badgeClass) + '">' + escapeHtml(imageCapability.label) + "</span>"
|
|
2386
2528
|
+ "</div>"
|
|
2387
2529
|
+ "</div>"
|
|
2530
|
+
+ '<label class="account-select"><input type="checkbox" data-profile-select data-profile-id="' + escapeHtml(profile.profileId) + '"' + (selected ? " checked" : "") + " /><span>\u9009\u62E9</span></label>"
|
|
2388
2531
|
+ "</div>"
|
|
2389
2532
|
+ '<div class="account-metrics">'
|
|
2390
2533
|
+ '<div class="quota-row">'
|
|
@@ -2407,6 +2550,7 @@ function renderAdminPage() {
|
|
|
2407
2550
|
+ "</div>"
|
|
2408
2551
|
+ '<div class="account-actions">'
|
|
2409
2552
|
+ actionButton
|
|
2553
|
+
+ '<button class="btn-secondary" type="button" data-profile-action="export" data-profile-id="' + escapeHtml(profile.profileId) + '">\u5BFC\u51FA</button>'
|
|
2410
2554
|
+ '<button class="btn-danger" type="button" data-profile-action="remove" data-profile-id="' + escapeHtml(profile.profileId) + '">\u5220\u9664</button>'
|
|
2411
2555
|
+ "</div>"
|
|
2412
2556
|
+ "</article>";
|
|
@@ -2567,6 +2711,7 @@ function renderAdminPage() {
|
|
|
2567
2711
|
["Base URL", config.baseUrl],
|
|
2568
2712
|
["Provider", config.status.activeProvider || "openai-codex"],
|
|
2569
2713
|
["\u9ED8\u8BA4\u6A21\u578B", config.settings.defaultModel],
|
|
2714
|
+
["\u4E0A\u6E38\u4EE3\u7406", config.settings.networkProxy && config.settings.networkProxy.enabled ? "\u5DF2\u542F\u7528" : "\u672A\u542F\u7528"],
|
|
2570
2715
|
["\u5F53\u524D\u7248\u672C", getVersionValue(config)],
|
|
2571
2716
|
["\u5F53\u524D\u5957\u9910", config.profile ? getPlanType(config.profile) : "\u672A\u767B\u5F55"],
|
|
2572
2717
|
["\u751F\u56FE\u80FD\u529B", getImageCapability(config.profile).detail],
|
|
@@ -2585,6 +2730,7 @@ function renderAdminPage() {
|
|
|
2585
2730
|
["API Base URL", config.baseUrl],
|
|
2586
2731
|
["\u5F53\u524D\u8D26\u53F7", getProfileDisplayLabel(config.profile)],
|
|
2587
2732
|
["\u9ED8\u8BA4\u6A21\u578B", config.settings.defaultModel],
|
|
2733
|
+
["\u4E0A\u6E38\u4EE3\u7406", config.settings.networkProxy && config.settings.networkProxy.enabled ? config.settings.networkProxy.url : "\u672A\u542F\u7528"],
|
|
2588
2734
|
["\u7248\u672C\u72B6\u6001", getVersionDetail(config)],
|
|
2589
2735
|
["\u5F53\u524D\u5957\u9910", config.profile ? getPlanType(config.profile) : "\u672A\u767B\u5F55"],
|
|
2590
2736
|
["\u751F\u56FE\u80FD\u529B", getImageCapability(config.profile).detail],
|
|
@@ -2640,6 +2786,17 @@ function renderAdminPage() {
|
|
|
2640
2786
|
hint.textContent = parts.join("\uFF0C") + "\u3002";
|
|
2641
2787
|
}
|
|
2642
2788
|
|
|
2789
|
+
function renderProxySettings(config) {
|
|
2790
|
+
const proxy = config.settings.networkProxy || {
|
|
2791
|
+
enabled: false,
|
|
2792
|
+
url: "",
|
|
2793
|
+
noProxy: "localhost,127.0.0.1,::1",
|
|
2794
|
+
};
|
|
2795
|
+
proxyEnabled.checked = !!proxy.enabled;
|
|
2796
|
+
proxyUrl.value = proxy.url || "";
|
|
2797
|
+
proxyNoProxy.value = proxy.noProxy || "localhost,127.0.0.1,::1";
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2643
2800
|
function syncHero(config) {
|
|
2644
2801
|
const profileText = config.profile
|
|
2645
2802
|
? "\u5F53\u524D\u8D26\u53F7\u4E3A " + getProfileDisplayLabel(config.profile) + "\uFF0C\u5957\u9910 " + getPlanType(config.profile) + "\uFF0C\u53EF\u5728\u53F3\u4FA7\u5B8C\u6210\u6A21\u578B\u5207\u6362\u548C\u63A5\u53E3\u8C03\u8BD5\u3002"
|
|
@@ -2676,6 +2833,7 @@ function renderAdminPage() {
|
|
|
2676
2833
|
renderProfiles(config);
|
|
2677
2834
|
renderModelOptions(config);
|
|
2678
2835
|
renderModelCatalogStatus(config);
|
|
2836
|
+
renderProxySettings(config);
|
|
2679
2837
|
renderUpdatePanel(config);
|
|
2680
2838
|
renderEndpoints(config);
|
|
2681
2839
|
renderServiceInfo(config);
|
|
@@ -2785,7 +2943,7 @@ function renderAdminPage() {
|
|
|
2785
2943
|
}
|
|
2786
2944
|
|
|
2787
2945
|
async function login() {
|
|
2788
|
-
const button =
|
|
2946
|
+
const button = oauthLoginBtn;
|
|
2789
2947
|
setBusy(button, true);
|
|
2790
2948
|
authStatus.textContent = "\u6B63\u5728\u65B0\u589E\u8D26\u53F7\u3001\u7B49\u5F85 OAuth \u5B8C\u6210\uFF0C\u5E76\u540C\u6B65\u989D\u5EA6\u4FE1\u606F...";
|
|
2791
2949
|
try {
|
|
@@ -2798,6 +2956,7 @@ function renderAdminPage() {
|
|
|
2798
2956
|
if (config.profile && config.profile.quota) {
|
|
2799
2957
|
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);
|
|
2800
2958
|
}
|
|
2959
|
+
closeAccountModal();
|
|
2801
2960
|
} catch (error) {
|
|
2802
2961
|
authStatus.textContent = error.message;
|
|
2803
2962
|
} finally {
|
|
@@ -2824,6 +2983,11 @@ function renderAdminPage() {
|
|
|
2824
2983
|
}
|
|
2825
2984
|
|
|
2826
2985
|
async function runProfileAction(action, profileId, button) {
|
|
2986
|
+
if (action === "export") {
|
|
2987
|
+
await exportProfile(profileId, button);
|
|
2988
|
+
return;
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2827
2991
|
setBusy(button, true);
|
|
2828
2992
|
authStatus.textContent = action === "activate" ? "\u6B63\u5728\u5207\u6362\u5F53\u524D\u8D26\u53F7..." : "\u6B63\u5728\u5220\u9664\u8D26\u53F7...";
|
|
2829
2993
|
try {
|
|
@@ -2856,6 +3020,116 @@ function renderAdminPage() {
|
|
|
2856
3020
|
}
|
|
2857
3021
|
}
|
|
2858
3022
|
|
|
3023
|
+
function downloadJsonFile(fileName, value) {
|
|
3024
|
+
const blob = new Blob([formatJson(value) + "\\n"], { type: "application/json" });
|
|
3025
|
+
const url = URL.createObjectURL(blob);
|
|
3026
|
+
const link = document.createElement("a");
|
|
3027
|
+
link.href = url;
|
|
3028
|
+
link.download = fileName;
|
|
3029
|
+
document.body.appendChild(link);
|
|
3030
|
+
link.click();
|
|
3031
|
+
link.remove();
|
|
3032
|
+
URL.revokeObjectURL(url);
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
async function importProfile() {
|
|
3036
|
+
const raw = profileImportJson.value.trim();
|
|
3037
|
+
if (!raw) {
|
|
3038
|
+
authStatus.textContent = "\u8BF7\u5148\u7C98\u8D34\u8981\u5BFC\u5165\u7684\u8D26\u53F7 JSON\u3002";
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
let payload;
|
|
3043
|
+
try {
|
|
3044
|
+
payload = JSON.parse(raw);
|
|
3045
|
+
} catch (_error) {
|
|
3046
|
+
authStatus.textContent = "\u5BFC\u5165\u5931\u8D25: JSON \u683C\u5F0F\u4E0D\u6B63\u786E\u3002";
|
|
3047
|
+
return;
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
setBusy(importProfileBtn, true);
|
|
3051
|
+
authStatus.textContent = "\u6B63\u5728\u5BFC\u5165\u8D26\u53F7\u5E76\u540C\u6B65\u989D\u5EA6\u4FE1\u606F...";
|
|
3052
|
+
try {
|
|
3053
|
+
let config = await fetchJson("/_gateway/admin/profiles/import", {
|
|
3054
|
+
method: "POST",
|
|
3055
|
+
headers: {
|
|
3056
|
+
"Content-Type": "application/json",
|
|
3057
|
+
},
|
|
3058
|
+
body: formatJson({
|
|
3059
|
+
profile: payload,
|
|
3060
|
+
}),
|
|
3061
|
+
});
|
|
3062
|
+
renderConfig(config);
|
|
3063
|
+
const count = config.importedProfileCount || 1;
|
|
3064
|
+
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);
|
|
3065
|
+
config = await syncQuotaAfterProfileChange(config, baseMessage);
|
|
3066
|
+
if (config.profile && config.profile.quota) {
|
|
3067
|
+
authStatus.textContent = "\u5DF2\u5BFC\u5165 " + String(count) + " \u4E2A\u8D26\u53F7\uFF0C\u989D\u5EA6\u4FE1\u606F\u5DF2\u540C\u6B65: " + getProfileDisplayLabel(config.profile);
|
|
3068
|
+
}
|
|
3069
|
+
profileImportJson.value = "";
|
|
3070
|
+
closeAccountModal();
|
|
3071
|
+
} catch (error) {
|
|
3072
|
+
authStatus.textContent = error.message;
|
|
3073
|
+
} finally {
|
|
3074
|
+
setBusy(importProfileBtn, false);
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
async function loadImportTemplate() {
|
|
3079
|
+
setBusy(loadImportTemplateBtn, true);
|
|
3080
|
+
authStatus.textContent = "\u6B63\u5728\u8BFB\u53D6\u5BFC\u5165\u53C2\u8003\u683C\u5F0F...";
|
|
3081
|
+
try {
|
|
3082
|
+
const result = await fetchJson("/_gateway/admin/profiles/import-template");
|
|
3083
|
+
profileImportJson.value = formatJson(result.profile);
|
|
3084
|
+
authStatus.textContent = "\u5DF2\u586B\u5165\u53C2\u8003\u683C\u5F0F\uFF0C\u53EF\u66FF\u6362\u5176\u4E2D\u7684 token \u540E\u5BFC\u5165\u3002";
|
|
3085
|
+
} catch (error) {
|
|
3086
|
+
authStatus.textContent = error.message;
|
|
3087
|
+
} finally {
|
|
3088
|
+
setBusy(loadImportTemplateBtn, false);
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
async function exportProfile(profileId, button, options) {
|
|
3093
|
+
setBusy(button, true);
|
|
3094
|
+
authStatus.textContent = "\u6B63\u5728\u5BFC\u51FA\u8D26\u53F7\u914D\u7F6E...";
|
|
3095
|
+
try {
|
|
3096
|
+
const exportAll = !!(options && options.all);
|
|
3097
|
+
const profileIds = options && Array.isArray(options.profileIds) ? options.profileIds : null;
|
|
3098
|
+
const result = await fetchJson("/_gateway/admin/profiles/export", {
|
|
3099
|
+
method: "POST",
|
|
3100
|
+
headers: {
|
|
3101
|
+
"Content-Type": "application/json",
|
|
3102
|
+
},
|
|
3103
|
+
body: formatJson(profileIds ? { profileIds: profileIds } : exportAll ? { all: true } : { profileId: profileId }),
|
|
3104
|
+
});
|
|
3105
|
+
const profile = result.profile;
|
|
3106
|
+
const isBundle = profile && Array.isArray(profile.profiles);
|
|
3107
|
+
const suffix = isBundle
|
|
3108
|
+
? "profiles-" + String(profile.profiles.length)
|
|
3109
|
+
: profile && profile.account_id ? profile.account_id : "active";
|
|
3110
|
+
downloadJsonFile("ai-zero-token-" + suffix + ".json", profile);
|
|
3111
|
+
authStatus.textContent = isBundle
|
|
3112
|
+
? "\u5DF2\u6279\u91CF\u5BFC\u51FA " + String(profile.profiles.length) + " \u4E2A\u8D26\u53F7\u3002\u8BF7\u59A5\u5584\u4FDD\u7BA1\u5BFC\u51FA\u7684 refresh token\u3002"
|
|
3113
|
+
: "\u8D26\u53F7\u914D\u7F6E\u5DF2\u5BFC\u51FA\u3002\u8BF7\u59A5\u5584\u4FDD\u7BA1\u5BFC\u51FA\u7684 refresh token\u3002";
|
|
3114
|
+
} catch (error) {
|
|
3115
|
+
authStatus.textContent = error.message;
|
|
3116
|
+
} finally {
|
|
3117
|
+
setBusy(button, false);
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
async function exportSelectedProfiles() {
|
|
3122
|
+
const profileIds = getSelectedProfileIds();
|
|
3123
|
+
if (profileIds.length === 0) {
|
|
3124
|
+
authStatus.textContent = "\u8BF7\u5148\u52FE\u9009\u8981\u5BFC\u51FA\u7684\u8D26\u53F7\u3002";
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
await exportProfile(null, exportSelectedProfilesBtn, {
|
|
3129
|
+
profileIds: profileIds,
|
|
3130
|
+
});
|
|
3131
|
+
}
|
|
3132
|
+
|
|
2859
3133
|
async function saveModel() {
|
|
2860
3134
|
const button = document.getElementById("saveModelBtn");
|
|
2861
3135
|
const select = document.getElementById("defaultModel");
|
|
@@ -2880,6 +3154,33 @@ function renderAdminPage() {
|
|
|
2880
3154
|
}
|
|
2881
3155
|
}
|
|
2882
3156
|
|
|
3157
|
+
async function saveProxy() {
|
|
3158
|
+
const button = document.getElementById("saveProxyBtn");
|
|
3159
|
+
setBusy(button, true);
|
|
3160
|
+
authStatus.textContent = "\u6B63\u5728\u4FDD\u5B58\u4EE3\u7406\u914D\u7F6E...";
|
|
3161
|
+
try {
|
|
3162
|
+
const config = await fetchJson("/_gateway/admin/settings", {
|
|
3163
|
+
method: "PUT",
|
|
3164
|
+
headers: {
|
|
3165
|
+
"Content-Type": "application/json",
|
|
3166
|
+
},
|
|
3167
|
+
body: formatJson({
|
|
3168
|
+
networkProxy: {
|
|
3169
|
+
enabled: proxyEnabled.checked,
|
|
3170
|
+
url: proxyUrl.value,
|
|
3171
|
+
noProxy: proxyNoProxy.value,
|
|
3172
|
+
},
|
|
3173
|
+
}),
|
|
3174
|
+
});
|
|
3175
|
+
renderConfig(config);
|
|
3176
|
+
authStatus.textContent = proxyEnabled.checked ? "\u4EE3\u7406\u914D\u7F6E\u5DF2\u542F\u7528\u3002" : "\u4EE3\u7406\u914D\u7F6E\u5DF2\u5173\u95ED\u3002";
|
|
3177
|
+
} catch (error) {
|
|
3178
|
+
authStatus.textContent = error.message;
|
|
3179
|
+
} finally {
|
|
3180
|
+
setBusy(button, false);
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
|
|
2883
3184
|
async function refreshModels() {
|
|
2884
3185
|
const button = document.getElementById("refreshModelsBtn");
|
|
2885
3186
|
setBusy(button, true);
|
|
@@ -3007,6 +3308,16 @@ function renderAdminPage() {
|
|
|
3007
3308
|
}
|
|
3008
3309
|
}
|
|
3009
3310
|
|
|
3311
|
+
function openAccountModal() {
|
|
3312
|
+
accountModal.classList.add("is-open");
|
|
3313
|
+
accountModal.setAttribute("aria-hidden", "false");
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
function closeAccountModal() {
|
|
3317
|
+
accountModal.classList.remove("is-open");
|
|
3318
|
+
accountModal.setAttribute("aria-hidden", "true");
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3010
3321
|
function openContactModal() {
|
|
3011
3322
|
contactModal.classList.add("is-open");
|
|
3012
3323
|
contactModal.setAttribute("aria-hidden", "false");
|
|
@@ -3017,7 +3328,7 @@ function renderAdminPage() {
|
|
|
3017
3328
|
contactModal.setAttribute("aria-hidden", "true");
|
|
3018
3329
|
}
|
|
3019
3330
|
|
|
3020
|
-
document.getElementById("loginBtn").addEventListener("click",
|
|
3331
|
+
document.getElementById("loginBtn").addEventListener("click", openAccountModal);
|
|
3021
3332
|
document.getElementById("refreshBtn").addEventListener("click", function () {
|
|
3022
3333
|
authStatus.textContent = "\u6B63\u5728\u540C\u6B65\u989D\u5EA6\u4E0E\u7248\u672C\u72B6\u6001...";
|
|
3023
3334
|
refreshConfig({
|
|
@@ -3029,11 +3340,17 @@ function renderAdminPage() {
|
|
|
3029
3340
|
});
|
|
3030
3341
|
});
|
|
3031
3342
|
document.getElementById("logoutBtn").addEventListener("click", logout);
|
|
3343
|
+
oauthLoginBtn.addEventListener("click", login);
|
|
3344
|
+
importProfileBtn.addEventListener("click", importProfile);
|
|
3345
|
+
loadImportTemplateBtn.addEventListener("click", loadImportTemplate);
|
|
3346
|
+
exportSelectedProfilesBtn.addEventListener("click", exportSelectedProfiles);
|
|
3347
|
+
document.getElementById("closeAccountModalBtn").addEventListener("click", closeAccountModal);
|
|
3032
3348
|
contactBtn.addEventListener("click", openContactModal);
|
|
3033
3349
|
document.getElementById("closeContactBtn").addEventListener("click", closeContactModal);
|
|
3034
3350
|
document.getElementById("closeImagePreviewBtn").addEventListener("click", closeImagePreviewModal);
|
|
3035
3351
|
document.getElementById("refreshModelsBtn").addEventListener("click", refreshModels);
|
|
3036
3352
|
document.getElementById("saveModelBtn").addEventListener("click", saveModel);
|
|
3353
|
+
document.getElementById("saveProxyBtn").addEventListener("click", saveProxy);
|
|
3037
3354
|
runTestBtn.addEventListener("click", runTest);
|
|
3038
3355
|
document.querySelectorAll("[data-result-tab]").forEach(function (button) {
|
|
3039
3356
|
button.addEventListener("click", function () {
|
|
@@ -3049,6 +3366,10 @@ function renderAdminPage() {
|
|
|
3049
3366
|
});
|
|
3050
3367
|
|
|
3051
3368
|
document.getElementById("profileList").addEventListener("click", function (event) {
|
|
3369
|
+
if (event.target.closest("[data-profile-select]")) {
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3052
3373
|
const button = event.target.closest("[data-profile-action]");
|
|
3053
3374
|
if (!button) {
|
|
3054
3375
|
return;
|
|
@@ -3063,6 +3384,25 @@ function renderAdminPage() {
|
|
|
3063
3384
|
runProfileAction(action, profileId, button);
|
|
3064
3385
|
});
|
|
3065
3386
|
|
|
3387
|
+
document.getElementById("profileList").addEventListener("change", function (event) {
|
|
3388
|
+
const checkbox = event.target.closest("[data-profile-select]");
|
|
3389
|
+
if (!checkbox) {
|
|
3390
|
+
return;
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
const profileId = checkbox.getAttribute("data-profile-id");
|
|
3394
|
+
if (!profileId) {
|
|
3395
|
+
return;
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
if (checkbox.checked) {
|
|
3399
|
+
state.selectedProfileIds[profileId] = true;
|
|
3400
|
+
} else {
|
|
3401
|
+
delete state.selectedProfileIds[profileId];
|
|
3402
|
+
}
|
|
3403
|
+
updateSelectedProfileControls();
|
|
3404
|
+
});
|
|
3405
|
+
|
|
3066
3406
|
document.getElementById("activateCurrentBtn").addEventListener("click", function () {
|
|
3067
3407
|
if (!state.config || !state.config.profile || !state.config.profile.profileId) {
|
|
3068
3408
|
return;
|
|
@@ -3134,6 +3474,12 @@ function renderAdminPage() {
|
|
|
3134
3474
|
}
|
|
3135
3475
|
});
|
|
3136
3476
|
|
|
3477
|
+
accountModal.addEventListener("click", function (event) {
|
|
3478
|
+
if (event.target === accountModal) {
|
|
3479
|
+
closeAccountModal();
|
|
3480
|
+
}
|
|
3481
|
+
});
|
|
3482
|
+
|
|
3137
3483
|
imagePreviewModal.addEventListener("click", function (event) {
|
|
3138
3484
|
if (event.target === imagePreviewModal) {
|
|
3139
3485
|
closeImagePreviewModal();
|
|
@@ -3147,6 +3493,9 @@ function renderAdminPage() {
|
|
|
3147
3493
|
if (event.key === "Escape" && contactModal.classList.contains("is-open")) {
|
|
3148
3494
|
closeContactModal();
|
|
3149
3495
|
}
|
|
3496
|
+
if (event.key === "Escape" && accountModal.classList.contains("is-open")) {
|
|
3497
|
+
closeAccountModal();
|
|
3498
|
+
}
|
|
3150
3499
|
});
|
|
3151
3500
|
|
|
3152
3501
|
setTesterResultTab(state.testerResultTab);
|
package/dist/server/app.js
CHANGED
|
@@ -67,17 +67,30 @@ const chatCompletionsBodySchema = z.object({
|
|
|
67
67
|
user: z.string().optional()
|
|
68
68
|
}).passthrough();
|
|
69
69
|
const settingsUpdateSchema = z.object({
|
|
70
|
-
defaultModel: z.string().min(1)
|
|
70
|
+
defaultModel: z.string().min(1).optional(),
|
|
71
|
+
networkProxy: z.object({
|
|
72
|
+
enabled: z.boolean(),
|
|
73
|
+
url: z.string().optional(),
|
|
74
|
+
noProxy: z.string().optional()
|
|
75
|
+
}).optional()
|
|
71
76
|
});
|
|
72
77
|
const profileActionSchema = z.object({
|
|
73
78
|
profileId: z.string().min(1)
|
|
74
79
|
});
|
|
80
|
+
const profileImportSchema = z.object({
|
|
81
|
+
profile: z.unknown()
|
|
82
|
+
});
|
|
83
|
+
const profileExportSchema = z.object({
|
|
84
|
+
profileId: z.string().min(1).optional(),
|
|
85
|
+
profileIds: z.array(z.string().min(1)).optional(),
|
|
86
|
+
all: z.boolean().optional()
|
|
87
|
+
});
|
|
75
88
|
const imageGenerationsBodySchema = z.object({
|
|
76
89
|
prompt: z.string().min(1),
|
|
77
90
|
model: z.string().optional(),
|
|
78
91
|
n: z.number().int().positive().optional(),
|
|
79
92
|
quality: z.enum(["low", "medium", "high", "auto"]).optional(),
|
|
80
|
-
size: z.
|
|
93
|
+
size: z.string().min(1).optional(),
|
|
81
94
|
background: z.enum(["transparent", "opaque", "auto"]).optional(),
|
|
82
95
|
output_format: z.enum(["png", "webp", "jpeg"]).optional(),
|
|
83
96
|
output_compression: z.number().int().min(0).max(100).optional(),
|
|
@@ -462,6 +475,49 @@ function createApp(params) {
|
|
|
462
475
|
await ctx.authService.removeProfile(parsed.data.profileId);
|
|
463
476
|
return buildAdminConfig(request);
|
|
464
477
|
});
|
|
478
|
+
app.post("/_gateway/admin/profiles/import", async (request, reply) => {
|
|
479
|
+
const parsed = profileImportSchema.safeParse(request.body);
|
|
480
|
+
if (!parsed.success) {
|
|
481
|
+
reply.code(400);
|
|
482
|
+
return {
|
|
483
|
+
error: {
|
|
484
|
+
type: "validation_error",
|
|
485
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
const importedProfiles = await ctx.authService.importProfiles(parsed.data.profile);
|
|
490
|
+
await ctx.authService.syncActiveProfileQuota("openai-codex", {
|
|
491
|
+
suppressErrors: true
|
|
492
|
+
});
|
|
493
|
+
return {
|
|
494
|
+
...await buildAdminConfig(request),
|
|
495
|
+
importedProfileCount: importedProfiles.length
|
|
496
|
+
};
|
|
497
|
+
});
|
|
498
|
+
app.get("/_gateway/admin/profiles/import-template", async () => ({
|
|
499
|
+
profile: ctx.authService.getProfileImportTemplate()
|
|
500
|
+
}));
|
|
501
|
+
app.post("/_gateway/admin/profiles/export", async (request, reply) => {
|
|
502
|
+
const parsed = profileExportSchema.safeParse(request.body ?? {});
|
|
503
|
+
if (!parsed.success) {
|
|
504
|
+
reply.code(400);
|
|
505
|
+
return {
|
|
506
|
+
error: {
|
|
507
|
+
type: "validation_error",
|
|
508
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
if (parsed.data.all || parsed.data.profileIds) {
|
|
513
|
+
return {
|
|
514
|
+
profile: await ctx.authService.exportProfiles(parsed.data.profileIds)
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
profile: await ctx.authService.exportProfile(parsed.data.profileId)
|
|
519
|
+
};
|
|
520
|
+
});
|
|
465
521
|
app.put("/_gateway/admin/settings", async (request, reply) => {
|
|
466
522
|
const parsed = settingsUpdateSchema.safeParse(request.body);
|
|
467
523
|
if (!parsed.success) {
|
|
@@ -473,7 +529,12 @@ function createApp(params) {
|
|
|
473
529
|
}
|
|
474
530
|
};
|
|
475
531
|
}
|
|
476
|
-
|
|
532
|
+
if (parsed.data.defaultModel) {
|
|
533
|
+
await ctx.configService.setDefaultModel(parsed.data.defaultModel);
|
|
534
|
+
}
|
|
535
|
+
if (parsed.data.networkProxy) {
|
|
536
|
+
await ctx.configService.setNetworkProxy(parsed.data.networkProxy);
|
|
537
|
+
}
|
|
477
538
|
return buildAdminConfig(request);
|
|
478
539
|
});
|
|
479
540
|
app.get("/v1/models", async () => ({
|
package/package.json
CHANGED