ai-zero-token 2.0.8 → 2.0.9

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.
@@ -0,0 +1,8 @@
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{T as s,a as c,h as l,n as u,p as d}from"./profiles-iNTmJFRe.js";import{_ as f,n as ee,p,r as m}from"./index-BM5N4YUY.js";var h=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`}]]),g=e(n(),1),_=r();function v(e){return e===`ai-zero-token`?`ai-zero-token`:`openai`}function y(e){return e===`openai`?`openai`:`AI Zero Token`}function b(e){return e===`openai`?`保留 Codex 原生历史`:`新的 provider 历史`}function ie(e){return e===`openai`?`openai_base_url`:`[model_providers.ai-zero-token]`}function ae(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 x(e){try{return ae(e)}catch{return e.trim().replace(/\/+$/g,``)}}function S(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 C(e){let[t,n]=(0,g.useState)({defaultModel:``,proxyEnabled:!1,proxyUrl:``,proxyNoProxy:`localhost,127.0.0.1,::1`,autoSwitchEnabled:!1,autoSwitchExcludedProfileIds:[],quotaSyncConcurrency:`3`,freeAccountWebGenerationEnabled:!1,serverPort:`8787`}),[r,C]=(0,g.useState)(`local`),[w,T]=(0,g.useState)(`http://127.0.0.1:8787/codex/v1`),[E,D]=(0,g.useState)(!1),[ce,le]=(0,g.useState)(()=>new Set),[O,ue]=(0,g.useState)(``),[k,A]=(0,g.useState)(`openai`),[j,de]=(0,g.useState)(!1),[M,N]=(0,g.useState)(null),[fe,P]=(0,g.useState)(!1),F=(0,g.useRef)(null),I=ce.size>0;(0,g.useEffect)(()=>()=>{F.current&&window.clearTimeout(F.current)},[]),(0,g.useEffect)(()=>{!e.config||I||n(oe(e.config))},[e.config,I]),(0,g.useEffect)(()=>{if(!e.config||E)return;let t=S(e.config),n=e.config.codex.gatewayProvider?.baseUrl;T(n||t),C(n&&x(n)!==x(t)?`remote`:`local`)},[e.config,E]),(0,g.useEffect)(()=>{!e.config||j||A(v(e.config.codex.gatewayProvider?.providerId))},[e.config,j]);function L(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),L({autoSwitchExcludedProfileIds:Array.from(r)})}function R(t){let n=S(e.config);D(!0),C(t),t===`local`?T(n):w.trim()||T(n)}function me(){return r===`local`?S(e.config):w}function z(e){de(!0),A(e)}let B=(0,g.useMemo)(()=>new Set(t.autoSwitchExcludedProfileIds),[t.autoSwitchExcludedProfileIds]),V=(0,g.useMemo)(()=>{let t=O.trim().toLowerCase();return(e.config?.profiles||[]).filter(e=>!t||se(e).includes(t))},[O,e.config?.profiles]),he=e.config?.profiles.length||0,H=(e.config?.profiles||[]).filter(e=>B.has(e.profileId)).length,U=(e.config?.profiles||[]).filter(e=>!B.has(e.profileId)&&u(e).key===`ready`).length,ge=Math.max(0,he-H-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 c=n?.restart?`restart`:`settings`;e.setBusy(c);try{let t=await p(`/_gateway/admin/settings`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:s(o)});e.setConfig(t),le(new Set),n?.restart?(e.setStatus(`设置已保存,正在重启本地网关...`),await p(`/_gateway/admin/restart`,{method:`POST`}),e.setStatus(`本地网关正在重启,页面会自动恢复。`)):e.setStatus(`设置已保存。`)}catch(t){e.setStatus(m(t))}finally{e.setBusy(null)}}async function ve(){e.setBusy(`proxy`);try{let n=await p(`/_gateway/admin/settings/proxy-test`,{method:`POST`,headers:{"Content-Type":`application/json`},body:s({networkProxy:{enabled:t.proxyEnabled,url:t.proxyUrl,noProxy:t.proxyNoProxy}})});e.setStatus(`代理测试通过: HTTP ${n.status},耗时 ${n.elapsedMs} ms。`)}catch(t){e.setStatus(`代理测试失败: ${m(t)}`)}finally{e.setBusy(null)}}async function ye(){e.setBusy(`models`);try{let t=await p(`/_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(m(t))}finally{e.setBusy(null)}}async function be(){e.setBusy(`codex-share`),P(!1),N(null),F.current&&=(window.clearTimeout(F.current),null);try{let t=await p(`/_gateway/admin/share`);if(!t.primary){let n=t.lanReachable?`没有检测到可分享的局域网地址。请确认设备已连接 Wi-Fi 或局域网。`:`当前网关只允许本机访问,不能分享给局域网设备。请把网关监听地址从 ${t.serverHost} 改为 0.0.0.0 后重启。`;N({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&&(P(!0),F.current=window.setTimeout(()=>{P(!1),F.current=null},2e3)),N({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=m(t);N({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 p(`/_gateway/admin/desktop/restart-codex`,{method:`POST`}),e.setStatus(t.restartedStatus)}catch(n){e.setStatus(`${t.failedStatusPrefix}: ${m(n)}`)}return}e.setStatus(t.deferStatus)}async function xe(){e.setBusy(`codex-provider`);try{let t=k,n=y(t),r=ae(me()),i=e.config?.codex.gatewayProvider?.baseUrl,a=v(e.config?.codex.gatewayProvider?.providerId),o=!!(e.config?.codex.gatewayProvider?.active&&a!==t),c=!!(e.config?.codex.gatewayProvider?.active&&i&&x(i)!==r);if(e.config?.codex.gatewayProvider?.active&&!c&&!o){let r=await p(`/_gateway/admin/codex/remove-provider`,{method:`POST`,headers:{"Content-Type":`application/json`},body:s({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 l=!!e.config?.codex.gatewayProvider?.active,u=await p(`/_gateway/admin/codex/configure-provider`,{method:`POST`,headers:{"Content-Type":`application/json`},body:s({baseUrl:r,providerId:t})});u.config&&e.setConfig(u.config);let d=u.codexProvider.historyMigration?.migratedCount||0,f=u.codexProvider.historyMigration?.rolloutPatchedCount||0,ee=d>0?`,已迁移 ${d} 条历史记录${f>0?`,已修复 ${f} 个会话索引`:``}`:``;await W({config:u.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:`${l?`已更新`:`已写入`} ${n} 接管配置:${u.codexProvider.baseUrl}${ee}。重启 Codex 后生效。`,restartingStatus:`正在重启 Codex 客户端...`,restartedStatus:`${l?`已更新`:`已接管`} ${n} 请求,并已重启 Codex 客户端。`,failedStatusPrefix:`${l?`已更新`:`已接管`} ${n} 请求,但重启 Codex 失败`})}catch(t){e.setStatus(m(t))}finally{e.setBusy(null)}}let G=v(e.config?.codex.gatewayProvider?.providerId),K=y(G),q=ie(k),J=!!e.config?.codex.gatewayProvider?.active,Y=e.busy===`codex-provider`,X=e.busy===`codex-share`,Se=S(e.config),Z=x(r===`local`?Se:w),Q=e.config?.codex.gatewayProvider?.baseUrl||``,$=!!(J&&Q&&x(Q)!==Z),Ce=!!(J&&G!==k),we=[`btn-secondary`,`codex-provider-button`,Y?`is-busy`:J&&!$&&!Ce?`is-active`:`is-inactive`].join(` `),Te=Y?`处理中`:J&&!$&&!Ce?`解除 Codex 接管`:J?`更新接管配置`:`写入并接管`,Ee=J?K:`未接管`,De=J?`is-included`:`is-excluded`;return(0,_.jsxs)(`section`,{className:`settings-page`,children:[(0,_.jsx)(`div`,{className:`settings-page-head settings-page-head-actions-only`,children:(0,_.jsx)(`div`,{className:`settings-page-actions`,children:(0,_.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:ye,disabled:e.busy===`models`,children:[e.busy===`models`?(0,_.jsx)(f,{className:`spin`,size:16}):(0,_.jsx)(a,{size:16}),`同步 Codex 模型`]})})}),(0,_.jsxs)(`div`,{className:`settings-grid`,children:[(0,_.jsxs)(`section`,{className:`settings-section codex-provider-section`,children:[(0,_.jsxs)(`div`,{className:`codex-provider-head`,children:[(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`h4`,{children:`Codex 请求接管`}),(0,_.jsx)(`p`,{className:`hint`,children:`默认使用 openai 保留 Codex 原生历史;也可以切到 AI Zero Token,写入新的 provider 历史分组。接管地址既可以是本机网关,也可以是远程网关 URL。`})]}),(0,_.jsx)(`span`,{className:`count-pill ${De}`,children:Ee})]}),(0,_.jsxs)(`div`,{className:`codex-provider-mode-row`,children:[(0,_.jsxs)(`div`,{className:`codex-provider-mode-copy`,children:[(0,_.jsx)(`div`,{className:`codex-provider-mode-title`,children:`历史记录模式`}),(0,_.jsxs)(`p`,{className:`hint`,children:[y(k),` · `,b(k)]})]}),(0,_.jsxs)(`div`,{className:`codex-provider-mode-toggle`,role:`group`,"aria-label":`历史记录模式`,children:[(0,_.jsx)(`button`,{className:`codex-provider-mode-option ${k===`openai`?`is-active`:``}`,type:`button`,onClick:()=>z(`openai`),children:`openai`}),(0,_.jsx)(`button`,{className:`codex-provider-mode-option ${k===`ai-zero-token`?`is-active`:``}`,type:`button`,onClick:()=>z(`ai-zero-token`),children:`AI Zero Token`})]})]}),(0,_.jsxs)(`div`,{className:`codex-provider-controls`,children:[(0,_.jsxs)(`div`,{className:`codex-mode-toggle`,role:`group`,"aria-label":`Codex 网关模式`,children:[(0,_.jsxs)(`button`,{className:`codex-mode-option ${r===`local`?`is-active`:``}`,type:`button`,onClick:()=>R(`local`),children:[(0,_.jsx)(h,{size:16}),`本机网关`]}),(0,_.jsxs)(`button`,{className:`codex-mode-option ${r===`remote`?`is-active`:``}`,type:`button`,onClick:()=>R(`remote`),children:[(0,_.jsx)(i,{size:16}),`远程网关`]})]}),(0,_.jsxs)(`label`,{className:`field codex-url-field`,children:[(0,_.jsx)(`span`,{children:`Codex 网关 URL`}),(0,_.jsx)(`input`,{className:`input codex-url-input`,value:r===`local`?Se:w,onChange:e=>{D(!0),C(`remote`),T(e.target.value)},placeholder:`http://192.168.1.10:8787/codex/v1`,readOnly:r===`local`})]}),(0,_.jsxs)(`div`,{className:`codex-provider-actions`,children:[(0,_.jsxs)(`button`,{className:`btn-secondary share-gateway-button`,type:`button`,onClick:be,disabled:X,children:[X?(0,_.jsx)(f,{className:`spin`,size:16}):(0,_.jsx)(ne,{size:16}),X?`生成中`:fe?`已复制`:`复制代理配置`]}),(0,_.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>R(`local`),children:[(0,_.jsx)(h,{size:16}),`使用本机地址`]}),(0,_.jsxs)(`button`,{className:we,type:`button`,onClick:xe,disabled:Y,children:[Y?(0,_.jsx)(f,{className:`spin`,size:16}):J&&!$?(0,_.jsx)(re,{size:16}):(0,_.jsx)(te,{size:16}),Te]})]})]}),M?(0,_.jsxs)(`div`,{className:`share-gateway-feedback ${M.tone===`success`?`is-success`:`is-warning`}`,role:`status`,"aria-live":`polite`,children:[(0,_.jsx)(`strong`,{children:M.title}),(0,_.jsx)(`span`,{children:M.detail}),(0,_.jsxs)(`div`,{className:`share-gateway-config-list`,children:[M.codexUrl?(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`span`,{children:`Codex 远程网关 URL`}),(0,_.jsx)(`code`,{children:M.codexUrl})]}):null,M.baseUrl?(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`span`,{children:`OpenAI 兼容 Base URL`}),(0,_.jsx)(`code`,{children:M.baseUrl})]}):null,M.apiKey?(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`span`,{children:`API Key`}),(0,_.jsx)(`code`,{children:M.apiKey})]}):null]})]}):null,(0,_.jsxs)(`p`,{className:`hint`,children:[`可直接输入 IP:端口,系统会自动补全为 http://IP:端口/codex/v1。当前将写入 `,(0,_.jsx)(`code`,{children:q}),`:`,(0,_.jsx)(`code`,{children:Z||`-`})]}),(0,_.jsxs)(`div`,{className:`codex-provider-meta-strip`,children:[(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`span`,{children:`配置文件`}),(0,_.jsx)(`code`,{children:e.config?.codex.gatewayProvider.path||`~/.codex/config.toml`})]}),(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`span`,{children:`当前状态`}),(0,_.jsx)(`code`,{children:J?`${K} · ${b(G)}`:`未接管`})]}),(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`span`,{children:`写入目标`}),(0,_.jsx)(`code`,{children:q})]}),(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`span`,{children:`接管地址`}),(0,_.jsx)(`code`,{children:Q||`未写入受管配置`})]}),(0,_.jsxs)(`div`,{className:`is-warning`,children:[(0,_.jsx)(`span`,{children:`远程网关提示`}),(0,_.jsx)(`strong`,{children:`远程请求会消耗对方网关机器上保存的账号额度。`})]})]})]}),(0,_.jsxs)(`section`,{className:`settings-section`,children:[(0,_.jsx)(`h4`,{children:`模型`}),(0,_.jsxs)(`label`,{className:`field`,children:[(0,_.jsx)(`span`,{children:`默认文本模型`}),(0,_.jsx)(`select`,{className:`control`,value:t.defaultModel,onChange:e=>L({defaultModel:e.target.value}),children:(e.config?.models||[]).map(e=>(0,_.jsx)(`option`,{value:e.id,children:e.id},e.id))})]}),(0,_.jsxs)(`p`,{className:`hint`,children:[`模型列表来源:`,e.config?.modelCatalog.source||`-`,`,共 `,e.config?.modelCatalog.modelCount||0,` 个。`]})]}),(0,_.jsxs)(`section`,{className:`settings-section free-image-section`,children:[(0,_.jsx)(`h4`,{children:`Free 账号生图`}),(0,_.jsxs)(`label`,{className:`switch-line`,children:[(0,_.jsx)(`input`,{type:`checkbox`,checked:t.freeAccountWebGenerationEnabled,onChange:e=>L({freeAccountWebGenerationEnabled:e.target.checked})}),(0,_.jsx)(`span`,{children:`允许 Free 账号使用 ChatGPT 网页链路生图`})]}),(0,_.jsx)(`p`,{className:`hint`,children:`关闭时,Free 账号生图会继续走原先 Codex Responses 图片工具链路,由上游决定是否可用。`}),(0,_.jsxs)(`p`,{className:`free-image-warning`,children:[(0,_.jsx)(`strong`,{children:`封号风险:`}),`该能力不是官方 API 标准流程,使用 Free 账号生图存在账号风控或封号风险。`,(0,_.jsx)(`strong`,{children:`额度较少:`}),`Free 额度通常较少,当前经验值大约 8 张,实际以上游账号为准。`]})]}),(0,_.jsxs)(`section`,{className:`settings-section`,children:[(0,_.jsx)(`h4`,{children:`上游代理`}),(0,_.jsxs)(`label`,{className:`switch-line`,children:[(0,_.jsx)(`input`,{type:`checkbox`,checked:t.proxyEnabled,onChange:e=>L({proxyEnabled:e.target.checked})}),(0,_.jsx)(`span`,{children:`启用 OAuth、模型刷新和接口转发代理`})]}),(0,_.jsxs)(`label`,{className:`field`,children:[(0,_.jsx)(`span`,{children:`代理地址`}),(0,_.jsx)(`input`,{className:`input`,value:t.proxyUrl,onChange:e=>L({proxyUrl:e.target.value}),placeholder:`http://127.0.0.1:7890`})]}),(0,_.jsxs)(`label`,{className:`field`,children:[(0,_.jsx)(`span`,{children:`No Proxy`}),(0,_.jsx)(`input`,{className:`input`,value:t.proxyNoProxy,onChange:e=>L({proxyNoProxy:e.target.value})})]}),(0,_.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:ve,disabled:e.busy===`proxy`,children:`测试代理`})]}),(0,_.jsxs)(`section`,{className:`settings-section`,children:[(0,_.jsx)(`h4`,{children:`端口`}),(0,_.jsxs)(`label`,{className:`field`,children:[(0,_.jsx)(`span`,{children:`网关端口`}),(0,_.jsx)(`input`,{className:`input`,inputMode:`numeric`,type:`number`,min:1,max:65535,value:t.serverPort,onChange:e=>L({serverPort:e.target.value})})]}),(0,_.jsx)(`p`,{className:`hint`,children:`修改后重启本地网关生效,桌面窗口不会退出。若端口被占用,启动时会自动顺延到下一个可用端口。`})]}),(0,_.jsxs)(`section`,{className:`settings-section`,children:[(0,_.jsx)(`h4`,{children:`账号运行策略`}),(0,_.jsxs)(`label`,{className:`switch-line`,children:[(0,_.jsx)(`input`,{type:`checkbox`,checked:t.autoSwitchEnabled,onChange:e=>L({autoSwitchEnabled:e.target.checked})}),(0,_.jsx)(`span`,{children:`当前 API 账号额度耗尽后自动切换到下一个仍有额度的账号`})]}),(0,_.jsxs)(`label`,{className:`field`,children:[(0,_.jsx)(`span`,{children:`全局额度刷新并发数`}),(0,_.jsx)(`input`,{className:`input`,inputMode:`numeric`,max:32,min:1,type:`number`,value:t.quotaSyncConcurrency,onChange:e=>L({quotaSyncConcurrency:e.target.value})})]}),(0,_.jsx)(`p`,{className:`hint`,children:`手动刷新全部账号额度时使用,默认 3。账号很多可以调高,遇到限流或失败增多时调低。`}),(0,_.jsx)(`p`,{className:`hint`,children:e.status})]}),(0,_.jsxs)(`section`,{className:`settings-section auto-switch-exclusion-section`,children:[(0,_.jsxs)(`div`,{className:`auto-switch-exclusion-head`,children:[(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`h4`,{children:`不参与自动轮换名单`}),(0,_.jsx)(`p`,{className:`hint`,children:`勾选表示手动排除。登录不可用或额度耗尽的账号即使未勾选,也不会被实际自动轮换选中。`})]}),(0,_.jsxs)(`div`,{className:`auto-switch-counts`,"aria-label":`自动轮换账号统计`,children:[(0,_.jsxs)(`span`,{className:`count-pill is-included`,children:[`可轮换 `,U,` 个`]}),(0,_.jsxs)(`span`,{className:`count-pill is-blocked`,children:[`不可用 `,ge,` 个`]}),(0,_.jsxs)(`span`,{className:`count-pill is-excluded`,children:[`手动排除 `,H,` 个`]})]})]}),(0,_.jsxs)(`label`,{className:`auto-switch-search`,children:[(0,_.jsx)(o,{size:16}),(0,_.jsx)(`input`,{value:O,onChange:e=>ue(e.target.value),placeholder:`搜索邮箱、账号 ID 或 Profile ID`})]}),(0,_.jsx)(`div`,{className:`auto-switch-profile-list`,children:V.length===0?(0,_.jsx)(`div`,{className:`auto-switch-empty`,children:`还没有匹配的账号。`}):V.map(t=>{let n=B.has(t.profileId),r=u(t),i=d(t),a=!!(e.config?.codex.accountId&&e.config.codex.accountId===t.accountId),o=r.key===`ready`?``:r.label,s=n?`is-excluded`:r.key===`ready`?`is-included`:`is-blocked`,f=n?`手动排除`:r.label;return(0,_.jsxs)(`label`,{className:`auto-switch-profile-row ${n?`is-excluded`:``}`,children:[(0,_.jsx)(`input`,{type:`checkbox`,checked:n,onChange:e=>pe(t.profileId,e.target.checked)}),(0,_.jsxs)(`span`,{className:`auto-switch-profile-main`,children:[(0,_.jsx)(`strong`,{children:l(t,e.showEmails)}),(0,_.jsxs)(`span`,{children:[c(t),` · `,i.label,t.isActive?` · 当前 API 使用中`:``,a?` · Codex 使用中`:``,o?` · ${o}`:``]})]}),(0,_.jsx)(`span`,{className:`auto-switch-state-pill ${s}`,children:f})]},t.profileId)})})]}),(0,_.jsxs)(`section`,{className:`settings-section`,children:[(0,_.jsx)(`h4`,{children:`显示`}),(0,_.jsxs)(`label`,{className:`switch-line`,children:[(0,_.jsx)(`input`,{type:`checkbox`,checked:e.showEmails,onChange:t=>e.setShowEmails(t.target.checked)}),(0,_.jsx)(`span`,{children:`脱敏模式`})]}),(0,_.jsx)(`p`,{className:`hint`,children:`开启后账号邮箱将以脱敏形式展示。`})]})]}),(0,_.jsxs)(`div`,{className:`settings-page-actions settings-page-footer-actions`,children:[(0,_.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>void _e(),disabled:e.busy===`settings`||e.busy===`restart`||!I,children:`保存设置`}),(0,_.jsx)(`button`,{className:`btn-primary`,type:`button`,onClick:()=>void _e({restart:!0}),disabled:e.busy===`settings`||e.busy===`restart`||!I||!e.config?.restartSupported,children:`保存并重启网关`})]})]})}export{C as SettingsPage};
@@ -1,3 +1,3 @@
1
- import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./upload-CwXb7Q1b.js";import{t as a}from"./zap-B4_oDbCp.js";import{S as o,m as s,w as c,x as l}from"./profiles-C5SmQvju.js";import{_ as u,a as d,b as f,c as p,i as m,l as h,n as g,o as _,p as v,r as y,s as b,t as x,u as S}from"./index-_5Ny0cZf.js";var C=t(`rotate-ccw`,[[`path`,{d:`M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8`,key:`1357e3`}],[`path`,{d:`M3 3v5h5`,key:`1xhq8a`}]]),w=e(n(),1),T=r();function E(e){let t=e.endpoint.startsWith(`/v1/images/`);function n(t){let n=t.currentTarget.files?.[0];t.currentTarget.value=``,n&&e.onImageUpload(n,e.imageUploadMode).catch(()=>void 0)}return(0,T.jsxs)(`section`,{className:`card tester-card`,id:`tester`,children:[(0,T.jsxs)(`div`,{className:`section-head compact`,children:[(0,T.jsxs)(`div`,{children:[(0,T.jsx)(`h2`,{children:`快速测试`}),(0,T.jsx)(`p`,{children:`页面直接调用当前网关暴露的 OpenAI 风格接口。`})]}),(0,T.jsx)(`span`,{className:`badge brand`,children:e.busy===`test`?`请求中`:`准备就绪`})]}),(0,T.jsx)(`div`,{className:`tester-tabs`,children:e.endpoints.map(t=>(0,T.jsx)(`button`,{className:`tab-btn ${e.endpoint===t.path?`is-active`:``}`,type:`button`,onClick:()=>e.onEndpoint(t.path),children:S[t.path]||t.path},t.path))}),(0,T.jsxs)(`div`,{className:`tester-workbench`,children:[(0,T.jsxs)(`div`,{className:`tester-pane tester-request-pane`,children:[(0,T.jsxs)(`label`,{className:`field`,children:[(0,T.jsx)(`span`,{children:`接口`}),(0,T.jsx)(`select`,{className:`control`,value:e.endpoint,onChange:t=>e.onEndpoint(t.target.value),children:e.endpoints.map(e=>(0,T.jsxs)(`option`,{value:e.path,children:[e.method,` `,e.path]},e.path))})]}),(0,T.jsxs)(`div`,{className:`tester-copy-row tester-copy-row-top`,children:[(0,T.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onCopyRequest,children:[(0,T.jsx)(f,{size:16}),`复制请求`]}),(0,T.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onCopyResponse,children:[(0,T.jsx)(f,{size:16}),`复制响应`]}),(0,T.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onCopyTiming,children:[(0,T.jsx)(f,{size:16}),`复制日志`]}),(0,T.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onResetExample,children:[(0,T.jsx)(C,{size:16}),`重置示例`]})]}),(0,T.jsxs)(`label`,{className:`field tester-body-field`,children:[(0,T.jsx)(`span`,{children:`请求体 JSON`}),(0,T.jsx)(`textarea`,{className:`textarea tester-textarea`,value:e.requestBody,onChange:t=>e.onRequestBody(t.target.value),disabled:e.activeEndpoint.method===`GET`,spellCheck:!1})]}),e.endpoint===`/v1/images/edits`&&(0,T.jsxs)(`div`,{className:`edit-upload-row`,children:[(0,T.jsxs)(`div`,{className:`edit-upload-mode`,role:`group`,"aria-label":`图片写入方式`,children:[(0,T.jsx)(`span`,{children:`写入方式`}),(0,T.jsxs)(`div`,{className:`edit-upload-toggle`,children:[(0,T.jsx)(`button`,{className:`tab-btn ${e.imageUploadMode===`base64`?`is-active`:``}`,type:`button`,onClick:()=>e.onImageUploadMode(`base64`),children:`Base64`}),(0,T.jsx)(`button`,{className:`tab-btn ${e.imageUploadMode===`image-bed`?`is-active`:``}`,type:`button`,onClick:()=>e.onImageUploadMode(`image-bed`),children:`图床`})]})]}),(0,T.jsxs)(`label`,{className:`btn-secondary upload-btn`,title:e.imageUploadMode===`base64`?`上传图片并写入 base64 data URL`:`上传图片到图床并写入公网链接`,children:[e.imageUploadMode===`image-bed`&&e.busy===`image-bed-upload`?(0,T.jsx)(u,{className:`spin`,size:16}):(0,T.jsx)(i,{size:16}),e.imageUploadMode===`base64`?`上传并写入 Base64`:`上传并写入图床链接`,(0,T.jsx)(`input`,{type:`file`,accept:`image/*`,onChange:n})]}),(0,T.jsxs)(`span`,{children:[`目标字段:images[0].image_url · `,e.imageUploadMode===`base64`?`直接写入 data URL`:`先上传图床再写入公网链接`]})]}),(0,T.jsx)(`p`,{className:`hint`,children:t?e.capability.detail:e.activeEndpoint.description||`GET /v1/models 无需请求体。`}),(0,T.jsxs)(`div`,{className:`tester-actions-bar`,children:[(0,T.jsx)(`div`,{className:`tester-actions-group`,children:(0,T.jsx)(`div`,{className:`example-row`,children:p.map(t=>(0,T.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>e.onEndpoint(t),disabled:!e.endpoints.some(e=>e.path===t),children:[`示例 `,S[t]||t]},t))})}),(0,T.jsxs)(`button`,{className:`btn-primary`,type:`button`,onClick:e.onRun,disabled:e.busy===`test`||t&&!e.config?.profile,children:[e.busy===`test`?(0,T.jsx)(u,{className:`spin`,size:16}):(0,T.jsx)(a,{size:16}),`发送请求`]})]})]}),(0,T.jsxs)(`div`,{className:`tester-pane tester-response-pane`,children:[(0,T.jsxs)(`div`,{className:`tester-result-head`,children:[(0,T.jsxs)(`div`,{className:`tester-result-tabs`,children:[(0,T.jsx)(`button`,{className:`tab-btn ${e.resultTab===`response`?`is-active`:``}`,type:`button`,onClick:()=>e.onResultTab(`response`),children:`响应 JSON`}),(0,T.jsx)(`button`,{className:`tab-btn ${e.resultTab===`timing`?`is-active`:``}`,type:`button`,onClick:()=>e.onResultTab(`timing`),children:`耗时日志`}),(0,T.jsx)(`button`,{className:`tab-btn ${e.resultTab===`preview`?`is-active`:``}`,type:`button`,onClick:()=>e.onResultTab(`preview`),children:`图片预览`})]}),(0,T.jsx)(`p`,{className:`status-inline`,children:e.status})]}),e.resultTab===`response`&&(0,T.jsx)(`pre`,{className:`pre`,children:e.responseBody}),e.resultTab===`timing`&&(0,T.jsx)(`pre`,{className:`pre`,children:e.timingBody}),e.resultTab===`preview`&&(0,T.jsx)(`div`,{className:`preview-panel`,children:e.previewImages.length===0?(0,T.jsx)(`div`,{className:`preview-empty`,children:`图片结果会显示在这里。点击缩略图可查看大图。`}):(0,T.jsx)(`div`,{className:`preview-grid`,children:e.previewImages.map(t=>(0,T.jsxs)(`figure`,{className:`preview-card`,children:[(0,T.jsx)(`button`,{type:`button`,onClick:()=>e.onPreview({src:t.src,meta:t.meta,filename:t.filename}),children:(0,T.jsx)(`img`,{src:t.src,alt:t.meta})}),(0,T.jsx)(`figcaption`,{children:t.meta}),(0,T.jsx)(`div`,{className:`preview-actions`,children:(0,T.jsx)(`a`,{href:t.src,download:t.filename,children:`下载`})})]},t.filename))})})]})]})]})}function D(e){let[t,n]=(0,w.useState)(`/v1/models`),[r,i]=(0,w.useState)(``),[a,u]=(0,w.useState)(`等待请求...`),[f,S]=(0,w.useState)(`等待请求...`),[C,D]=(0,w.useState)(`response`),[O,k]=(0,w.useState)([]),[A,j]=(0,w.useState)(`base64`),M=(0,w.useMemo)(()=>[...e.config?.supportedEndpoints||[]].filter(e=>p.includes(e.path)).sort(h),[e.config?.supportedEndpoints]),N=(0,w.useMemo)(()=>M.find(e=>e.path===t)||M[0]||{method:`GET`,path:`/v1/models`,description:``},[t,M]);(0,w.useEffect)(()=>{if(!e.config)return;let r=p.find(t=>e.config?.supportedEndpoints.some(e=>e.path===t)),a=e.config.supportedEndpoints.some(e=>e.path===t)?t:r||`/v1/models`;n(a),i(x(a,e.config.settings.defaultModel))},[e.config]);function P(t){n(t),i(x(t,e.config?.settings.defaultModel||`gpt-5.4`)),k([])}function F(){P(t)}function I(){g(r||x(t,e.config?.settings.defaultModel||`gpt-5.4`)).then(t=>e.setStatus(t?`请求体已复制。`:`请求体复制失败。`)).catch(()=>e.setStatus(`请求体复制失败。`))}function L(){g(a).then(t=>e.setStatus(t?`响应内容已复制。`:`响应内容复制失败。`)).catch(()=>e.setStatus(`响应内容复制失败。`))}function R(){g(f).then(t=>e.setStatus(t?`耗时日志已复制。`:`耗时日志复制失败。`)).catch(()=>e.setStatus(`耗时日志复制失败。`))}async function z(){let t=N,n=performance.now(),i=[];e.setBusy(`test`),D(`response`),u(`请求发送中...`),S(`请求发送中...`),k([]);try{let a=null,o={method:t.method,headers:{}};if(t.method!==`GET`){let e=performance.now();a=r.trim()?JSON.parse(r):{},i.push(`解析请求体: ${l(performance.now()-e)}`),o.headers[`Content-Type`]=`application/json`,o.body=c(a)}let d=performance.now(),f=await fetch(t.path,o);i.push(`等待响应头: ${l(performance.now()-d)}`);let p=performance.now(),h=await f.text();i.push(`读取响应体: ${l(performance.now()-p)}`);let g=performance.now(),_=h;try{_=h?JSON.parse(h):null}catch{_=h}i.push(`解析响应体: ${l(performance.now()-g)}`);let v=m(_);k(v),v.length>0&&D(`preview`),u(typeof _==`string`?_:c(b(_))),S([`${t.method} ${t.path}`,`HTTP 状态: ${f.status} ${f.statusText}`,...i].join(`
2
- `)),e.setStatus(`${f.ok?`成功`:`失败`}: HTTP ${f.status} ${t.method} ${t.path}`),e.setRequestLogs(r=>[{id:crypto.randomUUID(),time:Date.now(),method:t.method,endpoint:t.path,account:s(e.config?.profile,e.showEmails),model:typeof a==`object`&&a&&`model`in a?String(a.model||e.config?.settings.defaultModel||`-`):e.config?.settings.defaultModel||`-`,statusCode:f.status,durationMs:performance.now()-n,source:`管理台`},...r].slice(0,20)),e.config?.profile&&e.refreshConfig({silent:!0}).catch(()=>void 0)}catch(n){let r=y(n);u(r),S([`${t.method} ${t.path}(失败)`,...i,`错误: ${r}`].join(`
1
+ import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./upload-CwXb7Q1b.js";import{t as a}from"./zap-B4_oDbCp.js";import{C as o,S as s,T as c,h as l}from"./profiles-iNTmJFRe.js";import{_ as u,a as d,b as f,c as p,i as m,l as h,n as g,o as _,p as v,r as y,s as b,t as x,u as S}from"./index-BM5N4YUY.js";var C=t(`rotate-ccw`,[[`path`,{d:`M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8`,key:`1357e3`}],[`path`,{d:`M3 3v5h5`,key:`1xhq8a`}]]),w=e(n(),1),T=r();function E(e){let t=e.endpoint.startsWith(`/v1/images/`);function n(t){let n=t.currentTarget.files?.[0];t.currentTarget.value=``,n&&e.onImageUpload(n,e.imageUploadMode).catch(()=>void 0)}return(0,T.jsxs)(`section`,{className:`card tester-card`,id:`tester`,children:[(0,T.jsxs)(`div`,{className:`section-head compact`,children:[(0,T.jsxs)(`div`,{children:[(0,T.jsx)(`h2`,{children:`快速测试`}),(0,T.jsx)(`p`,{children:`页面直接调用当前网关暴露的 OpenAI 风格接口。`})]}),(0,T.jsx)(`span`,{className:`badge brand`,children:e.busy===`test`?`请求中`:`准备就绪`})]}),(0,T.jsx)(`div`,{className:`tester-tabs`,children:e.endpoints.map(t=>(0,T.jsx)(`button`,{className:`tab-btn ${e.endpoint===t.path?`is-active`:``}`,type:`button`,onClick:()=>e.onEndpoint(t.path),children:S[t.path]||t.path},t.path))}),(0,T.jsxs)(`div`,{className:`tester-workbench`,children:[(0,T.jsxs)(`div`,{className:`tester-pane tester-request-pane`,children:[(0,T.jsxs)(`label`,{className:`field`,children:[(0,T.jsx)(`span`,{children:`接口`}),(0,T.jsx)(`select`,{className:`control`,value:e.endpoint,onChange:t=>e.onEndpoint(t.target.value),children:e.endpoints.map(e=>(0,T.jsxs)(`option`,{value:e.path,children:[e.method,` `,e.path]},e.path))})]}),(0,T.jsxs)(`div`,{className:`tester-copy-row tester-copy-row-top`,children:[(0,T.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onCopyRequest,children:[(0,T.jsx)(f,{size:16}),`复制请求`]}),(0,T.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onCopyResponse,children:[(0,T.jsx)(f,{size:16}),`复制响应`]}),(0,T.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onCopyTiming,children:[(0,T.jsx)(f,{size:16}),`复制日志`]}),(0,T.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onResetExample,children:[(0,T.jsx)(C,{size:16}),`重置示例`]})]}),(0,T.jsxs)(`label`,{className:`field tester-body-field`,children:[(0,T.jsx)(`span`,{children:`请求体 JSON`}),(0,T.jsx)(`textarea`,{className:`textarea tester-textarea`,value:e.requestBody,onChange:t=>e.onRequestBody(t.target.value),disabled:e.activeEndpoint.method===`GET`,spellCheck:!1})]}),e.endpoint===`/v1/images/edits`&&(0,T.jsxs)(`div`,{className:`edit-upload-row`,children:[(0,T.jsxs)(`div`,{className:`edit-upload-mode`,role:`group`,"aria-label":`图片写入方式`,children:[(0,T.jsx)(`span`,{children:`写入方式`}),(0,T.jsxs)(`div`,{className:`edit-upload-toggle`,children:[(0,T.jsx)(`button`,{className:`tab-btn ${e.imageUploadMode===`base64`?`is-active`:``}`,type:`button`,onClick:()=>e.onImageUploadMode(`base64`),children:`Base64`}),(0,T.jsx)(`button`,{className:`tab-btn ${e.imageUploadMode===`image-bed`?`is-active`:``}`,type:`button`,onClick:()=>e.onImageUploadMode(`image-bed`),children:`图床`})]})]}),(0,T.jsxs)(`label`,{className:`btn-secondary upload-btn`,title:e.imageUploadMode===`base64`?`上传图片并写入 base64 data URL`:`上传图片到图床并写入公网链接`,children:[e.imageUploadMode===`image-bed`&&e.busy===`image-bed-upload`?(0,T.jsx)(u,{className:`spin`,size:16}):(0,T.jsx)(i,{size:16}),e.imageUploadMode===`base64`?`上传并写入 Base64`:`上传并写入图床链接`,(0,T.jsx)(`input`,{type:`file`,accept:`image/*`,onChange:n})]}),(0,T.jsxs)(`span`,{children:[`目标字段:images[0].image_url · `,e.imageUploadMode===`base64`?`直接写入 data URL`:`先上传图床再写入公网链接`]})]}),(0,T.jsx)(`p`,{className:`hint`,children:t?e.capability.detail:e.activeEndpoint.description||`GET /v1/models 无需请求体。`}),(0,T.jsxs)(`div`,{className:`tester-actions-bar`,children:[(0,T.jsx)(`div`,{className:`tester-actions-group`,children:(0,T.jsx)(`div`,{className:`example-row`,children:p.map(t=>(0,T.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>e.onEndpoint(t),disabled:!e.endpoints.some(e=>e.path===t),children:[`示例 `,S[t]||t]},t))})}),(0,T.jsxs)(`button`,{className:`btn-primary`,type:`button`,onClick:e.onRun,disabled:e.busy===`test`||t&&!e.config?.profile,children:[e.busy===`test`?(0,T.jsx)(u,{className:`spin`,size:16}):(0,T.jsx)(a,{size:16}),`发送请求`]})]})]}),(0,T.jsxs)(`div`,{className:`tester-pane tester-response-pane`,children:[(0,T.jsxs)(`div`,{className:`tester-result-head`,children:[(0,T.jsxs)(`div`,{className:`tester-result-tabs`,children:[(0,T.jsx)(`button`,{className:`tab-btn ${e.resultTab===`response`?`is-active`:``}`,type:`button`,onClick:()=>e.onResultTab(`response`),children:`响应 JSON`}),(0,T.jsx)(`button`,{className:`tab-btn ${e.resultTab===`timing`?`is-active`:``}`,type:`button`,onClick:()=>e.onResultTab(`timing`),children:`耗时日志`}),(0,T.jsx)(`button`,{className:`tab-btn ${e.resultTab===`preview`?`is-active`:``}`,type:`button`,onClick:()=>e.onResultTab(`preview`),children:`图片预览`})]}),(0,T.jsx)(`p`,{className:`status-inline`,children:e.status})]}),e.resultTab===`response`&&(0,T.jsx)(`pre`,{className:`pre`,children:e.responseBody}),e.resultTab===`timing`&&(0,T.jsx)(`pre`,{className:`pre`,children:e.timingBody}),e.resultTab===`preview`&&(0,T.jsx)(`div`,{className:`preview-panel`,children:e.previewImages.length===0?(0,T.jsx)(`div`,{className:`preview-empty`,children:`图片结果会显示在这里。点击缩略图可查看大图。`}):(0,T.jsx)(`div`,{className:`preview-grid`,children:e.previewImages.map(t=>(0,T.jsxs)(`figure`,{className:`preview-card`,children:[(0,T.jsx)(`button`,{type:`button`,onClick:()=>e.onPreview({src:t.src,meta:t.meta,filename:t.filename}),children:(0,T.jsx)(`img`,{src:t.src,alt:t.meta})}),(0,T.jsx)(`figcaption`,{children:t.meta}),(0,T.jsx)(`div`,{className:`preview-actions`,children:(0,T.jsx)(`a`,{href:t.src,download:t.filename,children:`下载`})})]},t.filename))})})]})]})]})}function D(e){let[t,n]=(0,w.useState)(`/v1/models`),[r,i]=(0,w.useState)(``),[a,u]=(0,w.useState)(`等待请求...`),[f,S]=(0,w.useState)(`等待请求...`),[C,D]=(0,w.useState)(`response`),[O,k]=(0,w.useState)([]),[A,j]=(0,w.useState)(`base64`),M=(0,w.useMemo)(()=>[...e.config?.supportedEndpoints||[]].filter(e=>p.includes(e.path)).sort(h),[e.config?.supportedEndpoints]),N=(0,w.useMemo)(()=>M.find(e=>e.path===t)||M[0]||{method:`GET`,path:`/v1/models`,description:``},[t,M]);(0,w.useEffect)(()=>{if(!e.config)return;let r=p.find(t=>e.config?.supportedEndpoints.some(e=>e.path===t)),a=e.config.supportedEndpoints.some(e=>e.path===t)?t:r||`/v1/models`;n(a),i(x(a,e.config.settings.defaultModel))},[e.config]);function P(t){n(t),i(x(t,e.config?.settings.defaultModel||`gpt-5.4`)),k([])}function F(){P(t)}function I(){g(r||x(t,e.config?.settings.defaultModel||`gpt-5.4`)).then(t=>e.setStatus(t?`请求体已复制。`:`请求体复制失败。`)).catch(()=>e.setStatus(`请求体复制失败。`))}function L(){g(a).then(t=>e.setStatus(t?`响应内容已复制。`:`响应内容复制失败。`)).catch(()=>e.setStatus(`响应内容复制失败。`))}function R(){g(f).then(t=>e.setStatus(t?`耗时日志已复制。`:`耗时日志复制失败。`)).catch(()=>e.setStatus(`耗时日志复制失败。`))}async function z(){let t=N,n=performance.now(),i=[];e.setBusy(`test`),D(`response`),u(`请求发送中...`),S(`请求发送中...`),k([]);try{let a=null,o={method:t.method,headers:{}};if(t.method!==`GET`){let e=performance.now();a=r.trim()?JSON.parse(r):{},i.push(`解析请求体: ${s(performance.now()-e)}`),o.headers[`Content-Type`]=`application/json`,o.body=c(a)}let d=performance.now(),f=await fetch(t.path,o);i.push(`等待响应头: ${s(performance.now()-d)}`);let p=performance.now(),h=await f.text();i.push(`读取响应体: ${s(performance.now()-p)}`);let g=performance.now(),_=h;try{_=h?JSON.parse(h):null}catch{_=h}i.push(`解析响应体: ${s(performance.now()-g)}`);let v=m(_);k(v),v.length>0&&D(`preview`),u(typeof _==`string`?_:c(b(_))),S([`${t.method} ${t.path}`,`HTTP 状态: ${f.status} ${f.statusText}`,...i].join(`
2
+ `)),e.setStatus(`${f.ok?`成功`:`失败`}: HTTP ${f.status} ${t.method} ${t.path}`),e.setRequestLogs(r=>[{id:crypto.randomUUID(),time:Date.now(),method:t.method,endpoint:t.path,account:l(e.config?.profile,e.showEmails),model:typeof a==`object`&&a&&`model`in a?String(a.model||e.config?.settings.defaultModel||`-`):e.config?.settings.defaultModel||`-`,statusCode:f.status,durationMs:performance.now()-n,source:`管理台`},...r].slice(0,20)),e.config?.profile&&e.refreshConfig({silent:!0}).catch(()=>void 0)}catch(n){let r=y(n);u(r),S([`${t.method} ${t.path}(失败)`,...i,`错误: ${r}`].join(`
3
3
  `)),e.setStatus(`请求失败。`)}finally{e.setBusy(null)}}async function B(t,n){if(!t.type.startsWith(`image/`)){e.setStatus(`请选择图片文件。`);return}try{if(n===`image-bed`){e.setBusy(`image-bed-upload`);let n=await _(t);i(d(r,(await v(`/_gateway/image-bed/upload`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({filename:t.name,dataUrl:n})})).url,e.config?.settings.defaultModel||`gpt-image-2`)),e.setStatus(`已上传到图床并写入公网链接(${t.name},${o(t.size)})。`);return}i(d(r,await _(t),e.config?.settings.defaultModel||`gpt-image-2`)),e.setStatus(`已将 ${t.name} 转成 base64 data URL,并写入请求体 images[0].image_url(${o(t.size)})。`)}catch(t){e.setStatus(`图片写入失败: ${y(t)}`)}finally{n===`image-bed`&&e.setBusy(null)}}return(0,T.jsx)(E,{config:e.config,endpoints:M,activeEndpoint:N,endpoint:t,requestBody:r,responseBody:a,timingBody:f,resultTab:C,status:e.status,busy:e.busy,previewImages:O,capability:e.capability,imageUploadMode:A,onEndpoint:P,onRequestBody:i,onResultTab:D,onRun:z,onResetExample:F,onCopyRequest:I,onCopyResponse:L,onCopyTiming:R,onImageUploadMode:j,onPreview:e.setPreviewImage,onImageUpload:B})}export{D as TesterPage};
@@ -1 +1 @@
1
- import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./clock-3-BzDANsVk.js";import{t as a}from"./refresh-cw-CAAH2rqe.js";import{t as o}from"./zap-B4_oDbCp.js";import{C as s,x as c}from"./profiles-C5SmQvju.js";import{S as l,_ as u,p as d,r as f}from"./index-_5Ny0cZf.js";import{t as p}from"./StatCard-7TEzqn2i.js";var m=t(`archive`,[[`rect`,{width:`20`,height:`5`,x:`2`,y:`3`,rx:`1`,key:`1wp1u1`}],[`path`,{d:`M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8`,key:`1s80jp`}],[`path`,{d:`M10 12h4`,key:`a56b0p`}]]),h=t(`camera`,[[`path`,{d:`M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z`,key:`18u6gg`}],[`circle`,{cx:`12`,cy:`13`,r:`3`,key:`1vg3eu`}]]),g=t(`database`,[[`ellipse`,{cx:`12`,cy:`5`,rx:`9`,ry:`3`,key:`msslwz`}],[`path`,{d:`M3 5V19A9 3 0 0 0 21 19V5`,key:`1wlel7`}],[`path`,{d:`M3 12A9 3 0 0 0 21 12`,key:`mv7ke4`}]]),_=t(`dollar-sign`,[[`line`,{x1:`12`,x2:`12`,y1:`2`,y2:`22`,key:`7eqyqh`}],[`path`,{d:`M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6`,key:`1b0p4s`}]]),v=t(`sigma`,[[`path`,{d:`M18 7V5a1 1 0 0 0-1-1H6.5a.5.5 0 0 0-.4.8l4.5 6a2 2 0 0 1 0 2.4l-4.5 6a.5.5 0 0 0 .4.8H17a1 1 0 0 0 1-1v-2`,key:`wuwx1p`}]]),y=e(n(),1),b=r();function x(){return{requestCount:0,successCount:0,failureCount:0,inputTokens:0,uncachedInputTokens:0,outputTokens:0,totalTokens:0,cacheCreationTokens:0,cacheReadTokens:0,inputCostUsd:0,outputCostUsd:0,cacheCreationCostUsd:0,cacheReadCostUsd:0,estimatedCostUsd:0,unknownTokenCount:0,unknownTokenStatusCounts:{},imageCount:0,totalDurationMs:0,averageDurationMs:0,p95DurationMs:0,durationBuckets:{}}}function S(e){return new Intl.NumberFormat(`zh-CN`).format(Math.round(e||0))}function C(e){return!Number.isFinite(e)||e<=0?`0`:e>=1e6?`${(e/1e6).toFixed(e>=1e7?1:2)}M`:e>=1e4?`${(e/1e3).toFixed(e>=1e5?0:1)}K`:S(e)}function w(e){return!Number.isFinite(e)||e<=0?`$0.00`:e<.01?`$${e.toFixed(5)}`:e<1?`$${e.toFixed(4)}`:`$${e.toFixed(2)}`}function T(e){return`${S(e.successCount)} 成功 / ${S(e.failureCount)} 失败`}function E(e){let t=e.inputTokens||0,n=e.cacheReadTokens||0;return t<=0||n<=0?`缓存命中 0%`:`缓存命中 ${Math.min(100,n/t*100).toFixed(1)}%`}function D(e){return e===`captured`?`已捕获用量`:e===`missing_terminal`?`缺少终态事件`:e===`terminal_without_usage`?`终态无 usage`:e===`parse_failed`?`SSE 解析失败`:e===`upstream_error`?`上游错误`:`未返回 usage`}function O(e){let t=Object.entries(e.unknownTokenStatusCounts??{}).filter(([,e])=>e>0).sort((e,t)=>t[1]-e[1]);if(t.length===0)return`未返回用量 ${S(e.unknownTokenCount)} 次`;let[n,r]=t[0]??[`not_returned`,0];return`未返回用量 ${S(e.unknownTokenCount)} 次,主要原因:${D(n)} ${S(r)} 次`}function k(e){let t=e.aggregate??x();return(0,b.jsxs)(`article`,{className:`usage-scope-card`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{children:e.title}),(0,b.jsxs)(`strong`,{children:[C(t.totalTokens),` 已知 token`]}),(0,b.jsx)(`p`,{children:e.detail})]}),(0,b.jsxs)(`dl`,{children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`dt`,{children:`新输入`}),(0,b.jsx)(`dd`,{children:C(t.uncachedInputTokens)})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`dt`,{children:`缓存读`}),(0,b.jsx)(`dd`,{children:C(t.cacheReadTokens)})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`dt`,{children:`估算费用`}),(0,b.jsx)(`dd`,{children:w(t.estimatedCostUsd)})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`dt`,{children:`平均`}),(0,b.jsx)(`dd`,{children:c(t.averageDurationMs)})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`dt`,{children:`P95`}),(0,b.jsx)(`dd`,{children:c(t.p95DurationMs)})]})]}),(0,b.jsxs)(`p`,{className:`usage-scope-footer`,children:[T(t),` · `,E(t),` · `,O(t)]})]})}function A(e){return(0,b.jsxs)(`section`,{className:`usage-table-card`,children:[(0,b.jsxs)(`div`,{className:`usage-table-head`,children:[(0,b.jsx)(`h3`,{children:e.title}),(0,b.jsxs)(`span`,{children:[e.rows.length,` 项`]})]}),e.rows.length===0?(0,b.jsx)(`div`,{className:`usage-empty`,children:e.empty}):(0,b.jsx)(`div`,{className:`usage-table-scroll`,children:(0,b.jsxs)(`table`,{className:`usage-table`,children:[(0,b.jsx)(`thead`,{children:(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`th`,{children:`维度`}),(0,b.jsx)(`th`,{children:`请求`}),(0,b.jsx)(`th`,{children:`失败`}),(0,b.jsx)(`th`,{children:`新输入`}),(0,b.jsx)(`th`,{children:`缓存读`}),(0,b.jsx)(`th`,{children:`估算费用`}),(0,b.jsx)(`th`,{children:`已知 token`}),(0,b.jsx)(`th`,{children:`未返回用量`}),(0,b.jsx)(`th`,{children:`图片`}),(0,b.jsx)(`th`,{children:`平均耗时`})]})}),(0,b.jsx)(`tbody`,{children:e.rows.map(e=>(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`td`,{children:e.label}),(0,b.jsx)(`td`,{children:S(e.aggregate.requestCount)}),(0,b.jsx)(`td`,{children:S(e.aggregate.failureCount)}),(0,b.jsx)(`td`,{children:C(e.aggregate.uncachedInputTokens)}),(0,b.jsx)(`td`,{children:C(e.aggregate.cacheReadTokens)}),(0,b.jsx)(`td`,{children:w(e.aggregate.estimatedCostUsd)}),(0,b.jsx)(`td`,{children:C(e.aggregate.totalTokens)}),(0,b.jsx)(`td`,{children:S(e.aggregate.unknownTokenCount)}),(0,b.jsx)(`td`,{children:S(e.aggregate.imageCount)}),(0,b.jsx)(`td`,{children:c(e.aggregate.averageDurationMs)})]},e.key))})]})})]})}function j(e){return(0,b.jsxs)(`section`,{className:`usage-table-card usage-daily-card`,children:[(0,b.jsxs)(`div`,{className:`usage-table-head`,children:[(0,b.jsx)(`h3`,{children:`每日趋势`}),(0,b.jsxs)(`span`,{children:[`最近 `,e.rows.length,` 天`]})]}),e.rows.length===0?(0,b.jsx)(`div`,{className:`usage-empty`,children:`还没有历史用量。`}):(0,b.jsx)(`div`,{className:`usage-table-scroll`,children:(0,b.jsxs)(`table`,{className:`usage-table`,children:[(0,b.jsx)(`thead`,{children:(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`th`,{children:`日期`}),(0,b.jsx)(`th`,{children:`请求`}),(0,b.jsx)(`th`,{children:`成功`}),(0,b.jsx)(`th`,{children:`失败`}),(0,b.jsx)(`th`,{children:`新输入`}),(0,b.jsx)(`th`,{children:`缓存读`}),(0,b.jsx)(`th`,{children:`估算费用`}),(0,b.jsx)(`th`,{children:`已知输入`}),(0,b.jsx)(`th`,{children:`已知输出`}),(0,b.jsx)(`th`,{children:`已知总 token`}),(0,b.jsx)(`th`,{children:`未返回用量`}),(0,b.jsx)(`th`,{children:`图片`}),(0,b.jsx)(`th`,{children:`P95`})]})}),(0,b.jsx)(`tbody`,{children:e.rows.map(e=>(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`td`,{children:e.date}),(0,b.jsx)(`td`,{children:S(e.aggregate.requestCount)}),(0,b.jsx)(`td`,{children:S(e.aggregate.successCount)}),(0,b.jsx)(`td`,{children:S(e.aggregate.failureCount)}),(0,b.jsx)(`td`,{children:C(e.aggregate.uncachedInputTokens)}),(0,b.jsx)(`td`,{children:C(e.aggregate.cacheReadTokens)}),(0,b.jsx)(`td`,{children:w(e.aggregate.estimatedCostUsd)}),(0,b.jsx)(`td`,{children:C(e.aggregate.inputTokens)}),(0,b.jsx)(`td`,{children:C(e.aggregate.outputTokens)}),(0,b.jsx)(`td`,{children:C(e.aggregate.totalTokens)}),(0,b.jsx)(`td`,{children:S(e.aggregate.unknownTokenCount)}),(0,b.jsx)(`td`,{children:S(e.aggregate.imageCount)}),(0,b.jsx)(`td`,{children:c(e.aggregate.p95DurationMs)})]},e.date))})]})})]})}function M(e){let[t,n]=(0,y.useState)(e.config?.usage??null),[r,T]=(0,y.useState)(!1),[D,O]=(0,y.useState)(!1);(0,y.useEffect)(()=>{n(e.config?.usage??null)},[e.config?.usage]);async function M(){T(!0);try{n(await d(`/_gateway/admin/usage`)),e.setStatus(`用量统计已刷新。`)}catch(t){e.setStatus(`用量统计刷新失败: ${f(t)}`)}finally{T(!1)}}async function N(){if(window.confirm(`会先备份当前统计目录,然后清空用量记录并从 0 重新统计。确认继续?`)){O(!0);try{let t=await d(`/_gateway/admin/usage/reset`,{method:`POST`});n(t.usage),e.setStatus(`用量统计已备份并清空:${t.backupDir}`)}catch(t){e.setStatus(`用量统计清空失败: ${f(t)}`)}finally{O(!1)}}}let P=t,F=P?.lifetime??x(),I=P?.today??x(),L=P?.startup??x(),R=(0,y.useMemo)(()=>F.requestCount===0?`0%`:`${(F.failureCount/F.requestCount*100).toFixed(1)}%`,[F.failureCount,F.requestCount]);return(0,b.jsxs)(`section`,{className:`usage-page`,children:[(0,b.jsxs)(`div`,{className:`usage-actions`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{children:`统计文件`}),(0,b.jsx)(`code`,{children:P?.storageDir||`~/.ai-zero-token/.state/usage`})]}),(0,b.jsxs)(`div`,{className:`usage-action-buttons`,children:[(0,b.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>void M(),disabled:r||D,children:[r?(0,b.jsx)(u,{className:`spin`,size:16}):(0,b.jsx)(a,{size:16}),`刷新统计`]}),(0,b.jsxs)(`button`,{className:`btn-danger`,type:`button`,onClick:()=>void N(),disabled:r||D,children:[D?(0,b.jsx)(u,{className:`spin`,size:16}):(0,b.jsx)(m,{size:16}),`备份并清空`]})]})]}),(0,b.jsxs)(`section`,{className:`summary-grid desktop-summary-grid usage-summary-grid`,children:[(0,b.jsx)(p,{icon:v,label:`历史已知 token`,value:C(F.totalTokens),detail:`新输入 ${C(F.uncachedInputTokens)},缓存读 ${C(F.cacheReadTokens)}`,tone:`blue`}),(0,b.jsx)(p,{icon:o,label:`今日已知 token`,value:C(I.totalTokens),detail:`${P?.todayDate||`今天`} · ${E(I)}`,tone:`green`}),(0,b.jsx)(p,{icon:l,label:`本次启动已知 token`,value:C(L.totalTokens),detail:`缓存读 ${C(L.cacheReadTokens)} · 启动于 ${s(P?.startedAt)}`,tone:`brand`}),(0,b.jsx)(p,{icon:_,label:`今日估算费用`,value:w(I.estimatedCostUsd),detail:`输入 ${w(I.inputCostUsd+I.cacheCreationCostUsd+I.cacheReadCostUsd)},输出 ${w(I.outputCostUsd)}`,tone:`green`}),(0,b.jsx)(p,{icon:h,label:`图片张数`,value:S(F.imageCount),detail:`历史累计生成或编辑图片`,tone:`orange`}),(0,b.jsx)(p,{icon:i,label:`历史 P95`,value:c(F.p95DurationMs),detail:`平均 ${c(F.averageDurationMs)}`,tone:`orange`}),(0,b.jsx)(p,{icon:g,label:`失败率`,value:R,detail:`${S(F.failureCount)} 次失败`,tone:F.failureCount>0?`orange`:`green`})]}),(0,b.jsxs)(`section`,{className:`usage-scope-grid`,children:[(0,b.jsx)(k,{title:`今日用量`,detail:`当天 00:00 到现在`,aggregate:I}),(0,b.jsx)(k,{title:`本次启动`,detail:`当前网关进程启动后到现在`,aggregate:L}),(0,b.jsx)(k,{title:`历史累计`,detail:`从开始记录以来的全部累计`,aggregate:F})]}),(0,b.jsx)(j,{rows:P?.daily??[]}),(0,b.jsxs)(`section`,{className:`usage-dimension-grid`,children:[(0,b.jsx)(A,{title:`按账号`,rows:P?.byAccount??[],empty:`还没有账号维度数据。`}),(0,b.jsx)(A,{title:`按模型`,rows:P?.byModel??[],empty:`还没有模型维度数据。`}),(0,b.jsx)(A,{title:`按接口`,rows:P?.byEndpoint??[],empty:`还没有接口维度数据。`}),(0,b.jsx)(A,{title:`按用量捕获状态`,rows:P?.byTokenUsageStatus??[],empty:`还没有用量捕获状态数据。`}),(0,b.jsx)(A,{title:`按错误`,rows:P?.byError??[],empty:`还没有错误记录。`}),(0,b.jsx)(A,{title:`按生图链路`,rows:P?.byImageRoute??[],empty:`还没有生图链路数据。`}),(0,b.jsx)(A,{title:`按来源`,rows:P?.bySource??[],empty:`还没有来源维度数据。`})]})]})}export{M as UsagePage};
1
+ import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./clock-3-BzDANsVk.js";import{t as a}from"./refresh-cw-CAAH2rqe.js";import{t as o}from"./zap-B4_oDbCp.js";import{S as s,w as c}from"./profiles-iNTmJFRe.js";import{S as l,_ as u,p as d,r as f}from"./index-BM5N4YUY.js";import{t as p}from"./StatCard-7TEzqn2i.js";var m=t(`archive`,[[`rect`,{width:`20`,height:`5`,x:`2`,y:`3`,rx:`1`,key:`1wp1u1`}],[`path`,{d:`M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8`,key:`1s80jp`}],[`path`,{d:`M10 12h4`,key:`a56b0p`}]]),h=t(`camera`,[[`path`,{d:`M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z`,key:`18u6gg`}],[`circle`,{cx:`12`,cy:`13`,r:`3`,key:`1vg3eu`}]]),g=t(`database`,[[`ellipse`,{cx:`12`,cy:`5`,rx:`9`,ry:`3`,key:`msslwz`}],[`path`,{d:`M3 5V19A9 3 0 0 0 21 19V5`,key:`1wlel7`}],[`path`,{d:`M3 12A9 3 0 0 0 21 12`,key:`mv7ke4`}]]),_=t(`dollar-sign`,[[`line`,{x1:`12`,x2:`12`,y1:`2`,y2:`22`,key:`7eqyqh`}],[`path`,{d:`M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6`,key:`1b0p4s`}]]),v=t(`sigma`,[[`path`,{d:`M18 7V5a1 1 0 0 0-1-1H6.5a.5.5 0 0 0-.4.8l4.5 6a2 2 0 0 1 0 2.4l-4.5 6a.5.5 0 0 0 .4.8H17a1 1 0 0 0 1-1v-2`,key:`wuwx1p`}]]),y=e(n(),1),b=r();function x(){return{requestCount:0,successCount:0,failureCount:0,inputTokens:0,uncachedInputTokens:0,outputTokens:0,totalTokens:0,cacheCreationTokens:0,cacheReadTokens:0,inputCostUsd:0,outputCostUsd:0,cacheCreationCostUsd:0,cacheReadCostUsd:0,estimatedCostUsd:0,unknownTokenCount:0,unknownTokenStatusCounts:{},imageCount:0,totalDurationMs:0,averageDurationMs:0,p95DurationMs:0,durationBuckets:{}}}function S(e){return new Intl.NumberFormat(`zh-CN`).format(Math.round(e||0))}function C(e){return!Number.isFinite(e)||e<=0?`0`:e>=1e6?`${(e/1e6).toFixed(e>=1e7?1:2)}M`:e>=1e4?`${(e/1e3).toFixed(e>=1e5?0:1)}K`:S(e)}function w(e){return!Number.isFinite(e)||e<=0?`$0.00`:e<.01?`$${e.toFixed(5)}`:e<1?`$${e.toFixed(4)}`:`$${e.toFixed(2)}`}function T(e){return`${S(e.successCount)} 成功 / ${S(e.failureCount)} 失败`}function E(e){let t=e.inputTokens||0,n=e.cacheReadTokens||0;return t<=0||n<=0?`缓存命中 0%`:`缓存命中 ${Math.min(100,n/t*100).toFixed(1)}%`}function D(e){return e===`captured`?`已捕获用量`:e===`missing_terminal`?`缺少终态事件`:e===`terminal_without_usage`?`终态无 usage`:e===`parse_failed`?`SSE 解析失败`:e===`upstream_error`?`上游错误`:`未返回 usage`}function O(e){let t=Object.entries(e.unknownTokenStatusCounts??{}).filter(([,e])=>e>0).sort((e,t)=>t[1]-e[1]);if(t.length===0)return`未返回用量 ${S(e.unknownTokenCount)} 次`;let[n,r]=t[0]??[`not_returned`,0];return`未返回用量 ${S(e.unknownTokenCount)} 次,主要原因:${D(n)} ${S(r)} 次`}function k(e){let t=e.aggregate??x();return(0,b.jsxs)(`article`,{className:`usage-scope-card`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{children:e.title}),(0,b.jsxs)(`strong`,{children:[C(t.totalTokens),` 已知 token`]}),(0,b.jsx)(`p`,{children:e.detail})]}),(0,b.jsxs)(`dl`,{children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`dt`,{children:`新输入`}),(0,b.jsx)(`dd`,{children:C(t.uncachedInputTokens)})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`dt`,{children:`缓存读`}),(0,b.jsx)(`dd`,{children:C(t.cacheReadTokens)})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`dt`,{children:`估算费用`}),(0,b.jsx)(`dd`,{children:w(t.estimatedCostUsd)})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`dt`,{children:`平均`}),(0,b.jsx)(`dd`,{children:s(t.averageDurationMs)})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`dt`,{children:`P95`}),(0,b.jsx)(`dd`,{children:s(t.p95DurationMs)})]})]}),(0,b.jsxs)(`p`,{className:`usage-scope-footer`,children:[T(t),` · `,E(t),` · `,O(t)]})]})}function A(e){return(0,b.jsxs)(`section`,{className:`usage-table-card`,children:[(0,b.jsxs)(`div`,{className:`usage-table-head`,children:[(0,b.jsx)(`h3`,{children:e.title}),(0,b.jsxs)(`span`,{children:[e.rows.length,` 项`]})]}),e.rows.length===0?(0,b.jsx)(`div`,{className:`usage-empty`,children:e.empty}):(0,b.jsx)(`div`,{className:`usage-table-scroll`,children:(0,b.jsxs)(`table`,{className:`usage-table`,children:[(0,b.jsx)(`thead`,{children:(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`th`,{children:`维度`}),(0,b.jsx)(`th`,{children:`请求`}),(0,b.jsx)(`th`,{children:`失败`}),(0,b.jsx)(`th`,{children:`新输入`}),(0,b.jsx)(`th`,{children:`缓存读`}),(0,b.jsx)(`th`,{children:`估算费用`}),(0,b.jsx)(`th`,{children:`已知 token`}),(0,b.jsx)(`th`,{children:`未返回用量`}),(0,b.jsx)(`th`,{children:`图片`}),(0,b.jsx)(`th`,{children:`平均耗时`})]})}),(0,b.jsx)(`tbody`,{children:e.rows.map(e=>(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`td`,{children:e.label}),(0,b.jsx)(`td`,{children:S(e.aggregate.requestCount)}),(0,b.jsx)(`td`,{children:S(e.aggregate.failureCount)}),(0,b.jsx)(`td`,{children:C(e.aggregate.uncachedInputTokens)}),(0,b.jsx)(`td`,{children:C(e.aggregate.cacheReadTokens)}),(0,b.jsx)(`td`,{children:w(e.aggregate.estimatedCostUsd)}),(0,b.jsx)(`td`,{children:C(e.aggregate.totalTokens)}),(0,b.jsx)(`td`,{children:S(e.aggregate.unknownTokenCount)}),(0,b.jsx)(`td`,{children:S(e.aggregate.imageCount)}),(0,b.jsx)(`td`,{children:s(e.aggregate.averageDurationMs)})]},e.key))})]})})]})}function j(e){return(0,b.jsxs)(`section`,{className:`usage-table-card usage-daily-card`,children:[(0,b.jsxs)(`div`,{className:`usage-table-head`,children:[(0,b.jsx)(`h3`,{children:`每日趋势`}),(0,b.jsxs)(`span`,{children:[`最近 `,e.rows.length,` 天`]})]}),e.rows.length===0?(0,b.jsx)(`div`,{className:`usage-empty`,children:`还没有历史用量。`}):(0,b.jsx)(`div`,{className:`usage-table-scroll`,children:(0,b.jsxs)(`table`,{className:`usage-table`,children:[(0,b.jsx)(`thead`,{children:(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`th`,{children:`日期`}),(0,b.jsx)(`th`,{children:`请求`}),(0,b.jsx)(`th`,{children:`成功`}),(0,b.jsx)(`th`,{children:`失败`}),(0,b.jsx)(`th`,{children:`新输入`}),(0,b.jsx)(`th`,{children:`缓存读`}),(0,b.jsx)(`th`,{children:`估算费用`}),(0,b.jsx)(`th`,{children:`已知输入`}),(0,b.jsx)(`th`,{children:`已知输出`}),(0,b.jsx)(`th`,{children:`已知总 token`}),(0,b.jsx)(`th`,{children:`未返回用量`}),(0,b.jsx)(`th`,{children:`图片`}),(0,b.jsx)(`th`,{children:`P95`})]})}),(0,b.jsx)(`tbody`,{children:e.rows.map(e=>(0,b.jsxs)(`tr`,{children:[(0,b.jsx)(`td`,{children:e.date}),(0,b.jsx)(`td`,{children:S(e.aggregate.requestCount)}),(0,b.jsx)(`td`,{children:S(e.aggregate.successCount)}),(0,b.jsx)(`td`,{children:S(e.aggregate.failureCount)}),(0,b.jsx)(`td`,{children:C(e.aggregate.uncachedInputTokens)}),(0,b.jsx)(`td`,{children:C(e.aggregate.cacheReadTokens)}),(0,b.jsx)(`td`,{children:w(e.aggregate.estimatedCostUsd)}),(0,b.jsx)(`td`,{children:C(e.aggregate.inputTokens)}),(0,b.jsx)(`td`,{children:C(e.aggregate.outputTokens)}),(0,b.jsx)(`td`,{children:C(e.aggregate.totalTokens)}),(0,b.jsx)(`td`,{children:S(e.aggregate.unknownTokenCount)}),(0,b.jsx)(`td`,{children:S(e.aggregate.imageCount)}),(0,b.jsx)(`td`,{children:s(e.aggregate.p95DurationMs)})]},e.date))})]})})]})}function M(e){let[t,n]=(0,y.useState)(e.config?.usage??null),[r,T]=(0,y.useState)(!1),[D,O]=(0,y.useState)(!1);(0,y.useEffect)(()=>{n(e.config?.usage??null)},[e.config?.usage]);async function M(){T(!0);try{n(await d(`/_gateway/admin/usage`)),e.setStatus(`用量统计已刷新。`)}catch(t){e.setStatus(`用量统计刷新失败: ${f(t)}`)}finally{T(!1)}}async function N(){if(window.confirm(`会先备份当前统计目录,然后清空用量记录并从 0 重新统计。确认继续?`)){O(!0);try{let t=await d(`/_gateway/admin/usage/reset`,{method:`POST`});n(t.usage),e.setStatus(`用量统计已备份并清空:${t.backupDir}`)}catch(t){e.setStatus(`用量统计清空失败: ${f(t)}`)}finally{O(!1)}}}let P=t,F=P?.lifetime??x(),I=P?.today??x(),L=P?.startup??x(),R=(0,y.useMemo)(()=>F.requestCount===0?`0%`:`${(F.failureCount/F.requestCount*100).toFixed(1)}%`,[F.failureCount,F.requestCount]);return(0,b.jsxs)(`section`,{className:`usage-page`,children:[(0,b.jsxs)(`div`,{className:`usage-actions`,children:[(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{children:`统计文件`}),(0,b.jsx)(`code`,{children:P?.storageDir||`~/.ai-zero-token/.state/usage`})]}),(0,b.jsxs)(`div`,{className:`usage-action-buttons`,children:[(0,b.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>void M(),disabled:r||D,children:[r?(0,b.jsx)(u,{className:`spin`,size:16}):(0,b.jsx)(a,{size:16}),`刷新统计`]}),(0,b.jsxs)(`button`,{className:`btn-danger`,type:`button`,onClick:()=>void N(),disabled:r||D,children:[D?(0,b.jsx)(u,{className:`spin`,size:16}):(0,b.jsx)(m,{size:16}),`备份并清空`]})]})]}),(0,b.jsxs)(`section`,{className:`summary-grid desktop-summary-grid usage-summary-grid`,children:[(0,b.jsx)(p,{icon:v,label:`历史已知 token`,value:C(F.totalTokens),detail:`新输入 ${C(F.uncachedInputTokens)},缓存读 ${C(F.cacheReadTokens)}`,tone:`blue`}),(0,b.jsx)(p,{icon:o,label:`今日已知 token`,value:C(I.totalTokens),detail:`${P?.todayDate||`今天`} · ${E(I)}`,tone:`green`}),(0,b.jsx)(p,{icon:l,label:`本次启动已知 token`,value:C(L.totalTokens),detail:`缓存读 ${C(L.cacheReadTokens)} · 启动于 ${c(P?.startedAt)}`,tone:`brand`}),(0,b.jsx)(p,{icon:_,label:`今日估算费用`,value:w(I.estimatedCostUsd),detail:`输入 ${w(I.inputCostUsd+I.cacheCreationCostUsd+I.cacheReadCostUsd)},输出 ${w(I.outputCostUsd)}`,tone:`green`}),(0,b.jsx)(p,{icon:h,label:`图片张数`,value:S(F.imageCount),detail:`历史累计生成或编辑图片`,tone:`orange`}),(0,b.jsx)(p,{icon:i,label:`历史 P95`,value:s(F.p95DurationMs),detail:`平均 ${s(F.averageDurationMs)}`,tone:`orange`}),(0,b.jsx)(p,{icon:g,label:`失败率`,value:R,detail:`${S(F.failureCount)} 次失败`,tone:F.failureCount>0?`orange`:`green`})]}),(0,b.jsxs)(`section`,{className:`usage-scope-grid`,children:[(0,b.jsx)(k,{title:`今日用量`,detail:`当天 00:00 到现在`,aggregate:I}),(0,b.jsx)(k,{title:`本次启动`,detail:`当前网关进程启动后到现在`,aggregate:L}),(0,b.jsx)(k,{title:`历史累计`,detail:`从开始记录以来的全部累计`,aggregate:F})]}),(0,b.jsx)(j,{rows:P?.daily??[]}),(0,b.jsxs)(`section`,{className:`usage-dimension-grid`,children:[(0,b.jsx)(A,{title:`按账号`,rows:P?.byAccount??[],empty:`还没有账号维度数据。`}),(0,b.jsx)(A,{title:`按模型`,rows:P?.byModel??[],empty:`还没有模型维度数据。`}),(0,b.jsx)(A,{title:`按接口`,rows:P?.byEndpoint??[],empty:`还没有接口维度数据。`}),(0,b.jsx)(A,{title:`按用量捕获状态`,rows:P?.byTokenUsageStatus??[],empty:`还没有用量捕获状态数据。`}),(0,b.jsx)(A,{title:`按错误`,rows:P?.byError??[],empty:`还没有错误记录。`}),(0,b.jsx)(A,{title:`按生图链路`,rows:P?.byImageRoute??[],empty:`还没有生图链路数据。`}),(0,b.jsx)(A,{title:`按来源`,rows:P?.bySource??[],empty:`还没有来源维度数据。`})]})]})}export{M as UsagePage};
@@ -5,10 +5,10 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/svg+xml" href="/assets/app-mark-nsRs4vo7.svg" />
7
7
  <title>AI Zero Token</title>
8
- <script type="module" crossorigin src="/assets/index-_5Ny0cZf.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-BM5N4YUY.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/jsx-runtime-DqpGtLhh.js">
10
- <link rel="modulepreload" crossorigin href="/assets/profiles-C5SmQvju.js">
11
- <link rel="stylesheet" crossorigin href="/assets/index-BRQrU_AA.css">
10
+ <link rel="modulepreload" crossorigin href="/assets/profiles-iNTmJFRe.js">
11
+ <link rel="stylesheet" crossorigin href="/assets/index-BgT1IdcO.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
@@ -97,14 +97,14 @@ function parseAuthorizationInput(value) {
97
97
  }
98
98
  return { code: trimmed };
99
99
  }
100
- function extractProfile(accessToken, refreshToken, expires, idToken) {
100
+ function extractProfile(accessToken, refreshToken, expires, idToken, fallback) {
101
101
  const payload = decodeJwtPayload(accessToken);
102
102
  const authClaim = payload?.[JWT_CLAIM_PATH];
103
- const accountId = authClaim?.chatgpt_account_id;
103
+ const accountId = authClaim?.chatgpt_account_id ?? fallback?.accountId;
104
104
  if (typeof accountId !== "string" || !accountId.trim()) {
105
105
  throw new Error("\u65E0\u6CD5\u4ECE access token \u4E2D\u63D0\u53D6 accountId\u3002");
106
106
  }
107
- const email = extractEmailFromPayload(payload);
107
+ const email = extractEmailFromPayload(payload) ?? fallback?.email;
108
108
  return {
109
109
  provider: "openai-codex",
110
110
  profileId: `openai-codex:${accountId}`,
@@ -176,7 +176,11 @@ async function refreshOpenAICodexToken(profile) {
176
176
  json.access_token,
177
177
  json.refresh_token,
178
178
  Date.now() + json.expires_in * 1e3,
179
- json.id_token ?? profile.idToken
179
+ json.id_token ?? profile.idToken,
180
+ {
181
+ accountId: profile.accountId,
182
+ email: profile.email
183
+ }
180
184
  );
181
185
  }
182
186
  function tryOpenBrowser(url) {
@@ -81,6 +81,28 @@ class AuthService {
81
81
  exportAudit: profile.exportAudit
82
82
  };
83
83
  }
84
+ decodeJwtExpiry(token) {
85
+ try {
86
+ const parts = token.split(".");
87
+ if (parts.length !== 3) {
88
+ return void 0;
89
+ }
90
+ const payload = parts[1] ?? "";
91
+ const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
92
+ const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
93
+ const parsed = JSON.parse(Buffer.from(normalized + padding, "base64").toString("utf8"));
94
+ return typeof parsed.exp === "number" && Number.isFinite(parsed.exp) ? parsed.exp * 1e3 : void 0;
95
+ } catch {
96
+ return void 0;
97
+ }
98
+ }
99
+ hasValidIdToken(profile) {
100
+ if (!profile.idToken) {
101
+ return false;
102
+ }
103
+ const expiresAt = this.decodeJwtExpiry(profile.idToken);
104
+ return typeof expiresAt === "number" ? Date.now() < expiresAt : true;
105
+ }
84
106
  buildExportAudit(current, kind, exportedAt) {
85
107
  return {
86
108
  exported: true,
@@ -551,12 +573,12 @@ class AuthService {
551
573
  if (!profile) {
552
574
  throw new Error(`\u6CA1\u6709\u627E\u5230\u8D26\u53F7: ${profileId}`);
553
575
  }
554
- if (profile.idToken && Date.now() < profile.expires) {
576
+ if (Date.now() < profile.expires && this.hasValidIdToken(profile)) {
555
577
  return this.toManagedProfile(profile);
556
578
  }
557
579
  const refreshed = await this.refreshStoredProfile(profile, provider);
558
- if (!refreshed.idToken) {
559
- throw new Error("\u5237\u65B0 token \u6210\u529F\uFF0C\u4F46\u4E0A\u6E38\u6CA1\u6709\u8FD4\u56DE id_token\u3002");
580
+ if (!this.hasValidIdToken(refreshed)) {
581
+ 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
582
  }
561
583
  return this.toManagedProfile(refreshed);
562
584
  }
@@ -2,6 +2,15 @@
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.9 Release Notes
6
+
7
+ Version `2.0.9` clarifies automatic account rotation eligibility and tightens Codex auth refresh handling:
8
+
9
+ - Settings now separates manually excluded accounts from accounts that are runtime-ineligible because login is unavailable or quota is exhausted.
10
+ - Account filters and stats distinguish configured rotation participation from the actual automatic rotation candidate pool.
11
+ - Refreshed Codex tokens preserve account identity metadata when upstream token payloads omit profile claims.
12
+ - 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.
13
+
5
14
  ## 2.0.6 Release Notes
6
15
 
7
16
  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.8",
3
+ "version": "2.0.9",
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};