ai-zero-token 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +31 -6
  3. package/README.zh-CN.md +31 -6
  4. package/admin-ui/dist/assets/accounts-CTjk9c4F.js +4 -0
  5. package/admin-ui/dist/assets/{docs-CihX3Xsm.js → docs-oNIugCIL.js} +3 -3
  6. package/admin-ui/dist/assets/{image-bed-BGjlDLks.js → image-bed-CQtIhjg_.js} +1 -1
  7. package/admin-ui/dist/assets/index-By4r-wy3.css +1 -0
  8. package/admin-ui/dist/assets/index-rgcJgVAu.js +10 -0
  9. package/admin-ui/dist/assets/{launch-BWw7Odq7.js → launch-B-2Zdz9m.js} +1 -1
  10. package/admin-ui/dist/assets/logs-JFuSf56b.js +1 -0
  11. package/admin-ui/dist/assets/{network-detect-cUdjg4zk.js → network-detect-SfvK6uhx.js} +1 -1
  12. package/admin-ui/dist/assets/{overview-CsjVVcvi.js → overview-X_WodIqE.js} +1 -1
  13. package/admin-ui/dist/assets/settings-0eXUAvcm.js +1 -0
  14. package/admin-ui/dist/assets/{tester-BIvH_8DY.js → tester-ocpF053C.js} +1 -1
  15. package/admin-ui/dist/index.html +2 -2
  16. package/build/mac-install-guide.txt +25 -0
  17. package/build/tray-icon-template.png +0 -0
  18. package/dist/core/providers/openai-codex/chat.js +77 -0
  19. package/dist/core/services/auth-service.js +88 -12
  20. package/dist/core/services/chat-service.js +1 -0
  21. package/dist/core/services/config-service.js +87 -23
  22. package/dist/core/services/version-service.js +1 -1
  23. package/dist/core/store/profile-store.js +73 -32
  24. package/dist/core/store/settings-store.js +14 -0
  25. package/dist/desktop/main.js +616 -15
  26. package/dist/server/app.js +512 -58
  27. package/dist/server/index.js +2 -1
  28. package/docs/API_USAGE.md +65 -1
  29. package/docs/DESKTOP_RELEASE.md +48 -3
  30. package/package.json +33 -2
  31. package/admin-ui/dist/assets/accounts-Ddq82u6R.js +0 -1
  32. package/admin-ui/dist/assets/index-CX8e0-BB.js +0 -10
  33. package/admin-ui/dist/assets/index-ywn2Jwpu.css +0 -1
  34. package/admin-ui/dist/assets/logs-DDdgDVwo.js +0 -1
  35. package/admin-ui/dist/assets/settings-Be99HpDD.js +0 -1
