ai-zero-token 2.0.8 → 2.0.10
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 +14 -0
- package/admin-ui/dist/assets/accounts-zuNclJ6N.js +4 -0
- package/admin-ui/dist/assets/{docs-BQaF_ZMr.js → docs-CUK12iV2.js} +1 -1
- package/admin-ui/dist/assets/{image-bed-D4w1m7k6.js → image-bed-t3AMBCa_.js} +1 -1
- package/admin-ui/dist/assets/{index-BRQrU_AA.css → index-BgT1IdcO.css} +1 -1
- package/admin-ui/dist/assets/{index-_5Ny0cZf.js → index-D3zM8GaW.js} +3 -3
- package/admin-ui/dist/assets/{launch-BEDxgkQf.js → launch-CULKebZb.js} +1 -1
- package/admin-ui/dist/assets/{logs-BcL0n0Ld.js → logs-DRKlPkqN.js} +1 -1
- package/admin-ui/dist/assets/{network-detect-lEfklmIy.js → network-detect-CdR3HfIw.js} +1 -1
- package/admin-ui/dist/assets/overview-CcwiFi_D.js +1 -0
- package/admin-ui/dist/assets/{profiles-C5SmQvju.js → profiles-iNTmJFRe.js} +1 -1
- package/admin-ui/dist/assets/settings-DcbJl6NY.js +8 -0
- package/admin-ui/dist/assets/{tester-Ca4JOgAq.js → tester-D04mWM2O.js} +2 -2
- package/admin-ui/dist/assets/{usage-hMH0gMZ5.js → usage-BPuN1EW0.js} +1 -1
- package/admin-ui/dist/index.html +3 -3
- package/dist/core/providers/openai-codex/oauth.js +84 -24
- package/dist/core/services/auth-service.js +50 -7
- package/dist/server/app.js +84 -4
- package/docs/DESKTOP_RELEASE.md +18 -0
- package/package.json +1 -1
- package/admin-ui/dist/assets/accounts-p9bqmijS.js +0 -4
- package/admin-ui/dist/assets/overview-DsUMffIU.js +0 -1
- package/admin-ui/dist/assets/settings-a3HxExcC.js +0 -8
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
importProfilesFromJson
|
|
30
30
|
} from "../store/profile-transfer.js";
|
|
31
31
|
const DEFAULT_QUOTA_SYNC_CONCURRENCY = 3;
|
|
32
|
+
const AUTH_CLAIM_PATH = "https://api.openai.com/auth";
|
|
32
33
|
function getQuotaSyncConcurrency(configured) {
|
|
33
34
|
const raw = process.env.AZT_QUOTA_SYNC_CONCURRENCY;
|
|
34
35
|
const parsed = raw ? Number.parseInt(raw, 10) : configured ?? DEFAULT_QUOTA_SYNC_CONCURRENCY;
|
|
@@ -81,6 +82,47 @@ class AuthService {
|
|
|
81
82
|
exportAudit: profile.exportAudit
|
|
82
83
|
};
|
|
83
84
|
}
|
|
85
|
+
decodeJwtPayload(token) {
|
|
86
|
+
try {
|
|
87
|
+
const parts = token.split(".");
|
|
88
|
+
if (parts.length !== 3) {
|
|
89
|
+
return void 0;
|
|
90
|
+
}
|
|
91
|
+
const payload = parts[1] ?? "";
|
|
92
|
+
const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
93
|
+
const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
|
|
94
|
+
const parsed = JSON.parse(Buffer.from(normalized + padding, "base64").toString("utf8"));
|
|
95
|
+
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
96
|
+
} catch {
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
getJwtExpiry(payload) {
|
|
101
|
+
return typeof payload.exp === "number" && Number.isFinite(payload.exp) ? payload.exp * 1e3 : void 0;
|
|
102
|
+
}
|
|
103
|
+
getJwtAccountId(payload) {
|
|
104
|
+
const authClaim = payload[AUTH_CLAIM_PATH];
|
|
105
|
+
if (!authClaim || typeof authClaim !== "object") {
|
|
106
|
+
return void 0;
|
|
107
|
+
}
|
|
108
|
+
const accountId = authClaim.chatgpt_account_id;
|
|
109
|
+
return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
|
|
110
|
+
}
|
|
111
|
+
hasValidIdToken(profile) {
|
|
112
|
+
if (!profile.idToken) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const payload = this.decodeJwtPayload(profile.idToken);
|
|
116
|
+
if (!payload) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
const expiresAt = this.getJwtExpiry(payload);
|
|
120
|
+
if (typeof expiresAt !== "number" || Date.now() >= expiresAt) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
const tokenAccountId = this.getJwtAccountId(payload);
|
|
124
|
+
return !tokenAccountId || tokenAccountId === profile.accountId;
|
|
125
|
+
}
|
|
84
126
|
buildExportAudit(current, kind, exportedAt) {
|
|
85
127
|
return {
|
|
86
128
|
exported: true,
|
|
@@ -344,11 +386,15 @@ class AuthService {
|
|
|
344
386
|
});
|
|
345
387
|
return this.toManagedProfile(activated);
|
|
346
388
|
}
|
|
347
|
-
async login(provider) {
|
|
389
|
+
async login(provider, options) {
|
|
348
390
|
if (provider !== "openai-codex") {
|
|
349
391
|
throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
|
|
350
392
|
}
|
|
351
|
-
const profile = await loginOpenAICodex();
|
|
393
|
+
const profile = await loginOpenAICodex(options);
|
|
394
|
+
await saveProfile(profile);
|
|
395
|
+
return this.toManagedProfile(profile);
|
|
396
|
+
}
|
|
397
|
+
async saveLoggedInProfile(profile) {
|
|
352
398
|
await saveProfile(profile);
|
|
353
399
|
return this.toManagedProfile(profile);
|
|
354
400
|
}
|
|
@@ -551,12 +597,9 @@ class AuthService {
|
|
|
551
597
|
if (!profile) {
|
|
552
598
|
throw new Error(`\u6CA1\u6709\u627E\u5230\u8D26\u53F7: ${profileId}`);
|
|
553
599
|
}
|
|
554
|
-
if (profile.idToken && Date.now() < profile.expires) {
|
|
555
|
-
return this.toManagedProfile(profile);
|
|
556
|
-
}
|
|
557
600
|
const refreshed = await this.refreshStoredProfile(profile, provider);
|
|
558
|
-
if (!refreshed
|
|
559
|
-
throw new Error("\u5237\u65B0 token \u6210\u529F\uFF0C\u4F46\u4E0A\u6E38\u6CA1\u6709\u8FD4\u56DE id_token\u3002");
|
|
601
|
+
if (!this.hasValidIdToken(refreshed)) {
|
|
602
|
+
throw new Error("\u5237\u65B0 token \u6210\u529F\uFF0C\u4F46\u4E0A\u6E38\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684 id_token\u3002\u8BF7\u91CD\u65B0\u767B\u5F55\u6216\u91CD\u65B0\u5BFC\u5165\u5305\u542B\u6709\u6548 id_token \u7684\u8D26\u53F7 JSON\u3002");
|
|
560
603
|
}
|
|
561
604
|
return this.toManagedProfile(refreshed);
|
|
562
605
|
}
|
package/dist/server/app.js
CHANGED
|
@@ -19,6 +19,9 @@ import { createGatewayContext } from "../core/context.js";
|
|
|
19
19
|
import { isTransientHttpError, requestText } from "../core/providers/http-client.js";
|
|
20
20
|
import { streamOpenAICodex } from "../core/providers/openai-codex/chat.js";
|
|
21
21
|
import { generateChatGPTWebImage } from "../core/providers/openai-codex/chatgpt-web-image.js";
|
|
22
|
+
import {
|
|
23
|
+
startOpenAICodexLogin
|
|
24
|
+
} from "../core/providers/openai-codex/oauth.js";
|
|
22
25
|
const packageRoot = path.dirname(fileURLToPath(new URL("../../package.json", import.meta.url)));
|
|
23
26
|
const adminUiDistDir = path.join(packageRoot, "admin-ui", "dist");
|
|
24
27
|
const adminUiIndexPath = path.join(adminUiDistDir, "index.html");
|
|
@@ -187,6 +190,13 @@ const profileImportSchema = z.object({
|
|
|
187
190
|
const runtimeRefreshSchema = z.object({
|
|
188
191
|
staleOnly: z.boolean().optional()
|
|
189
192
|
});
|
|
193
|
+
const oauthManualSchema = z.object({
|
|
194
|
+
loginId: z.string().min(1),
|
|
195
|
+
input: z.string().min(1)
|
|
196
|
+
});
|
|
197
|
+
const oauthCancelSchema = z.object({
|
|
198
|
+
loginId: z.string().min(1)
|
|
199
|
+
});
|
|
190
200
|
const profileExportSchema = z.object({
|
|
191
201
|
profileId: z.string().min(1).optional(),
|
|
192
202
|
profileIds: z.array(z.string().min(1)).optional(),
|
|
@@ -1397,6 +1407,20 @@ function createApp(params) {
|
|
|
1397
1407
|
const ctx = createGatewayContext();
|
|
1398
1408
|
const gatewayRequestLogs = [];
|
|
1399
1409
|
const codexResponseProfileBindings = /* @__PURE__ */ new Map();
|
|
1410
|
+
let pendingOAuthLogin = null;
|
|
1411
|
+
function clearPendingOAuthLogin(loginId) {
|
|
1412
|
+
if (!pendingOAuthLogin || loginId && pendingOAuthLogin.id !== loginId) {
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
pendingOAuthLogin.session.close();
|
|
1416
|
+
pendingOAuthLogin = null;
|
|
1417
|
+
}
|
|
1418
|
+
async function saveCompletedOAuthLogin(profile) {
|
|
1419
|
+
await ctx.authService.saveLoggedInProfile(profile);
|
|
1420
|
+
await ctx.authService.syncActiveProfileQuota("openai-codex", {
|
|
1421
|
+
suppressErrors: true
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1400
1424
|
function rememberCodexResponseProfile(responseId, profile) {
|
|
1401
1425
|
codexResponseProfileBindings.set(responseId, {
|
|
1402
1426
|
profileId: profile.profileId,
|
|
@@ -1645,10 +1669,66 @@ function createApp(params) {
|
|
|
1645
1669
|
};
|
|
1646
1670
|
});
|
|
1647
1671
|
app.post("/_gateway/admin/login", async (request) => {
|
|
1648
|
-
|
|
1649
|
-
await
|
|
1650
|
-
|
|
1651
|
-
|
|
1672
|
+
clearPendingOAuthLogin();
|
|
1673
|
+
const session = await startOpenAICodexLogin();
|
|
1674
|
+
const loginId = randomUUID();
|
|
1675
|
+
pendingOAuthLogin = {
|
|
1676
|
+
id: loginId,
|
|
1677
|
+
session,
|
|
1678
|
+
createdAt: Date.now()
|
|
1679
|
+
};
|
|
1680
|
+
const code = await session.waitForCode(6e4);
|
|
1681
|
+
if (!code) {
|
|
1682
|
+
return {
|
|
1683
|
+
login: {
|
|
1684
|
+
status: "manual_required",
|
|
1685
|
+
loginId,
|
|
1686
|
+
message: "60 \u79D2\u5185\u6CA1\u6709\u6536\u5230 OAuth \u81EA\u52A8\u56DE\u8C03\u3002\u53EF\u4EE5\u7C98\u8D34\u6D4F\u89C8\u5668\u5730\u5740\u680F\u91CC\u7684\u5B8C\u6574\u56DE\u8C03 URL \u6216 authorization code \u7EE7\u7EED\u767B\u5F55\u3002"
|
|
1687
|
+
},
|
|
1688
|
+
config: await buildAdminConfig(request)
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
try {
|
|
1692
|
+
const profile = await session.completeWithCode(code);
|
|
1693
|
+
await saveCompletedOAuthLogin(profile);
|
|
1694
|
+
return buildAdminConfig(request);
|
|
1695
|
+
} finally {
|
|
1696
|
+
clearPendingOAuthLogin(loginId);
|
|
1697
|
+
}
|
|
1698
|
+
});
|
|
1699
|
+
app.post("/_gateway/admin/login/manual", async (request, reply) => {
|
|
1700
|
+
const parsed = oauthManualSchema.safeParse(request.body);
|
|
1701
|
+
if (!parsed.success) {
|
|
1702
|
+
reply.code(400);
|
|
1703
|
+
return {
|
|
1704
|
+
error: {
|
|
1705
|
+
type: "validation_error",
|
|
1706
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
if (!pendingOAuthLogin || pendingOAuthLogin.id !== parsed.data.loginId) {
|
|
1711
|
+
reply.code(404);
|
|
1712
|
+
return {
|
|
1713
|
+
error: {
|
|
1714
|
+
type: "oauth_login_not_found",
|
|
1715
|
+
message: "\u6CA1\u6709\u627E\u5230\u7B49\u5F85\u4E2D\u7684 OAuth \u767B\u5F55\uFF0C\u8BF7\u91CD\u65B0\u70B9\u51FB\u767B\u5F55\u3002"
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
try {
|
|
1720
|
+
const profile = await pendingOAuthLogin.session.completeWithInput(parsed.data.input);
|
|
1721
|
+
await saveCompletedOAuthLogin(profile);
|
|
1722
|
+
return buildAdminConfig(request);
|
|
1723
|
+
} finally {
|
|
1724
|
+
clearPendingOAuthLogin(parsed.data.loginId);
|
|
1725
|
+
}
|
|
1726
|
+
});
|
|
1727
|
+
app.post("/_gateway/admin/login/cancel", async (request) => {
|
|
1728
|
+
const parsed = oauthCancelSchema.safeParse(request.body);
|
|
1729
|
+
if (parsed.success) {
|
|
1730
|
+
clearPendingOAuthLogin(parsed.data.loginId);
|
|
1731
|
+
}
|
|
1652
1732
|
return buildAdminConfig(request);
|
|
1653
1733
|
});
|
|
1654
1734
|
app.post("/_gateway/admin/logout", async (request) => {
|
package/docs/DESKTOP_RELEASE.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
This project ships the desktop app with Electron. The desktop main process starts the existing local Fastify gateway and loads the React management UI served by that gateway.
|
|
4
4
|
|
|
5
|
+
## 2.0.10 Release Notes
|
|
6
|
+
|
|
7
|
+
Version `2.0.10` improves OAuth login recovery and Codex auth freshness:
|
|
8
|
+
|
|
9
|
+
- Desktop OAuth login can continue with a pasted callback URL or authorization code when automatic callback capture times out.
|
|
10
|
+
- The local OAuth callback listener binds both IPv4 and IPv6 loopback addresses for browser redirect compatibility.
|
|
11
|
+
- Applying an account to local Codex refreshes the profile first so the written `auth.json` includes a current `id_token`.
|
|
12
|
+
- Saved `id_token` values are checked for expiry and account identity before use.
|
|
13
|
+
|
|
14
|
+
## 2.0.9 Release Notes
|
|
15
|
+
|
|
16
|
+
Version `2.0.9` clarifies automatic account rotation eligibility and tightens Codex auth refresh handling:
|
|
17
|
+
|
|
18
|
+
- Settings now separates manually excluded accounts from accounts that are runtime-ineligible because login is unavailable or quota is exhausted.
|
|
19
|
+
- Account filters and stats distinguish configured rotation participation from the actual automatic rotation candidate pool.
|
|
20
|
+
- Refreshed Codex tokens preserve account identity metadata when upstream token payloads omit profile claims.
|
|
21
|
+
- Saved profiles validate `id_token` expiry before Codex image and web flows use them, with clearer recovery messaging when a fresh `id_token` is unavailable.
|
|
22
|
+
|
|
5
23
|
## 2.0.6 Release Notes
|
|
6
24
|
|
|
7
25
|
Version `2.0.6` adds Free-account image routing controls and local usage/account statistics:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-zero-token",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.10",
|
|
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",
|
|
@@ -1,4 +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{C as o,_ as s,a as c,b as l,d as u,f as d,g as f,h as p,i as m,m as h,n as g,o as _,p as v,r as y,s as b,t as x,u as S,v as C,w,y as T}from"./profiles-C5SmQvju.js";import{_ as E,d as D,p as O,r as k,x as A}from"./index-_5Ny0cZf.js";import{t as j}from"./InfoRow-0ULI9iI3.js";var M=e(t(),1),N=n();function P(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,N.jsxs)(`section`,{className:`card`,id:`accounts`,children:[(0,N.jsxs)(`div`,{className:`section-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`h2`,{children:`账号额度预览`}),(0,N.jsx)(`p`,{children:`账号信息采用卡片式布局展示,支持搜索、状态筛选和额度排序。`})]}),(0,N.jsxs)(`div`,{className:`section-actions`,children:[(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onLocate,children:`定位当前账号`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onExportSelected,children:`导出所选`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onSelectVisible,disabled:e.visibleCount===0,children:`全选筛选结果`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onClearSelected,disabled:e.selectedCount===0,children:`取消选择`}),(0,N.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:e.onRemoveSelected,disabled:e.selectedCount===0||e.busy===`bulk-remove`,children:`删除所选`}),(0,N.jsx)(`button`,{className:`btn-primary`,type:`button`,onClick:e.onAddAccount,children:`新增账号`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onRefreshStatus,children:`刷新状态`}),(0,N.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:e.onClearAccounts,children:`清空账号`})]})]}),(0,N.jsx)(`div`,{className:`account-stat-strip`,"aria-label":`账号池统计`,children:e.accountStats.map(t=>(0,N.jsxs)(`button`,{className:`account-stat-pill tone-${t.tone} ${e.filter.status===t.key?`is-active`:``}`,type:`button`,onClick:()=>e.onFilter({...e.filter,status:t.key}),children:[(0,N.jsx)(`span`,{children:t.label}),(0,N.jsx)(`strong`,{children:t.value})]},t.key))}),(0,N.jsxs)(`div`,{className:`filter-row`,children:[(0,N.jsxs)(`label`,{className:`search-box`,children:[(0,N.jsx)(a,{size:16}),(0,N.jsx)(`input`,{value:e.filter.search,onChange:t=>e.onFilter({...e.filter,search:t.target.value}),placeholder:`搜索邮箱、账号 ID 或 Profile ID`})]}),(0,N.jsxs)(`select`,{className:`control`,value:e.filter.status,onChange:t=>e.onFilter({...e.filter,status:t.target.value}),children:[(0,N.jsx)(`option`,{value:`all`,children:`全部状态`}),(0,N.jsx)(`option`,{value:`available`,children:`可用`}),(0,N.jsx)(`option`,{value:`unavailable`,children:`不可用`}),(0,N.jsx)(`option`,{value:`active`,children:`使用中`}),(0,N.jsx)(`option`,{value:`api-active`,children:`API 使用中`}),(0,N.jsx)(`option`,{value:`codex-active`,children:`Codex 使用中`}),(0,N.jsx)(`option`,{value:`healthy`,children:`健康`}),(0,N.jsx)(`option`,{value:`warning`,children:`即将耗尽`}),(0,N.jsx)(`option`,{value:`unknown`,children:`待请求验证`}),(0,N.jsx)(`option`,{value:`exhausted`,children:`额度耗尽`}),(0,N.jsx)(`option`,{value:`invalid`,children:`登录/认证异常`}),(0,N.jsx)(`option`,{value:`login-invalid`,children:`登录失效`}),(0,N.jsx)(`option`,{value:`auth-error`,children:`认证异常`}),(0,N.jsx)(`option`,{value:`expired`,children:`已过期`}),(0,N.jsx)(`option`,{value:`free`,children:`Free`}),(0,N.jsx)(`option`,{value:`plus`,children:`Plus`}),(0,N.jsx)(`option`,{value:`pro-team`,children:`Pro/Team`}),(0,N.jsx)(`option`,{value:`auto-included`,children:`参与轮换`}),(0,N.jsx)(`option`,{value:`auto-excluded`,children:`排除轮换`})]}),(0,N.jsxs)(`select`,{className:`control`,value:e.filter.sort,onChange:t=>e.onFilter({...e.filter,sort:t.target.value}),children:[(0,N.jsx)(`option`,{value:`quota-desc`,children:`默认排序`}),(0,N.jsx)(`option`,{value:`latency-asc`,children:`按额度更新时间`}),(0,N.jsx)(`option`,{value:`expiry-asc`,children:`按过期时间`}),(0,N.jsx)(`option`,{value:`name-asc`,children:`按名称排序`}),(0,N.jsx)(`option`,{value:`quota-asc`,children:`按剩余额度升序`}),(0,N.jsx)(`option`,{value:`plan-desc`,children:`按套餐排序`}),(0,N.jsx)(`option`,{value:`email-asc`,children:`按邮箱排序`})]}),(0,N.jsxs)(`span`,{className:`account-selected-count`,children:[`已选择 `,e.selectedCount,` 个`]})]}),(0,N.jsx)(`div`,{className:`account-grid ${n}`,children:e.profiles.length===0?(0,N.jsx)(`div`,{className:`empty-state`,children:`还没有匹配的账号。可以新增账号或调整筛选条件。`}):e.profiles.map(n=>{let a=d(n),o=u(n),p=T(n),y=!!e.expandedProfiles[n.profileId],b=!!(t&&n.accountId===t),S=l(n,b),w=_(n),D=c(n),O=n.exportAudit,k=O?.exported?`已导出 ${O.count} 次`:`未导出`,M=typeof e.busy==`string`&&e.busy.startsWith(`profile:`)&&e.busy.endsWith(n.profileId),P=e.busy===`profile:sync-quota:${n.profileId}`;return(0,N.jsxs)(`article`,{className:`account-card plan-${g(n)} ${w?`is-auth-invalid`:``}`,"data-profile-card":n.profileId,title:w?x(n):void 0,children:[S&&(0,N.jsx)(`span`,{className:`usage-corner ${S.className}`,children:(0,N.jsx)(`span`,{children:S.label})}),(0,N.jsxs)(`div`,{className:`account-head`,children:[(0,N.jsxs)(`div`,{className:`account-title`,children:[(0,N.jsxs)(`div`,{className:`account-name`,children:[(0,N.jsx)(`span`,{className:`avatar`,children:v(n)}),(0,N.jsx)(`strong`,{children:h(n,e.showEmails)}),(0,N.jsx)(`button`,{"aria-label":`刷新额度`,className:`account-icon-btn`,disabled:M,onClick:()=>e.onAction(`sync-quota`,n),title:`刷新额度`,type:`button`,children:P?(0,N.jsx)(E,{className:`spin`,size:14}):(0,N.jsx)(i,{size:14})})]}),(0,N.jsxs)(`div`,{className:`badge-row`,children:[(0,N.jsx)(`span`,{className:`badge brand`,children:m(n)}),(0,N.jsx)(`span`,{className:`badge ${a.tone}`,children:a.label}),(0,N.jsx)(`span`,{className:`badge ${D.ok?`green`:`orange`}`,children:`gpt-image-2`}),(0,N.jsx)(`span`,{className:`badge ${O?.exported?`orange`:`muted`}`,children:k})]})]}),(0,N.jsxs)(`label`,{className:`account-select`,children:[(0,N.jsx)(`input`,{type:`checkbox`,checked:!!e.selectedProfiles[n.profileId],onChange:t=>e.onSelect(n.profileId,t.target.checked)}),(0,N.jsx)(`span`,{children:`选择`})]})]}),(0,N.jsxs)(`div`,{className:`account-metrics`,children:[(0,N.jsx)(L,{label:s(n,`primary`),value:o,tone:f(o)}),(0,N.jsx)(L,{label:s(n,`secondary`),value:p,tone:f(p)})]}),(0,N.jsxs)(`div`,{className:`usage-status-row`,children:[(0,N.jsxs)(`span`,{className:`usage-status ${n.isActive?`is-active`:``}`,children:[(0,N.jsx)(r,{size:14}),(0,N.jsx)(`span`,{children:`API`}),(0,N.jsx)(`span`,{className:`usage-dot ${n.isActive?`active`:``}`}),(0,N.jsx)(`span`,{className:`usage-state-text`,children:n.isActive?`使用中`:`未使用`})]}),(0,N.jsxs)(`span`,{className:`usage-status ${b?`is-active`:``}`,children:[(0,N.jsx)(A,{size:14}),(0,N.jsx)(`span`,{children:`Codex`}),(0,N.jsx)(`span`,{className:`usage-dot ${b?`active`:``}`}),(0,N.jsx)(`span`,{className:`usage-state-text`,children:b?`使用中`:`未使用`})]})]}),(0,N.jsxs)(`div`,{className:`compact-meta-row`,children:[(0,N.jsxs)(`div`,{className:`compact-reset-list`,children:[(0,N.jsxs)(`div`,{className:`compact-meta-item`,children:[(0,N.jsx)(`label`,{children:s(n,`primary`)}),(0,N.jsx)(`strong`,{children:C(n,`primary`)})]}),(0,N.jsxs)(`div`,{className:`compact-meta-item`,children:[(0,N.jsx)(`label`,{children:s(n,`secondary`)}),(0,N.jsx)(`strong`,{children:C(n,`secondary`)})]})]}),(0,N.jsx)(`div`,{className:`compact-meta-actions`,children:(0,N.jsxs)(`button`,{className:`details-toggle ${y?`is-expanded`:``}`,type:`button`,onClick:()=>e.onToggle(n.profileId),children:[(0,N.jsx)(`span`,{children:y?`收起详情`:`查看详情`}),(0,N.jsx)(I,{})]})})]}),y&&(0,N.jsxs)(`div`,{className:`meta-grid`,children:[(0,N.jsx)(j,{label:`套餐`,value:m(n)}),(0,N.jsx)(j,{label:`Account ID`,value:(e.showEmails,n.accountId),code:!0}),(0,N.jsx)(j,{label:`Profile ID`,value:(e.showEmails,n.profileId),code:!0}),(0,N.jsx)(j,{label:`认证状态`,value:x(n)}),(0,N.jsx)(j,{label:`生图能力`,value:D.ok?`gpt-image-2 可用`:D.detail}),(0,N.jsx)(j,{label:`导出记录`,value:F(O)}),(0,N.jsx)(j,{label:`过期时间`,value:n.expiresAt?new Date(n.expiresAt).toLocaleString(`zh-CN`):`-`}),(0,N.jsx)(j,{label:`额度快照`,value:n.quota?.capturedAt?new Date(n.quota.capturedAt).toLocaleString(`zh-CN`):`-`})]}),(0,N.jsxs)(`div`,{className:`account-actions`,children:[(0,N.jsx)(`button`,{className:`btn-secondary ${n.isActive?`is-current`:``}`,type:`button`,onClick:()=>e.onAction(`activate`,n),disabled:n.isActive||M||w,children:w?`网关不可用`:n.isActive?`网关使用中`:`应用网关`}),(0,N.jsx)(`button`,{className:`btn-secondary ${b?`is-current codex`:``}`,type:`button`,onClick:()=>e.onAction(`apply-codex`,n),disabled:b||M||w,children:w?`Codex 不可用`:b?`Codex 使用中`:`应用 Codex`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>e.onAction(`export`,n),disabled:M,children:`导出`}),(0,N.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:()=>e.onAction(`remove`,n),disabled:M,children:`删除`})]})]},n.profileId)})})]})}function F(e){if(!e?.exported)return`未导出`;let t=e.lastExportKind===`single`?`单账号导出`:e.lastExportKind===`batch`?`批量导出`:`全部导出`;return`${e.count} 次,最近 ${o(e.lastExportedAt)},方式 ${t}`}function I(){return(0,N.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,"aria-hidden":`true`,children:(0,N.jsx)(`path`,{d:`m6 9 6 6 6-6`})})}function L(e){return(0,N.jsxs)(`div`,{className:`quota-row`,children:[(0,N.jsxs)(`div`,{className:`quota-line`,children:[(0,N.jsxs)(`span`,{children:[e.label,` · 已用 `,e.value,`% / 剩余 `,100-e.value,`%`]}),(0,N.jsxs)(`strong`,{children:[`剩余 `,100-e.value,`%`]})]}),(0,N.jsx)(`div`,{className:`progress-track`,children:(0,N.jsx)(`div`,{className:`progress-bar ${e.tone}`,style:{width:`${e.value}%`}})})]})}function R(e){let[t,n]=(0,M.useState)({}),[r,i]=(0,M.useState)({}),[a,o]=(0,M.useState)({search:``,status:`all`,sort:`quota-desc`}),s=(0,M.useMemo)(()=>{let t=e.config?.profiles?[...e.config.profiles]:[],n=new Set(e.config?.settings.autoSwitch.excludedProfileIds||[]),r=a.search.trim().toLowerCase(),i=t.filter(t=>{let i=[h(t,!0).toLowerCase(),t.accountId,t.profileId,t.email||``].join(` `).toLowerCase(),o=d(t),s=!!(e.codexAccountId&&t.accountId===e.codexAccountId),c=g(t);return r&&!i.includes(r)?!1:a.status===`active`?t.isActive||s:a.status===`healthy`?o.key===`healthy`:a.status===`warning`?o.key===`warning`:a.status===`unknown`?o.key===`unknown`:a.status===`exhausted`?o.key===`exhausted`:a.status===`expired`?o.key===`expired`:a.status===`invalid`?o.key===`invalid`:a.status===`login-invalid`?t.authStatus?.state===`token_invalidated`:a.status===`auth-error`?t.authStatus?.state===`auth_error`:a.status===`available`?o.key===`healthy`||o.key===`warning`||o.key===`unknown`:a.status===`unavailable`?o.key===`invalid`||o.key===`expired`||o.key===`exhausted`:a.status===`free`?c===`free`:a.status===`plus`?c===`plus`:a.status===`pro-team`?c===`pro`||c===`team`||c===`enterprise`||c===`premium`:a.status===`api-active`?t.isActive:a.status===`codex-active`?s:a.status===`auto-included`?!n.has(t.profileId):a.status===`auto-excluded`?n.has(t.profileId):!0});return i.sort((t,n)=>{let r=p(t,e.codexAccountId)-p(n,e.codexAccountId);if(r!==0)return r;let i=y(n)-y(t);if(i!==0)return i;let o=S(n)-S(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`?h(t,!0).localeCompare(h(n,!0),`zh-CN`):a.sort===`quota-asc`?100-u(n)-(100-u(t)):a.sort===`plan-desc`?y(n)-y(t):a.sort===`email-asc`?h(t,!0).localeCompare(h(n,!0)):u(n)-u(t):o}),i},[a,e.codexAccountId,e.config?.profiles,e.config?.settings.autoSwitch.excludedProfileIds]),c=(0,M.useMemo)(()=>{let t=e.config?.profiles||[],n=new Set(e.config?.settings.autoSwitch.excludedProfileIds||[]),r=e=>t.filter(e).length,i=r(t=>!!(e.codexAccountId&&t.accountId===e.codexAccountId));return[{key:`all`,label:`总账号`,value:t.length,tone:`blue`},{key:`available`,label:`可用`,value:r(e=>[`healthy`,`warning`,`unknown`].includes(d(e).key)),tone:`green`},{key:`unavailable`,label:`不可用`,value:r(e=>[`invalid`,`expired`,`exhausted`].includes(d(e).key)),tone:`red`},{key:`unknown`,label:`待请求验证`,value:r(e=>d(e).key===`unknown`),tone:`blue`},{key:`login-invalid`,label:`登录失效`,value:r(e=>e.authStatus?.state===`token_invalidated`),tone:`red`},{key:`auth-error`,label:`认证异常`,value:r(e=>e.authStatus?.state===`auth_error`),tone:`red`},{key:`exhausted`,label:`额度耗尽`,value:r(e=>d(e).key===`exhausted`),tone:`orange`},{key:`free`,label:`Free`,value:r(e=>g(e)===`free`),tone:`muted`},{key:`plus`,label:`Plus`,value:r(e=>g(e)===`plus`),tone:`brand`},{key:`pro-team`,label:`Pro/Team`,value:r(e=>[`pro`,`team`,`enterprise`,`premium`].includes(g(e))),tone:`blue`},{key:`api-active`,label:`API 使用中`,value:r(e=>e.isActive),tone:`green`},{key:`codex-active`,label:`Codex 使用中`,value:i,tone:`green`},{key:`auto-included`,label:`参与轮换`,value:r(e=>!n.has(e.profileId)),tone:`blue`},{key:`auto-excluded`,label:`排除轮换`,value:r(e=>n.has(e.profileId)),tone:`orange`}]},[e.codexAccountId,e.config?.profiles,e.config?.settings.autoSwitch.excludedProfileIds]),l=Object.values(t).filter(Boolean).length,f=Object.keys(t).filter(e=>t[e]),m=(0,M.useMemo)(()=>s.map(e=>e.profileId),[s]);async function v(t,n){let r=await O(`/_gateway/admin/profiles/export`,{method:`POST`,headers:{"Content-Type":`application/json`},body:w(n?{profileIds:n}:{profileId:t})});D(`ai-zero-token-${n?`profiles-${n.length}`:t||`active`}.json`,r.profile),r.config?e.setConfig(r.config):await e.refreshConfig({silent:!0}),e.setStatus(n?`已导出 ${n.length} 个账号。`:`账号配置已导出。`)}async function x(t,n){if(!(t===`remove`&&!window.confirm(`确认删除 ${h(n,e.showEmails)}?`))){if((t===`activate`||t===`apply-codex`)&&_(n)){e.setStatus(`${h(n,e.showEmails)} 登录已失效,不能应用到${t===`activate`?`网关`:`Codex`}。`);return}if((t===`activate`||t===`apply-codex`)&&b(n)){let r=t===`activate`?`网关`:`Codex`;if(!window.confirm(`${h(n,e.showEmails)} 的额度看起来已耗尽,仍要应用到${r}吗?`))return}if(t===`export`){await v(n.profileId);return}e.setBusy(`profile:${t}:${n.profileId}`);try{let r=await O({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:w({profileId:n.profileId})}),i=`config`in r?r.config:r;if(e.setConfig(i),e.setStatus(t===`activate`?`已应用到网关。`:t===`apply-codex`?`已应用到本机 Codex。`:t===`sync-quota`?`额度信息已同步。`:`账号已删除。`),t===`apply-codex`)if(i.codexRestartSupported&&window.confirm(`Codex 账号已切换,是否现在重启 Codex 客户端?
|
|
2
|
-
|
|
3
|
-
Codex 通常在启动时读取本机 auth.json,重启后新账号会立即生效。`))try{await O(`/_gateway/admin/desktop/restart-codex`,{method:`POST`}),e.setStatus(`已应用到本机 Codex,并已重启 Codex 客户端。`)}catch(t){e.setStatus(`已应用到本机 Codex,但重启 Codex 失败: ${k(t)}`)}else e.setStatus(`已应用到本机 Codex,重启 Codex 客户端后生效。`)}catch(t){e.setStatus(k(t))}finally{e.setBusy(null)}}}async function C(){let t=f;if(t.length===0){e.setStatus(`请先勾选要删除的账号。`);return}let r=e.config?.profiles.filter(e=>t.includes(e.profileId)).slice(0,3).map(t=>h(t,e.showEmails)),i=r?.length?`\n\n${r.join(`
|
|
4
|
-
`)}${t.length>r.length?`\n等 ${t.length} 个账号`:``}`:``;if(window.confirm(`确认删除所选 ${t.length} 个账号?此操作不可撤销。${i}`)){e.setBusy(`bulk-remove`),e.setStatus(`正在删除 ${t.length} 个账号...`);try{let r=await O(`/_gateway/admin/profiles/remove-batch`,{method:`POST`,headers:{"Content-Type":`application/json`},body:w({profileIds:t})});e.setConfig(r),n({}),e.setStatus(`已删除 ${r.removedProfileCount??t.length} 个账号。`)}catch(t){e.setStatus(`删除所选失败: ${k(t)}`)}finally{e.setBusy(null)}}}function T(t,r){if(t.length===0){e.setStatus(`没有可选择的账号。`);return}n(e=>{let n={...e};for(let e of t)n[e]=!0;return n}),e.setStatus(r)}return(0,N.jsx)(P,{config:e.config,profiles:s,accountStats:c,showEmails:e.showEmails,filter:a,selectedProfiles:t,expandedProfiles:r,selectedCount:l,visibleCount:m.length,busy:e.busy,onFilter:o,onSelect:(e,t)=>n(n=>({...n,[e]:t})),onSelectVisible:()=>T(m,`已选择当前筛选结果 ${m.length} 个账号。`),onClearSelected:()=>{n({}),e.setStatus(`已取消选择。`)},onToggle:e=>i(t=>({...t,[e]:!t[e]})),onAction:x,onLocate:()=>e.activeProfile&&document.querySelector(`[data-profile-card="${e.activeProfile.profileId}"]`)?.scrollIntoView({behavior:`smooth`,block:`center`}),onExportSelected:()=>{let t=f;if(t.length===0){e.setStatus(`请先勾选要导出的账号。`);return}v(void 0,t).catch(t=>e.setStatus(t instanceof Error?t.message:String(t)))},onRemoveSelected:()=>void C(),onAddAccount:()=>e.setAccountModalOpen(!0),onRefreshStatus:()=>e.refreshConfig({runtime:!0}),onClearAccounts:()=>e.logout()})}export{R as AccountsPage};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as e,r as t,t as n}from"./jsx-runtime-DqpGtLhh.js";import{t as r}from"./circle-check-ZYtn9GqY.js";import{t as i}from"./clock-3-BzDANsVk.js";import{t as a}from"./earth-DFdZaQIi.js";import{t as o}from"./zap-B4_oDbCp.js";import{c as s,l as c,m as l,x as u}from"./profiles-C5SmQvju.js";import{g as d,h as f}from"./index-_5Ny0cZf.js";import{t as p}from"./InfoRow-0ULI9iI3.js";import{t as m}from"./StatCard-7TEzqn2i.js";var h=e(t(),1),g=n();function _(e){let t=l(e.apiProfile,e.showEmails),n=e.codexProfile?l(e.codexProfile,e.showEmails):e.codexEmail?e.showEmails?e.codexEmail:s(e.codexEmail):e.codexAccountId?e.showEmails?e.codexAccountId:c(e.codexAccountId):`未应用`;return(0,g.jsxs)(`div`,{className:`usage-summary`,children:[(0,g.jsxs)(`div`,{className:`usage-summary-row`,children:[(0,g.jsx)(`span`,{children:`网关:`}),(0,g.jsx)(`strong`,{children:t})]}),(0,g.jsxs)(`div`,{className:`usage-summary-row`,children:[(0,g.jsx)(`span`,{children:`Codex:`}),(0,g.jsx)(`strong`,{children:n})]})]})}function v(e){let t=e?.quota?.primaryUsedPercent;return typeof t!=`number`||!Number.isFinite(t)?0:Math.max(0,Math.min(100,Math.round(t)))}function y(e,t,n,r){let i=Array.isArray(e?.profiles)?e.profiles:[],a=620+(e?.profile?v(e.profile):42)*7+n*90,o=i.reduce((e,t,n)=>e+v(t)*(n+1),0),s=Math.max(1,r/60);return Array.from({length:12},(e,r)=>{let i=Math.sin((r+1+n)*(.65+s*.08))*(120+s*18),c=Math.cos((r+1)*(1.05+s*.06)+n)*(72+s*10),l=t[r]?t[r].durationMs*(n===0?.24:.12):0,u=a+i+c+l+o%280;return Math.max(220,Math.min(2200,Math.round(u)))})}function b(e,t,n,r){return e.map((i,a)=>{let o=t/(e.length-1)*a,s=n-i/r*(n-16)-8;return`${a===0?`M`:`L`}${o.toFixed(2)} ${s.toFixed(2)}`}).join(` `)}function x(e){let t=y(e.config,e.requestLogs,0,e.windowMinutes),n=y(e.config,e.requestLogs,1,e.windowMinutes).map(e=>Math.max(180,e-260)),r=Math.max(...t,...n,2e3),i=b(t,720,210,r),a=b(n,720,210,r),o=Date.now(),s=Math.max(10,Math.round(e.windowMinutes/6)),c=Array.from({length:6},(e,t)=>new Date(o-(5-t)*s*60*1e3).toLocaleTimeString(`zh-CN`,{hour12:!1,hour:`2-digit`,minute:`2-digit`}));return(0,g.jsxs)(`section`,{className:`trend-card`,"aria-label":`请求耗时趋势`,children:[(0,g.jsxs)(`div`,{className:`section-head compact`,children:[(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`h3`,{children:`请求耗时趋势`}),(0,g.jsx)(`p`,{children:`基于最近调试请求和账号额度状态生成的本地趋势视图。`})]}),(0,g.jsxs)(`select`,{className:`control`,value:e.windowMinutes,onChange:t=>e.onWindow(Number(t.target.value)),children:[(0,g.jsx)(`option`,{value:60,children:`近 1 小时`}),(0,g.jsx)(`option`,{value:180,children:`近 3 小时`}),(0,g.jsx)(`option`,{value:720,children:`近 12 小时`})]})]}),(0,g.jsxs)(`div`,{className:`chart-wrap`,children:[(0,g.jsxs)(`div`,{className:`chart-legend`,children:[(0,g.jsxs)(`span`,{className:`legend-item`,children:[(0,g.jsx)(`span`,{className:`legend-swatch purple`}),`网关响应`]}),(0,g.jsxs)(`span`,{className:`legend-item`,children:[(0,g.jsx)(`span`,{className:`legend-swatch blue`}),`上游响应`]})]}),(0,g.jsxs)(`svg`,{className:`trend-svg`,viewBox:`0 0 720 210`,role:`img`,"aria-label":`请求耗时趋势折线图`,children:[(0,g.jsxs)(`defs`,{children:[(0,g.jsxs)(`linearGradient`,{id:`areaA`,x1:`0`,y1:`0`,x2:`0`,y2:`1`,children:[(0,g.jsx)(`stop`,{offset:`0%`,stopColor:`rgba(99,91,255,0.18)`}),(0,g.jsx)(`stop`,{offset:`100%`,stopColor:`rgba(99,91,255,0.02)`})]}),(0,g.jsxs)(`linearGradient`,{id:`areaB`,x1:`0`,y1:`0`,x2:`0`,y2:`1`,children:[(0,g.jsx)(`stop`,{offset:`0%`,stopColor:`rgba(59,130,246,0.16)`}),(0,g.jsx)(`stop`,{offset:`100%`,stopColor:`rgba(59,130,246,0.02)`})]})]}),[1,2,3,4].map(e=>(0,g.jsx)(`line`,{x1:`0`,y1:e*42,x2:720,y2:e*42,stroke:`#e2e8f0`,strokeWidth:`1`},e)),(0,g.jsx)(`path`,{d:`${i} L 720 210 L 0 210 Z`,fill:`url(#areaA)`,stroke:`none`}),(0,g.jsx)(`path`,{d:`${a} L 720 210 L 0 210 Z`,fill:`url(#areaB)`,stroke:`none`}),(0,g.jsx)(`path`,{d:i,fill:`none`,stroke:`#635bff`,strokeWidth:`2.4`,strokeLinecap:`round`}),(0,g.jsx)(`path`,{d:a,fill:`none`,stroke:`#3b82f6`,strokeWidth:`2.4`,strokeLinecap:`round`})]}),(0,g.jsx)(`div`,{className:`trend-labels`,children:c.map((e,t)=>(0,g.jsx)(`span`,{children:e},`${e}-${t}`))})]})]})}function S(e){return(0,g.jsxs)(`section`,{className:`card service-card endpoint-card`,children:[(0,g.jsx)(`div`,{className:`section-head compact`,children:(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`h3`,{children:`网关信息`}),(0,g.jsx)(`p`,{children:`桌面端与 CLI 共享同一套本地服务。`})]})}),(0,g.jsxs)(`div`,{className:`service-list compact-grid`,children:[(0,g.jsx)(p,{label:`管理页`,value:e.config?.adminUrl||`-`,code:!0}),(0,g.jsx)(p,{label:`Base URL`,value:e.config?.baseUrl||`-`,code:!0}),(0,g.jsx)(p,{label:`默认模型`,value:e.config?.settings.defaultModel||`-`}),(0,g.jsx)(p,{label:`生图模型`,value:`gpt-image-2`}),(0,g.jsx)(p,{label:`兼容接口`,value:e.config?.supportedEndpoints.map(e=>e.path).join(`,`)||`-`}),(0,g.jsx)(p,{label:`令牌预览`,value:e.config?.profile?.accessTokenPreview||`未登录`,code:!0}),(0,g.jsx)(p,{label:`模型来源`,value:e.config?.modelCatalog.source||`-`})]})]})}function C(e){return new Intl.NumberFormat(`zh-CN`).format(Math.round(e||0))}function w(e){let[t,n]=(0,h.useState)(60),s=e.requestLogs.length?e.requestLogs.reduce((e,t)=>e+t.durationMs,0)/e.requestLogs.length:0,c=e.config?.usage?.today,l=c?.failureCount??0;return(0,g.jsxs)(g.Fragment,{children:[(0,g.jsxs)(`section`,{className:`summary-grid desktop-summary-grid overview-summary-grid`,children:[(0,g.jsx)(m,{icon:f,label:`账号总数`,value:String(e.config?.status.profileCount||0),detail:`已保存到本地账号池`,tone:`blue`}),(0,g.jsx)(m,{icon:a,label:`当前账号状态`,value:(0,g.jsx)(_,{apiProfile:e.activeProfile,codexProfile:e.codexProfile,codexEmail:e.codexEmail,codexAccountId:e.codexAccountId,showEmails:e.showEmails}),detail:e.config?.status.loggedIn||e.codexProfile?``:`需要先登录或导入账号`,tone:e.config?.status.loggedIn||e.codexProfile?`green`:`orange`,compact:!0}),(0,g.jsx)(m,{icon:o,label:`今日请求数`,value:C(c?.requestCount??e.requestLogs.length),detail:c?`${C(c.successCount)} 成功 / ${C(c.failureCount)} 失败`:`基于本页最近测试记录`,tone:`blue`}),(0,g.jsx)(m,{icon:i,label:`今日 token`,value:C(c?.totalTokens??0),detail:c?`未返回 token ${C(c.unknownTokenCount)} 次`:`统计最近 ${e.requestLogs.length} 次`,tone:`orange`}),(0,g.jsx)(m,{icon:d,label:`服务状态`,value:e.config?.status.loggedIn?`运行中`:`等待登录`,detail:`网关可转发请求`,tone:e.config?.status.loggedIn?`green`:`orange`}),(0,g.jsx)(m,{icon:r,label:`今日异常`,value:C(l),detail:`平均耗时 ${u(c?c.averageDurationMs:s)}`,tone:l>0?`orange`:`green`})]}),(0,g.jsxs)(`section`,{className:`overview-grid`,children:[(0,g.jsx)(x,{config:e.config,requestLogs:e.requestLogs,windowMinutes:t,onWindow:n}),(0,g.jsx)(S,{config:e.config})]})]})}export{w as OverviewPage};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./earth-DFdZaQIi.js";import{t as a}from"./refresh-cw-CAAH2rqe.js";import{t as o}from"./search-B2hz41D3.js";import{f as s,i as c,m as l,o as u,s as d,w as f}from"./profiles-C5SmQvju.js";import{_ as p,n as ee,p as m,r as h}from"./index-_5Ny0cZf.js";var g=t(`monitor-cog`,[[`path`,{d:`M12 17v4`,key:`1riwvh`}],[`path`,{d:`m14.305 7.53.923-.382`,key:`1mlnsw`}],[`path`,{d:`m15.228 4.852-.923-.383`,key:`82mpwg`}],[`path`,{d:`m16.852 3.228-.383-.924`,key:`ln4sir`}],[`path`,{d:`m16.852 8.772-.383.923`,key:`1dejw0`}],[`path`,{d:`m19.148 3.228.383-.924`,key:`192kgf`}],[`path`,{d:`m19.53 9.696-.382-.924`,key:`fiavlr`}],[`path`,{d:`m20.772 4.852.924-.383`,key:`1j8mgp`}],[`path`,{d:`m20.772 7.148.924.383`,key:`zix9be`}],[`path`,{d:`M22 13v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7`,key:`1tnzv8`}],[`path`,{d:`M8 21h8`,key:`1ev6f3`}],[`circle`,{cx:`18`,cy:`6`,r:`3`,key:`1h7g24`}]]),te=t(`plug-zap`,[[`path`,{d:`M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z`,key:`goz73y`}],[`path`,{d:`m2 22 3-3`,key:`19mgm9`}],[`path`,{d:`M7.5 13.5 10 11`,key:`7xgeeb`}],[`path`,{d:`M10.5 16.5 13 14`,key:`10btkg`}],[`path`,{d:`m18 3-4 4h6l-4 4`,key:`16psg9`}]]),ne=t(`share-2`,[[`circle`,{cx:`18`,cy:`5`,r:`3`,key:`gq8acd`}],[`circle`,{cx:`6`,cy:`12`,r:`3`,key:`w7nqdw`}],[`circle`,{cx:`18`,cy:`19`,r:`3`,key:`1xt0gg`}],[`line`,{x1:`8.59`,x2:`15.42`,y1:`13.51`,y2:`17.49`,key:`47mynk`}],[`line`,{x1:`15.41`,x2:`8.59`,y1:`6.51`,y2:`10.49`,key:`1n3mei`}]]),re=t(`unplug`,[[`path`,{d:`m19 5 3-3`,key:`yk6iyv`}],[`path`,{d:`m2 22 3-3`,key:`19mgm9`}],[`path`,{d:`M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z`,key:`goz73y`}],[`path`,{d:`M7.5 13.5 10 11`,key:`7xgeeb`}],[`path`,{d:`M10.5 16.5 13 14`,key:`10btkg`}],[`path`,{d:`m12 6 6 6 2.3-2.3a2.4 2.4 0 0 0 0-3.4l-2.6-2.6a2.4 2.4 0 0 0-3.4 0Z`,key:`1snsnr`}]]),_=e(n(),1),v=r();function y(e){return e===`ai-zero-token`?`ai-zero-token`:`openai`}function b(e){return e===`openai`?`openai`:`AI Zero Token`}function ie(e){return e===`openai`?`保留 Codex 原生历史`:`新的 provider 历史`}function ae(e){return e===`openai`?`openai_base_url`:`[model_providers.ai-zero-token]`}function x(e){let t=e.trim();if(!t)throw Error(`请填写 Codex 网关 URL。`);/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(t)||(t=`http://${t}`);let n;try{n=new URL(t)}catch{throw Error(`Codex 网关 URL 格式错误,请填写 http(s) 地址或 IP:端口。`)}if(n.protocol!==`http:`&&n.protocol!==`https:`)throw Error(`Codex 网关 URL 只支持 http 或 https。`);n.hash=``,n.search=``;let r=n.pathname.replace(/\/+$/g,``);return!r||r===`/`||r===`/v1`?n.pathname=`/codex/v1`:r.endsWith(`/codex`)?n.pathname=`${r}/v1`:n.pathname=r,n.toString().replace(/\/+$/g,``)}function S(e){try{return x(e)}catch{return e.trim().replace(/\/+$/g,``)}}function C(e){return e?.codexBaseUrl||`http://127.0.0.1:8787/codex/v1`}function oe(e){return{defaultModel:e.settings.defaultModel,proxyEnabled:e.settings.networkProxy.enabled,proxyUrl:e.settings.networkProxy.url,proxyNoProxy:e.settings.networkProxy.noProxy||`localhost,127.0.0.1,::1`,autoSwitchEnabled:e.settings.autoSwitch.enabled,autoSwitchExcludedProfileIds:e.settings.autoSwitch.excludedProfileIds||[],quotaSyncConcurrency:String(e.settings.runtime?.quotaSyncConcurrency||3),freeAccountWebGenerationEnabled:!!e.settings.image?.freeAccountWebGenerationEnabled,serverPort:String(e.settings.server.port||8787)}}function se(e){return[l(e,!0),e.email||``,e.accountId,e.profileId,c(e)].join(` `).toLowerCase()}function w(e){let[t,n]=(0,_.useState)({defaultModel:``,proxyEnabled:!1,proxyUrl:``,proxyNoProxy:`localhost,127.0.0.1,::1`,autoSwitchEnabled:!1,autoSwitchExcludedProfileIds:[],quotaSyncConcurrency:`3`,freeAccountWebGenerationEnabled:!1,serverPort:`8787`}),[r,w]=(0,_.useState)(`local`),[T,E]=(0,_.useState)(`http://127.0.0.1:8787/codex/v1`),[D,O]=(0,_.useState)(!1),[ce,le]=(0,_.useState)(()=>new Set),[k,ue]=(0,_.useState)(``),[A,j]=(0,_.useState)(`openai`),[M,de]=(0,_.useState)(!1),[N,P]=(0,_.useState)(null),[fe,F]=(0,_.useState)(!1),I=(0,_.useRef)(null),L=ce.size>0;(0,_.useEffect)(()=>()=>{I.current&&window.clearTimeout(I.current)},[]),(0,_.useEffect)(()=>{!e.config||L||n(oe(e.config))},[e.config,L]),(0,_.useEffect)(()=>{if(!e.config||D)return;let t=C(e.config),n=e.config.codex.gatewayProvider?.baseUrl;E(n||t),w(n&&S(n)!==S(t)?`remote`:`local`)},[e.config,D]),(0,_.useEffect)(()=>{!e.config||M||j(y(e.config.codex.gatewayProvider?.providerId))},[e.config,M]);function R(e){n(t=>({...t,...e})),le(t=>{let n=new Set(t);for(let t of Object.keys(e))n.add(t);return n})}function pe(e,n){let r=new Set(t.autoSwitchExcludedProfileIds);n?r.add(e):r.delete(e),R({autoSwitchExcludedProfileIds:Array.from(r)})}function z(t){let n=C(e.config);O(!0),w(t),t===`local`?E(n):T.trim()||E(n)}function me(){return r===`local`?C(e.config):T}function B(e){de(!0),j(e)}let V=(0,_.useMemo)(()=>new Set(t.autoSwitchExcludedProfileIds),[t.autoSwitchExcludedProfileIds]),H=(0,_.useMemo)(()=>{let t=k.trim().toLowerCase();return(e.config?.profiles||[]).filter(e=>!t||se(e).includes(t))},[k,e.config?.profiles]),he=e.config?.profiles.length||0,U=(e.config?.profiles||[]).filter(e=>V.has(e.profileId)).length,ge=Math.max(0,he-U);async function _e(n){let r=(...e)=>e.some(e=>ce.has(e)),i=Number.parseInt(t.serverPort,10);if(r(`serverPort`)&&(!Number.isInteger(i)||i<1||i>65535)){e.setStatus(`端口必须是 1 到 65535 之间的整数。`);return}let a=Number.parseInt(t.quotaSyncConcurrency,10);if(r(`quotaSyncConcurrency`)&&(!Number.isInteger(a)||a<1||a>32)){e.setStatus(`全局额度刷新并发数必须是 1 到 32 之间的整数。`);return}let o={};r(`defaultModel`)&&(o.defaultModel=t.defaultModel),r(`proxyEnabled`,`proxyUrl`,`proxyNoProxy`)&&(o.networkProxy={enabled:t.proxyEnabled,url:t.proxyUrl,noProxy:t.proxyNoProxy}),r(`autoSwitchEnabled`,`autoSwitchExcludedProfileIds`)&&(o.autoSwitch={},r(`autoSwitchEnabled`)&&(o.autoSwitch.enabled=t.autoSwitchEnabled),r(`autoSwitchExcludedProfileIds`)&&(o.autoSwitch.excludedProfileIds=t.autoSwitchExcludedProfileIds)),r(`quotaSyncConcurrency`)&&(o.runtime={quotaSyncConcurrency:a}),r(`freeAccountWebGenerationEnabled`)&&(o.image={freeAccountWebGenerationEnabled:t.freeAccountWebGenerationEnabled}),r(`serverPort`)&&(o.server={port:i});let s=n?.restart?`restart`:`settings`;e.setBusy(s);try{let t=await m(`/_gateway/admin/settings`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:f(o)});e.setConfig(t),le(new Set),n?.restart?(e.setStatus(`设置已保存,正在重启本地网关...`),await m(`/_gateway/admin/restart`,{method:`POST`}),e.setStatus(`本地网关正在重启,页面会自动恢复。`)):e.setStatus(`设置已保存。`)}catch(t){e.setStatus(h(t))}finally{e.setBusy(null)}}async function ve(){e.setBusy(`proxy`);try{let n=await m(`/_gateway/admin/settings/proxy-test`,{method:`POST`,headers:{"Content-Type":`application/json`},body:f({networkProxy:{enabled:t.proxyEnabled,url:t.proxyUrl,noProxy:t.proxyNoProxy}})});e.setStatus(`代理测试通过: HTTP ${n.status},耗时 ${n.elapsedMs} ms。`)}catch(t){e.setStatus(`代理测试失败: ${h(t)}`)}finally{e.setBusy(null)}}async function ye(){e.setBusy(`models`);try{let t=await m(`/_gateway/models/refresh`,{method:`POST`});await e.refreshConfig({silent:!0});let n=t.catalog?.modelCount??0;e.setStatus(n>0?`Codex 模型列表已从网络同步,共 ${n} 个。`:`Codex 模型列表已从网络同步。`)}catch(t){e.setStatus(h(t))}finally{e.setBusy(null)}}async function be(){e.setBusy(`codex-share`),F(!1),P(null),I.current&&=(window.clearTimeout(I.current),null);try{let t=await m(`/_gateway/admin/share`);if(!t.primary){let n=t.lanReachable?`没有检测到可分享的局域网地址。请确认设备已连接 Wi-Fi 或局域网。`:`当前网关只允许本机访问,不能分享给局域网设备。请把网关监听地址从 ${t.serverHost} 改为 0.0.0.0 后重启。`;P({tone:`warning`,title:`不能分享代理配置`,detail:n}),e.setStatus(n);return}let n=t.addresses.slice(1).map(e=>`备用 Codex 远程网关 URL:\n${e.codexBaseUrl}`).join(`
|
|
2
|
-
|
|
3
|
-
`),r=[`AI Zero Token 代理配置`,``,`Codex 远程网关 URL:`,t.primary.codexBaseUrl,``,`OpenAI 兼容 Base URL:`,t.primary.baseUrl,``,`API Key:`,`任意值,例如 local`,``,`说明:`,`远程请求会消耗这台网关机器上保存的账号额度。`,`请确认两台设备在同一局域网,且防火墙允许访问该端口。`,...n?[``,n]:[]].join(`
|
|
4
|
-
`),i=await ee(r);i&&(F(!0),I.current=window.setTimeout(()=>{F(!1),I.current=null},2e3)),P({tone:i?`success`:`warning`,title:i?`代理配置已复制`:`复制失败,请手动复制`,detail:i?`把这段配置发给对方。对方在 AI Zero Token 的「远程网关」里填 Codex 地址,OpenAI 兼容客户端填 Base URL。`:`浏览器未允许写入剪贴板,请手动复制下面的代理配置。`,codexUrl:t.primary.codexBaseUrl,baseUrl:t.primary.baseUrl,apiKey:`任意值,例如 local`}),e.setStatus(i?`代理配置已复制:${t.primary.codexBaseUrl}`:r)}catch(t){let n=h(t);P({tone:`warning`,title:`代理配置生成失败`,detail:n}),e.setStatus(n)}finally{e.setBusy(null)}}async function W(t){if(t.config?.codexRestartSupported&&window.confirm(t.confirmMessage)){e.setStatus(t.restartingStatus);try{await m(`/_gateway/admin/desktop/restart-codex`,{method:`POST`}),e.setStatus(t.restartedStatus)}catch(n){e.setStatus(`${t.failedStatusPrefix}: ${h(n)}`)}return}e.setStatus(t.deferStatus)}async function xe(){e.setBusy(`codex-provider`);try{let t=A,n=b(t),r=x(me()),i=e.config?.codex.gatewayProvider?.baseUrl,a=y(e.config?.codex.gatewayProvider?.providerId),o=!!(e.config?.codex.gatewayProvider?.active&&a!==t),s=!!(e.config?.codex.gatewayProvider?.active&&i&&S(i)!==r);if(e.config?.codex.gatewayProvider?.active&&!s&&!o){let r=await m(`/_gateway/admin/codex/remove-provider`,{method:`POST`,headers:{"Content-Type":`application/json`},body:f({providerId:t})});r.config&&e.setConfig(r.config),r.codexProvider.removed?await W({config:r.config??e.config,confirmMessage:`Codex ${n} 接管已解除,是否现在重启 Codex 客户端?\n\nCodex 通常在启动时读取本机 config.toml,重启后会回到原本的 Codex 配置。`,deferStatus:`已解除 ${n} 接管。重启 Codex 后会回到原本的 Codex 配置。`,restartingStatus:`正在重启 Codex 客户端...`,restartedStatus:`已解除 ${n} 接管,并已重启 Codex 客户端。`,failedStatusPrefix:`已解除 ${n} 接管,但重启 Codex 失败`}):e.setStatus(`未发现当前受管的 Codex provider 配置。`);return}let c=!!e.config?.codex.gatewayProvider?.active,l=await m(`/_gateway/admin/codex/configure-provider`,{method:`POST`,headers:{"Content-Type":`application/json`},body:f({baseUrl:r,providerId:t})});l.config&&e.setConfig(l.config);let u=l.codexProvider.historyMigration?.migratedCount||0,d=l.codexProvider.historyMigration?.rolloutPatchedCount||0,p=u>0?`,已迁移 ${u} 条历史记录${d>0?`,已修复 ${d} 个会话索引`:``}`:``;await W({config:l.config??e.config,confirmMessage:t===`openai`?`Codex 接管将使用 openai 历史记录模式,是否现在重启 Codex 客户端?
|
|
5
|
-
|
|
6
|
-
重启后请求仍会走 AI Zero Token 网关,历史记录会继续归在 Codex 原生 openai provider 下。`:`Codex 接管将切换到 AI Zero Token 新 provider,是否现在重启 Codex 客户端?
|
|
7
|
-
|
|
8
|
-
重启后请求仍会走 AI Zero Token 网关,历史记录会归在新的 AI Zero Token provider 下。`,deferStatus:`${c?`已更新`:`已写入`} ${n} 接管配置:${l.codexProvider.baseUrl}${p}。重启 Codex 后生效。`,restartingStatus:`正在重启 Codex 客户端...`,restartedStatus:`${c?`已更新`:`已接管`} ${n} 请求,并已重启 Codex 客户端。`,failedStatusPrefix:`${c?`已更新`:`已接管`} ${n} 请求,但重启 Codex 失败`})}catch(t){e.setStatus(h(t))}finally{e.setBusy(null)}}let G=y(e.config?.codex.gatewayProvider?.providerId),K=b(G),q=ae(A),J=!!e.config?.codex.gatewayProvider?.active,Y=e.busy===`codex-provider`,X=e.busy===`codex-share`,Se=C(e.config),Ce=S(r===`local`?Se:T),Z=e.config?.codex.gatewayProvider?.baseUrl||``,Q=!!(J&&Z&&S(Z)!==Ce),$=!!(J&&G!==A),we=[`btn-secondary`,`codex-provider-button`,Y?`is-busy`:J&&!Q&&!$?`is-active`:`is-inactive`].join(` `),Te=Y?`处理中`:J&&!Q&&!$?`解除 Codex 接管`:J?`更新接管配置`:`写入并接管`,Ee=J?K:`未接管`,De=J?`is-included`:`is-excluded`;return(0,v.jsxs)(`section`,{className:`settings-page`,children:[(0,v.jsx)(`div`,{className:`settings-page-head settings-page-head-actions-only`,children:(0,v.jsx)(`div`,{className:`settings-page-actions`,children:(0,v.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:ye,disabled:e.busy===`models`,children:[e.busy===`models`?(0,v.jsx)(p,{className:`spin`,size:16}):(0,v.jsx)(a,{size:16}),`同步 Codex 模型`]})})}),(0,v.jsxs)(`div`,{className:`settings-grid`,children:[(0,v.jsxs)(`section`,{className:`settings-section codex-provider-section`,children:[(0,v.jsxs)(`div`,{className:`codex-provider-head`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`h4`,{children:`Codex 请求接管`}),(0,v.jsx)(`p`,{className:`hint`,children:`默认使用 openai 保留 Codex 原生历史;也可以切到 AI Zero Token,写入新的 provider 历史分组。接管地址既可以是本机网关,也可以是远程网关 URL。`})]}),(0,v.jsx)(`span`,{className:`count-pill ${De}`,children:Ee})]}),(0,v.jsxs)(`div`,{className:`codex-provider-mode-row`,children:[(0,v.jsxs)(`div`,{className:`codex-provider-mode-copy`,children:[(0,v.jsx)(`div`,{className:`codex-provider-mode-title`,children:`历史记录模式`}),(0,v.jsxs)(`p`,{className:`hint`,children:[b(A),` · `,ie(A)]})]}),(0,v.jsxs)(`div`,{className:`codex-provider-mode-toggle`,role:`group`,"aria-label":`历史记录模式`,children:[(0,v.jsx)(`button`,{className:`codex-provider-mode-option ${A===`openai`?`is-active`:``}`,type:`button`,onClick:()=>B(`openai`),children:`openai`}),(0,v.jsx)(`button`,{className:`codex-provider-mode-option ${A===`ai-zero-token`?`is-active`:``}`,type:`button`,onClick:()=>B(`ai-zero-token`),children:`AI Zero Token`})]})]}),(0,v.jsxs)(`div`,{className:`codex-provider-controls`,children:[(0,v.jsxs)(`div`,{className:`codex-mode-toggle`,role:`group`,"aria-label":`Codex 网关模式`,children:[(0,v.jsxs)(`button`,{className:`codex-mode-option ${r===`local`?`is-active`:``}`,type:`button`,onClick:()=>z(`local`),children:[(0,v.jsx)(g,{size:16}),`本机网关`]}),(0,v.jsxs)(`button`,{className:`codex-mode-option ${r===`remote`?`is-active`:``}`,type:`button`,onClick:()=>z(`remote`),children:[(0,v.jsx)(i,{size:16}),`远程网关`]})]}),(0,v.jsxs)(`label`,{className:`field codex-url-field`,children:[(0,v.jsx)(`span`,{children:`Codex 网关 URL`}),(0,v.jsx)(`input`,{className:`input codex-url-input`,value:r===`local`?Se:T,onChange:e=>{O(!0),w(`remote`),E(e.target.value)},placeholder:`http://192.168.1.10:8787/codex/v1`,readOnly:r===`local`})]}),(0,v.jsxs)(`div`,{className:`codex-provider-actions`,children:[(0,v.jsxs)(`button`,{className:`btn-secondary share-gateway-button`,type:`button`,onClick:be,disabled:X,children:[X?(0,v.jsx)(p,{className:`spin`,size:16}):(0,v.jsx)(ne,{size:16}),X?`生成中`:fe?`已复制`:`复制代理配置`]}),(0,v.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>z(`local`),children:[(0,v.jsx)(g,{size:16}),`使用本机地址`]}),(0,v.jsxs)(`button`,{className:we,type:`button`,onClick:xe,disabled:Y,children:[Y?(0,v.jsx)(p,{className:`spin`,size:16}):J&&!Q?(0,v.jsx)(re,{size:16}):(0,v.jsx)(te,{size:16}),Te]})]})]}),N?(0,v.jsxs)(`div`,{className:`share-gateway-feedback ${N.tone===`success`?`is-success`:`is-warning`}`,role:`status`,"aria-live":`polite`,children:[(0,v.jsx)(`strong`,{children:N.title}),(0,v.jsx)(`span`,{children:N.detail}),(0,v.jsxs)(`div`,{className:`share-gateway-config-list`,children:[N.codexUrl?(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`Codex 远程网关 URL`}),(0,v.jsx)(`code`,{children:N.codexUrl})]}):null,N.baseUrl?(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`OpenAI 兼容 Base URL`}),(0,v.jsx)(`code`,{children:N.baseUrl})]}):null,N.apiKey?(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`API Key`}),(0,v.jsx)(`code`,{children:N.apiKey})]}):null]})]}):null,(0,v.jsxs)(`p`,{className:`hint`,children:[`可直接输入 IP:端口,系统会自动补全为 http://IP:端口/codex/v1。当前将写入 `,(0,v.jsx)(`code`,{children:q}),`:`,(0,v.jsx)(`code`,{children:Ce||`-`})]}),(0,v.jsxs)(`div`,{className:`codex-provider-meta-strip`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`配置文件`}),(0,v.jsx)(`code`,{children:e.config?.codex.gatewayProvider.path||`~/.codex/config.toml`})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`当前状态`}),(0,v.jsx)(`code`,{children:J?`${K} · ${ie(G)}`:`未接管`})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`写入目标`}),(0,v.jsx)(`code`,{children:q})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`接管地址`}),(0,v.jsx)(`code`,{children:Z||`未写入受管配置`})]}),(0,v.jsxs)(`div`,{className:`is-warning`,children:[(0,v.jsx)(`span`,{children:`远程网关提示`}),(0,v.jsx)(`strong`,{children:`远程请求会消耗对方网关机器上保存的账号额度。`})]})]})]}),(0,v.jsxs)(`section`,{className:`settings-section`,children:[(0,v.jsx)(`h4`,{children:`模型`}),(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`默认文本模型`}),(0,v.jsx)(`select`,{className:`control`,value:t.defaultModel,onChange:e=>R({defaultModel:e.target.value}),children:(e.config?.models||[]).map(e=>(0,v.jsx)(`option`,{value:e.id,children:e.id},e.id))})]}),(0,v.jsxs)(`p`,{className:`hint`,children:[`模型列表来源:`,e.config?.modelCatalog.source||`-`,`,共 `,e.config?.modelCatalog.modelCount||0,` 个。`]})]}),(0,v.jsxs)(`section`,{className:`settings-section free-image-section`,children:[(0,v.jsx)(`h4`,{children:`Free 账号生图`}),(0,v.jsxs)(`label`,{className:`switch-line`,children:[(0,v.jsx)(`input`,{type:`checkbox`,checked:t.freeAccountWebGenerationEnabled,onChange:e=>R({freeAccountWebGenerationEnabled:e.target.checked})}),(0,v.jsx)(`span`,{children:`允许 Free 账号使用 ChatGPT 网页链路生图`})]}),(0,v.jsx)(`p`,{className:`hint`,children:`关闭时,Free 账号生图会继续走原先 Codex Responses 图片工具链路,由上游决定是否可用。`}),(0,v.jsxs)(`p`,{className:`free-image-warning`,children:[(0,v.jsx)(`strong`,{children:`封号风险:`}),`该能力不是官方 API 标准流程,使用 Free 账号生图存在账号风控或封号风险。`,(0,v.jsx)(`strong`,{children:`额度较少:`}),`Free 额度通常较少,当前经验值大约 8 张,实际以上游账号为准。`]})]}),(0,v.jsxs)(`section`,{className:`settings-section`,children:[(0,v.jsx)(`h4`,{children:`上游代理`}),(0,v.jsxs)(`label`,{className:`switch-line`,children:[(0,v.jsx)(`input`,{type:`checkbox`,checked:t.proxyEnabled,onChange:e=>R({proxyEnabled:e.target.checked})}),(0,v.jsx)(`span`,{children:`启用 OAuth、模型刷新和接口转发代理`})]}),(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`代理地址`}),(0,v.jsx)(`input`,{className:`input`,value:t.proxyUrl,onChange:e=>R({proxyUrl:e.target.value}),placeholder:`http://127.0.0.1:7890`})]}),(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`No Proxy`}),(0,v.jsx)(`input`,{className:`input`,value:t.proxyNoProxy,onChange:e=>R({proxyNoProxy:e.target.value})})]}),(0,v.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:ve,disabled:e.busy===`proxy`,children:`测试代理`})]}),(0,v.jsxs)(`section`,{className:`settings-section`,children:[(0,v.jsx)(`h4`,{children:`端口`}),(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`网关端口`}),(0,v.jsx)(`input`,{className:`input`,inputMode:`numeric`,type:`number`,min:1,max:65535,value:t.serverPort,onChange:e=>R({serverPort:e.target.value})})]}),(0,v.jsx)(`p`,{className:`hint`,children:`修改后重启本地网关生效,桌面窗口不会退出。若端口被占用,启动时会自动顺延到下一个可用端口。`})]}),(0,v.jsxs)(`section`,{className:`settings-section`,children:[(0,v.jsx)(`h4`,{children:`账号运行策略`}),(0,v.jsxs)(`label`,{className:`switch-line`,children:[(0,v.jsx)(`input`,{type:`checkbox`,checked:t.autoSwitchEnabled,onChange:e=>R({autoSwitchEnabled:e.target.checked})}),(0,v.jsx)(`span`,{children:`当前 API 账号额度耗尽后自动切换到下一个仍有额度的账号`})]}),(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`全局额度刷新并发数`}),(0,v.jsx)(`input`,{className:`input`,inputMode:`numeric`,max:32,min:1,type:`number`,value:t.quotaSyncConcurrency,onChange:e=>R({quotaSyncConcurrency:e.target.value})})]}),(0,v.jsx)(`p`,{className:`hint`,children:`手动刷新全部账号额度时使用,默认 3。账号很多可以调高,遇到限流或失败增多时调低。`}),(0,v.jsx)(`p`,{className:`hint`,children:e.status})]}),(0,v.jsxs)(`section`,{className:`settings-section auto-switch-exclusion-section`,children:[(0,v.jsxs)(`div`,{className:`auto-switch-exclusion-head`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`h4`,{children:`不参与自动轮换名单`}),(0,v.jsx)(`p`,{className:`hint`,children:`这些账号不会被自动切换选中,也不会在自己额度耗尽时触发自动切走;仍可在账号页手动应用到网关或 Codex。`})]}),(0,v.jsxs)(`div`,{className:`auto-switch-counts`,"aria-label":`自动轮换账号统计`,children:[(0,v.jsxs)(`span`,{className:`count-pill is-included`,children:[`参与 `,ge,` 个`]}),(0,v.jsxs)(`span`,{className:`count-pill is-excluded`,children:[`不参与 `,U,` 个`]})]})]}),(0,v.jsxs)(`label`,{className:`auto-switch-search`,children:[(0,v.jsx)(o,{size:16}),(0,v.jsx)(`input`,{value:k,onChange:e=>ue(e.target.value),placeholder:`搜索邮箱、账号 ID 或 Profile ID`})]}),(0,v.jsx)(`div`,{className:`auto-switch-profile-list`,children:H.length===0?(0,v.jsx)(`div`,{className:`auto-switch-empty`,children:`还没有匹配的账号。`}):H.map(t=>{let n=V.has(t.profileId),r=s(t),i=!!(e.config?.codex.accountId&&e.config.codex.accountId===t.accountId),a=u(t)?`登录不可用`:d(t)?`额度耗尽`:``;return(0,v.jsxs)(`label`,{className:`auto-switch-profile-row ${n?`is-excluded`:``}`,children:[(0,v.jsx)(`input`,{type:`checkbox`,checked:n,onChange:e=>pe(t.profileId,e.target.checked)}),(0,v.jsxs)(`span`,{className:`auto-switch-profile-main`,children:[(0,v.jsx)(`strong`,{children:l(t,e.showEmails)}),(0,v.jsxs)(`span`,{children:[c(t),` · `,r.label,t.isActive?` · 当前 API 使用中`:``,i?` · Codex 使用中`:``,a?` · ${a}`:``]})]}),(0,v.jsx)(`span`,{className:`auto-switch-state-pill ${n?`is-excluded`:`is-included`}`,children:n?`不参与轮换`:`参与轮换`})]},t.profileId)})})]}),(0,v.jsxs)(`section`,{className:`settings-section`,children:[(0,v.jsx)(`h4`,{children:`显示`}),(0,v.jsxs)(`label`,{className:`switch-line`,children:[(0,v.jsx)(`input`,{type:`checkbox`,checked:e.showEmails,onChange:t=>e.setShowEmails(t.target.checked)}),(0,v.jsx)(`span`,{children:`脱敏模式`})]}),(0,v.jsx)(`p`,{className:`hint`,children:`开启后账号邮箱将以脱敏形式展示。`})]})]}),(0,v.jsxs)(`div`,{className:`settings-page-actions settings-page-footer-actions`,children:[(0,v.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>void _e(),disabled:e.busy===`settings`||e.busy===`restart`||!L,children:`保存设置`}),(0,v.jsx)(`button`,{className:`btn-primary`,type:`button`,onClick:()=>void _e({restart:!0}),disabled:e.busy===`settings`||e.busy===`restart`||!L||!e.config?.restartSupported,children:`保存并重启网关`})]})]})}export{w as SettingsPage};
|