@@ -41,7 +41,8 @@ async function startServer(params) {
41
41
  const app = createApp({
42
42
  corsOrigin: resolveCorsOrigin(),
43
43
  bodyLimit,
44
- onRestart: params?.onRestart
44
+ onRestart: params?.onRestart,
45
+ onRestartCodex: params?.onRestartCodex
45
46
  });
46
47
  try {
47
48
  await app.listen({
package/docs/API_USAGE.md CHANGED
@@ -41,6 +41,24 @@ Image model: gpt-image-2
41
41
 
42
42
  Use `GET /v1/models` to see the models available through the current local Codex cache.
43
43
 
44
+ ## OpenClaw Settings
45
+
46
+ Use the OpenAI-compatible provider mode in OpenClaw:
47
+
48
+ ```text
49
+ Provider: OpenAI compatible
50
+ Base URL: http://127.0.0.1:8787/v1
51
+ API Key: local
52
+ Model: gpt-5.4
53
+ Chat endpoint: /chat/completions
54
+ Streaming: enabled
55
+ Tools / function calling: enabled
56
+ ```
57
+
58
+ The gateway accepts OpenClaw-style `chat.completions` requests with `tools`, `tool_choice`, `parallel_tool_calls`, `reasoning_effort`, assistant `tool_calls`, and tool-role result messages. It translates those fields to the upstream Codex Responses shape and returns OpenAI-style chat responses.
59
+
60
+ OpenClaw requests are visible in the management console request log when the client sends an OpenClaw user agent. The log keeps safe summaries only; it does not store full access tokens.
61
+
44
62
  ## Models
45
63
 
46
64
  ```bash
@@ -74,6 +92,50 @@ curl http://127.0.0.1:8787/v1/chat/completions \
74
92
  "messages": [
75
93
  { "role": "user", "content": "Reply with OK only." }
76
94
  ]
95
+ }'
96
+ ```
97
+
98
+ Streaming chat completions:
99
+
100
+ ```bash
101
+ curl http://127.0.0.1:8787/v1/chat/completions \
102
+ -H "Content-Type: application/json" \
103
+ -d '{
104
+ "model": "gpt-5.4",
105
+ "stream": true,
106
+ "messages": [
107
+ { "role": "user", "content": "Reply with OK only." }
108
+ ]
109
+ }'
110
+ ```
111
+
112
+ Tool-call compatible request:
113
+
114
+ ```bash
115
+ curl http://127.0.0.1:8787/v1/chat/completions \
116
+ -H "Content-Type: application/json" \
117
+ -d '{
118
+ "model": "gpt-5.4",
119
+ "messages": [
120
+ { "role": "user", "content": "What is the weather tool argument for Shanghai?" }
121
+ ],
122
+ "tools": [
123
+ {
124
+ "type": "function",
125
+ "function": {
126
+ "name": "get_weather",
127
+ "description": "Get weather for a city.",
128
+ "parameters": {
129
+ "type": "object",
130
+ "properties": {
131
+ "city": { "type": "string" }
132
+ },
133
+ "required": ["city"]
134
+ }
135
+ }
136
+ }
137
+ ],
138
+ "tool_choice": "auto"
77
139
  }'
78
140
  ```
79
141
 
@@ -134,5 +196,7 @@ console.log(response.choices[0]?.message?.content);
134
196
 
135
197
  - Login first through the management page or `azt login`.
136
198
  - A model appearing in `/v1/models` means the local Codex cache lists it. Final availability still depends on the active account.
137
- - `stream=true` is not supported yet.
199
+ - `stream=true` is supported for `/v1/chat/completions` through OpenAI-style SSE chunks. `/v1/responses` streaming is still not implemented.
200
+ - `n > 1` is not supported for `/v1/chat/completions`.
201
+ - Tool/function calling is supported for common OpenAI-compatible clients, including OpenClaw, but exact upstream behavior still depends on the active Codex model and account.
138
202
  - The default listener is `0.0.0.0:8787`, so local-network clients can call the gateway by using the machine IP.
@@ -2,6 +2,16 @@
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.4 Release Notes
6
+
7
+ Version `2.0.4` adds the macOS menu-bar account panel and OpenClaw compatibility work:
8
+
9
+ - Menu-bar quick account panel for switching gateway/Codex accounts.
10
+ - Menu actions for quota refresh, Base URL copy, console open, gateway restart, and quit.
11
+ - Desktop Codex restart hook after applying an account to local Codex.
12
+ - OpenClaw-compatible `chat.completions` streaming and tool-call fields.
13
+ - Real gateway request log entries for recent API traffic.
14
+
5
15
  ## 2.0.0 Release Notes
6
16
 
7
17
  Version `2.0.0` is the first desktop-focused major release. It includes:
@@ -41,6 +51,13 @@ npm run dist:win
41
51
 
42
52
  Creates macOS and Windows distributables. macOS builds should be produced on macOS. Windows builds are best produced on Windows CI or a runner with a complete Windows packaging environment.
43
53
 
54
+ `npm run dist:mac` must build both Apple Silicon and Intel macOS packages. It runs:
55
+
56
+ ```bash
57
+ npm run dist:mac:arm64
58
+ npm run dist:mac:x64
59
+ ```
60
+
44
61
  ## UI Engineering Standards
45
62
 
46
63
  Before building release artifacts, the desktop React UI should follow:
@@ -65,6 +82,8 @@ Unsigned builds are suitable for internal testing only. Public commercial distri
65
82
 
66
83
  `electron-builder` reads the standard signing environment variables. Configure these in CI instead of committing credentials to the repository.
67
84
 
85
+ Until macOS Developer ID signing and notarization are configured, each macOS DMG must include `build/mac-install-guide.txt` so users can handle the Gatekeeper verification prompt.
86
+
68
87
  ## Release Artifacts
69
88
 
70
89
  The packaged output is written to:
@@ -75,17 +94,42 @@ release/
75
94
 
76
95
  The folder is intentionally ignored by git.
77
96
 
97
+ Every desktop GitHub Release must upload exactly these user-facing artifacts:
98
+
99
+ ```text
100
+ AI Zero Token-{version}-mac-arm64.dmg
101
+ AI Zero Token-{version}-mac-x64.dmg
102
+ AI Zero Token Setup {version}.exe
103
+ AI Zero Token-{version}-win.zip
104
+ ```
105
+
106
+ For `2.0.4`, replace `{version}` with `2.0.4`.
107
+
108
+ Artifact purpose:
109
+
110
+ - `AI Zero Token-{version}-mac-arm64.dmg`: macOS Apple Silicon builds for M1/M2/M3/M4 devices.
111
+ - `AI Zero Token-{version}-mac-x64.dmg`: macOS Intel builds.
112
+ - `AI Zero Token Setup {version}.exe`: Windows installer and primary Windows download.
113
+ - `AI Zero Token-{version}-win.zip`: Windows portable zip.
114
+
115
+ Do not upload unpacked app directories, debug metadata, universal macOS builds, or auto-update metadata unless the release explicitly enables an auto-update channel.
116
+
117
+ macOS DMG files should include the in-package `mac-install-guide.txt` helper document. Do not upload this text file as a standalone GitHub Release asset.
118
+
78
119
  ### Publish Flow
79
120
 
80
121
  1. Build the desktop package:
81
122
 
82
123
  ```bash
83
- npm run dist:dir
124
+ npm run dist:mac
125
+ npm run dist:win
84
126
  ```
85
127
 
86
- 2. Upload the generated files from `release/` to the matching GitHub Release tag.
128
+ 2. Rename the generated files, if needed, so the GitHub Release uses the standard artifact names listed above.
129
+
130
+ 3. Upload only the standard artifact files from `release/` to the matching GitHub Release tag.
87
131
 
88
- 3. Publish the npm package after confirming `package.json` and `package-lock.json` both point at the new version:
132
+ 4. Publish the npm package after confirming `package.json` and `package-lock.json` both point at the new version:
89
133
 
90
134
  ```bash
91
135
  npm publish
@@ -99,6 +143,7 @@ App icon files live in:
99
143
  build/icon.png
100
144
  build/icon.icns
101
145
  build/icon.ico
146
+ build/tray-icon-template.png
102
147
  ```
103
148
 
104
149
  They are included in Electron packaging and npm packing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-zero-token",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Local-first OpenAI-compatible AI CLI and gateway with Codex OAuth, multi-account management, and gpt-image-2 image generation/editing.",
5
5
  "license": "MIT",
6
6
  "author": "AI Zero Token Contributors",
@@ -50,7 +50,7 @@
50
50
  "desktop:dev": "node scripts/dev.mjs desktop",
51
51
  "dist": "npm run build && electron-builder",
52
52
  "dist:dir": "npm run build && electron-builder --dir",
53
- "dist:mac": "npm run build && electron-builder --mac --universal",
53
+ "dist:mac": "npm run dist:mac:arm64 && npm run dist:mac:x64",
54
54
  "dist:mac:arm64": "npm run build && electron-builder --mac --arm64",
55
55
  "dist:mac:x64": "npm run build && electron-builder --mac --x64",
56
56
  "dist:win": "npm run build && electron-builder --win",
@@ -63,6 +63,7 @@
63
63
  "dependencies": {
64
64
  "@fastify/cors": "^11.1.0",
65
65
  "fastify": "^5.8.2",
66
+ "fflate": "^0.8.2",
66
67
  "zod": "^4.3.6"
67
68
  },
68
69
  "devDependencies": {
@@ -89,11 +90,18 @@
89
90
  "dist/**/*",
90
91
  "admin-ui/dist/**/*",
91
92
  "build/icon.*",
93
+ "build/tray-icon-template.png",
92
94
  "package.json",
93
95
  "README.md",
94
96
  "README.zh-CN.md",
95
97
  "LICENSE"
96
98
  ],
99
+ "extraResources": [
100
+ {
101
+ "from": "build/mac-install-guide.txt",
102
+ "to": "mac-install-guide.txt"
103
+ }
104
+ ],
97
105
  "extraMetadata": {
98
106
  "main": "dist/desktop/main.js"
99
107
  },
@@ -106,6 +114,27 @@
106
114
  "zip"
107
115
  ]
108
116
  },
117
+ "dmg": {
118
+ "contents": [
119
+ {
120
+ "x": 130,
121
+ "y": 160,
122
+ "type": "file"
123
+ },
124
+ {
125
+ "x": 410,
126
+ "y": 160,
127
+ "type": "link",
128
+ "path": "/Applications"
129
+ },
130
+ {
131
+ "x": 270,
132
+ "y": 300,
133
+ "type": "file",
134
+ "path": "build/mac-install-guide.txt"
135
+ }
136
+ ]
137
+ },
109
138
  "win": {
110
139
  "icon": "build/icon.ico",
111
140
  "target": [
@@ -125,6 +154,8 @@
125
154
  "build/icon.png",
126
155
  "build/icon.icns",
127
156
  "build/icon.ico",
157
+ "build/tray-icon-template.png",
158
+ "build/mac-install-guide.txt",
128
159
  "CHANGELOG.md",
129
160
  "docs/API_USAGE.md",
130
161
  "docs/DESKTOP_RELEASE.md",
@@ -1 +0,0 @@
1
- import{a as e,r as t,t as n}from"./jsx-runtime-DqpGtLhh.js";import{t as r}from"./earth-DFdZaQIi.js";import{t as i}from"./refresh-cw-CAAH2rqe.js";import{t as a}from"./search-B2hz41D3.js";import{_ as o,a as s,b as c,d as l,f as u,g as d,h as f,i as p,m,n as h,o as g,p as _,r as v,s as y,t as b,u as x,v as S,w as C,y as w}from"./profiles-DMOjJORP.js";import{_ as T,d as E,p as D,r as O,x as k}from"./index-CX8e0-BB.js";import{t as A}from"./InfoRow-0ULI9iI3.js";var j=e(t(),1),M=n();function N(e){let t=e.config?.codex?.accountId,n=e.profiles.length<=0?``:e.profiles.length===1?`profile-count-1`:e.profiles.length===2?`profile-count-2`:e.profiles.length===3?`profile-count-3`:`profile-count-many`;return(0,M.jsxs)(`section`,{className:`card`,id:`accounts`,children:[(0,M.jsxs)(`div`,{className:`section-head`,children:[(0,M.jsxs)(`div`,{children:[(0,M.jsx)(`h2`,{children:`账号额度预览`}),(0,M.jsx)(`p`,{children:`账号信息采用卡片式布局展示,支持搜索、状态筛选和额度排序。`})]}),(0,M.jsxs)(`div`,{className:`section-actions`,children:[(0,M.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onLocate,children:`定位当前账号`}),(0,M.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onExportSelected,children:`导出所选`}),(0,M.jsx)(`button`,{className:`btn-primary`,type:`button`,onClick:e.onAddAccount,children:`新增账号`}),(0,M.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onRefreshStatus,children:`刷新状态`}),(0,M.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:e.onClearAccounts,children:`清空账号`})]})]}),(0,M.jsxs)(`div`,{className:`filter-row`,children:[(0,M.jsxs)(`label`,{className:`search-box`,children:[(0,M.jsx)(a,{size:16}),(0,M.jsx)(`input`,{value:e.filter.search,onChange:t=>e.onFilter({...e.filter,search:t.target.value}),placeholder:`搜索邮箱、账号 ID 或 Profile ID`})]}),(0,M.jsxs)(`select`,{className:`control`,value:e.filter.status,onChange:t=>e.onFilter({...e.filter,status:t.target.value}),children:[(0,M.jsx)(`option`,{value:`all`,children:`全部状态`}),(0,M.jsx)(`option`,{value:`active`,children:`使用中`}),(0,M.jsx)(`option`,{value:`healthy`,children:`健康`}),(0,M.jsx)(`option`,{value:`warning`,children:`即将耗尽`}),(0,M.jsx)(`option`,{value:`exhausted`,children:`额度耗尽`}),(0,M.jsx)(`option`,{value:`invalid`,children:`登录失效`}),(0,M.jsx)(`option`,{value:`expired`,children:`已过期`})]}),(0,M.jsxs)(`select`,{className:`control`,value:e.filter.sort,onChange:t=>e.onFilter({...e.filter,sort:t.target.value}),children:[(0,M.jsx)(`option`,{value:`quota-desc`,children:`默认排序`}),(0,M.jsx)(`option`,{value:`latency-asc`,children:`按额度更新时间`}),(0,M.jsx)(`option`,{value:`expiry-asc`,children:`按过期时间`}),(0,M.jsx)(`option`,{value:`name-asc`,children:`按名称排序`}),(0,M.jsx)(`option`,{value:`quota-asc`,children:`按剩余额度升序`}),(0,M.jsx)(`option`,{value:`plan-desc`,children:`按套餐排序`}),(0,M.jsx)(`option`,{value:`email-asc`,children:`按邮箱排序`})]}),(0,M.jsxs)(`span`,{className:`account-selected-count`,children:[`已选择 `,e.selectedCount,` 个`]})]}),(0,M.jsx)(`div`,{className:`account-grid ${n}`,children:e.profiles.length===0?(0,M.jsx)(`div`,{className:`empty-state`,children:`还没有匹配的账号。可以新增账号或调整筛选条件。`}):e.profiles.map(n=>{let a=u(n),f=l(n),v=w(n),y=!!e.expandedProfiles[n.profileId],x=!!(t&&n.accountId===t),C=c(n,x),E=g(n),D=s(n),O=typeof e.busy==`string`&&e.busy.startsWith(`profile:`)&&e.busy.endsWith(n.profileId),j=e.busy===`profile:sync-quota:${n.profileId}`;return(0,M.jsxs)(`article`,{className:`account-card plan-${h(n)} ${E?`is-auth-invalid`:``}`,"data-profile-card":n.profileId,title:E?b(n):void 0,children:[C&&(0,M.jsx)(`span`,{className:`usage-corner ${C.className}`,children:(0,M.jsx)(`span`,{children:C.label})}),(0,M.jsxs)(`div`,{className:`account-head`,children:[(0,M.jsxs)(`div`,{className:`account-title`,children:[(0,M.jsxs)(`div`,{className:`account-name`,children:[(0,M.jsx)(`span`,{className:`avatar`,children:_(n)}),(0,M.jsx)(`strong`,{children:m(n,e.showEmails)}),(0,M.jsx)(`button`,{"aria-label":`刷新额度`,className:`account-icon-btn`,disabled:O,onClick:()=>e.onAction(`sync-quota`,n),title:`刷新额度`,type:`button`,children:j?(0,M.jsx)(T,{className:`spin`,size:14}):(0,M.jsx)(i,{size:14})})]}),(0,M.jsxs)(`div`,{className:`badge-row`,children:[(0,M.jsx)(`span`,{className:`badge brand`,children:p(n)}),(0,M.jsx)(`span`,{className:`badge ${a.tone}`,children:a.label}),(0,M.jsx)(`span`,{className:`badge ${D.ok?`green`:`orange`}`,children:`gpt-image-2`})]})]}),(0,M.jsxs)(`label`,{className:`account-select`,children:[(0,M.jsx)(`input`,{type:`checkbox`,checked:!!e.selectedProfiles[n.profileId],onChange:t=>e.onSelect(n.profileId,t.target.checked)}),(0,M.jsx)(`span`,{children:`选择`})]})]}),(0,M.jsxs)(`div`,{className:`account-metrics`,children:[(0,M.jsx)(F,{label:o(n,`primary`),value:f,tone:d(f)}),(0,M.jsx)(F,{label:o(n,`secondary`),value:v,tone:d(v)})]}),(0,M.jsxs)(`div`,{className:`usage-status-row`,children:[(0,M.jsxs)(`span`,{className:`usage-status ${n.isActive?`is-active`:``}`,children:[(0,M.jsx)(r,{size:14}),(0,M.jsx)(`span`,{children:`API`}),(0,M.jsx)(`span`,{className:`usage-dot ${n.isActive?`active`:``}`}),(0,M.jsx)(`span`,{className:`usage-state-text`,children:n.isActive?`使用中`:`未使用`})]}),(0,M.jsxs)(`span`,{className:`usage-status ${x?`is-active`:``}`,children:[(0,M.jsx)(k,{size:14}),(0,M.jsx)(`span`,{children:`Codex`}),(0,M.jsx)(`span`,{className:`usage-dot ${x?`active`:``}`}),(0,M.jsx)(`span`,{className:`usage-state-text`,children:x?`使用中`:`未使用`})]})]}),(0,M.jsxs)(`div`,{className:`compact-meta-row`,children:[(0,M.jsxs)(`div`,{className:`compact-reset-list`,children:[(0,M.jsxs)(`div`,{className:`compact-meta-item`,children:[(0,M.jsx)(`label`,{children:o(n,`primary`)}),(0,M.jsx)(`strong`,{children:S(n,`primary`)})]}),(0,M.jsxs)(`div`,{className:`compact-meta-item`,children:[(0,M.jsx)(`label`,{children:o(n,`secondary`)}),(0,M.jsx)(`strong`,{children:S(n,`secondary`)})]})]}),(0,M.jsx)(`div`,{className:`compact-meta-actions`,children:(0,M.jsxs)(`button`,{className:`details-toggle ${y?`is-expanded`:``}`,type:`button`,onClick:()=>e.onToggle(n.profileId),children:[(0,M.jsx)(`span`,{children:y?`收起详情`:`查看详情`}),(0,M.jsx)(P,{})]})})]}),y&&(0,M.jsxs)(`div`,{className:`meta-grid`,children:[(0,M.jsx)(A,{label:`套餐`,value:p(n)}),(0,M.jsx)(A,{label:`Account ID`,value:(e.showEmails,n.accountId),code:!0}),(0,M.jsx)(A,{label:`Profile ID`,value:(e.showEmails,n.profileId),code:!0}),(0,M.jsx)(A,{label:`认证状态`,value:b(n)}),(0,M.jsx)(A,{label:`生图能力`,value:D.ok?`gpt-image-2 可用`:D.detail}),(0,M.jsx)(A,{label:`过期时间`,value:n.expiresAt?new Date(n.expiresAt).toLocaleString(`zh-CN`):`-`}),(0,M.jsx)(A,{label:`额度快照`,value:n.quota?.capturedAt?new Date(n.quota.capturedAt).toLocaleString(`zh-CN`):`-`})]}),(0,M.jsxs)(`div`,{className:`account-actions`,children:[(0,M.jsx)(`button`,{className:`btn-secondary ${n.isActive?`is-current`:``}`,type:`button`,onClick:()=>e.onAction(`activate`,n),disabled:n.isActive||O||E,children:E?`网关不可用`:n.isActive?`网关使用中`:`应用网关`}),(0,M.jsx)(`button`,{className:`btn-secondary ${x?`is-current codex`:``}`,type:`button`,onClick:()=>e.onAction(`apply-codex`,n),disabled:x||O||E,children:E?`Codex 不可用`:x?`Codex 使用中`:`应用 Codex`}),(0,M.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>e.onAction(`export`,n),disabled:O,children:`导出`}),(0,M.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:()=>e.onAction(`remove`,n),disabled:O,children:`删除`})]})]},n.profileId)})})]})}function P(){return(0,M.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,"aria-hidden":`true`,children:(0,M.jsx)(`path`,{d:`m6 9 6 6 6-6`})})}function F(e){return(0,M.jsxs)(`div`,{className:`quota-row`,children:[(0,M.jsxs)(`div`,{className:`quota-line`,children:[(0,M.jsxs)(`span`,{children:[e.label,` · 已用 `,e.value,`% / 剩余 `,100-e.value,`%`]}),(0,M.jsxs)(`strong`,{children:[`剩余 `,100-e.value,`%`]})]}),(0,M.jsx)(`div`,{className:`progress-track`,children:(0,M.jsx)(`div`,{className:`progress-bar ${e.tone}`,style:{width:`${e.value}%`}})})]})}function I(e){let[t,n]=(0,j.useState)({}),[r,i]=(0,j.useState)({}),[a,o]=(0,j.useState)({search:``,status:`all`,sort:`quota-desc`}),s=(0,j.useMemo)(()=>{let t=e.config?.profiles?[...e.config.profiles]:[],n=a.search.trim().toLowerCase(),r=t.filter(t=>{let r=[m(t,!0).toLowerCase(),t.accountId,t.profileId,t.email||``].join(` `).toLowerCase(),i=u(t),o=!!(e.codexAccountId&&t.accountId===e.codexAccountId);return n&&!r.includes(n)?!1:a.status===`active`?t.isActive||o:a.status===`healthy`?i.key===`healthy`:a.status===`warning`?i.key===`warning`:a.status===`exhausted`?i.key===`exhausted`:a.status===`expired`?i.key===`expired`:a.status===`invalid`?i.key===`invalid`:!0});return r.sort((t,n)=>{let r=f(t,e.codexAccountId)-f(n,e.codexAccountId);if(r!==0)return r;let i=v(n)-v(t);if(i!==0)return i;let o=x(n)-x(t);return o===0?a.sort===`latency-asc`?(n.quota?.capturedAt||0)-(t.quota?.capturedAt||0):a.sort===`expiry-asc`?(t.expiresAt||2**53-1)-(n.expiresAt||2**53-1):a.sort===`name-asc`?m(t,!0).localeCompare(m(n,!0),`zh-CN`):a.sort===`quota-asc`?100-l(n)-(100-l(t)):a.sort===`plan-desc`?v(n)-v(t):a.sort===`email-asc`?m(t,!0).localeCompare(m(n,!0)):l(n)-l(t):o}),r},[a,e.codexAccountId,e.config?.profiles]),c=Object.values(t).filter(Boolean).length;async function d(t,n){let r=await D(`/_gateway/admin/profiles/export`,{method:`POST`,headers:{"Content-Type":`application/json`},body:C(n?{profileIds:n}:{profileId:t})});E(`ai-zero-token-${n?`profiles-${n.length}`:t||`active`}.json`,r.profile),e.setStatus(n?`已导出 ${n.length} 个账号。`:`账号配置已导出。`)}async function p(t,n){if(!(t===`remove`&&!window.confirm(`确认删除 ${m(n,e.showEmails)}?`))){if((t===`activate`||t===`apply-codex`)&&g(n)){e.setStatus(`${m(n,e.showEmails)} 登录已失效,不能应用到${t===`activate`?`网关`:`Codex`}。`);return}if((t===`activate`||t===`apply-codex`)&&y(n)){let r=t===`activate`?`网关`:`Codex`;if(!window.confirm(`${m(n,e.showEmails)} 的额度看起来已耗尽,仍要应用到${r}吗?`))return}if(t===`export`){await d(n.profileId);return}e.setBusy(`profile:${t}:${n.profileId}`);try{let r=await D({activate:`/_gateway/admin/profiles/activate`,"apply-codex":`/_gateway/admin/codex/apply`,"sync-quota":`/_gateway/admin/profiles/sync-quota`,remove:`/_gateway/admin/profiles/remove`}[t],{method:`POST`,headers:{"Content-Type":`application/json`},body:C({profileId:n.profileId})});e.setConfig(`config`in r?r.config:r),e.setStatus(t===`activate`?`已应用到网关。`:t===`apply-codex`?`已应用到本机 Codex。`:t===`sync-quota`?`额度信息已同步。`:`账号已删除。`)}catch(t){e.setStatus(O(t))}finally{e.setBusy(null)}}}return(0,M.jsx)(N,{config:e.config,profiles:s,showEmails:e.showEmails,filter:a,selectedProfiles:t,expandedProfiles:r,selectedCount:c,busy:e.busy,onFilter:o,onSelect:(e,t)=>n(n=>({...n,[e]:t})),onToggle:e=>i(t=>({...t,[e]:!t[e]})),onAction:p,onLocate:()=>e.activeProfile&&document.querySelector(`[data-profile-card="${e.activeProfile.profileId}"]`)?.scrollIntoView({behavior:`smooth`,block:`center`}),onExportSelected:()=>{let n=Object.keys(t).filter(e=>t[e]);if(n.length===0){e.setStatus(`请先勾选要导出的账号。`);return}d(void 0,n).catch(t=>e.setStatus(t instanceof Error?t.message:String(t)))},onAddAccount:()=>e.setAccountModalOpen(!0),onRefreshStatus:()=>e.refreshConfig({runtime:!0}),onClearAccounts:()=>e.logout()})}export{I as AccountsPage};