ai-zero-token 1.0.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +32 -0
- package/README.zh-CN.md +32 -0
- package/admin-ui/dist/assets/app-mark-Gd2QnHMO.svg +9 -0
- package/admin-ui/dist/assets/index-CCywUil_.js +1745 -0
- package/admin-ui/dist/assets/index-DNzR8XR7.css +1 -0
- package/admin-ui/dist/assets/wechat-contact-Dlaib1YP.png +0 -0
- package/admin-ui/dist/index.html +14 -0
- package/build/icon.icns +0 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/dist/core/context.js +3 -0
- package/dist/core/providers/http-client.js +10 -4
- package/dist/core/providers/openai-codex/chat.js +23 -0
- package/dist/core/providers/openai-codex/oauth.js +24 -1
- package/dist/core/services/auth-service.js +176 -24
- package/dist/core/services/chat-service.js +2 -2
- package/dist/core/services/image-service.js +2 -2
- package/dist/core/services/network-detect-service.js +239 -0
- package/dist/desktop/main.js +143 -0
- package/dist/server/admin-page.js +112 -19
- package/dist/server/app.js +98 -5
- package/docs/DESKTOP_RELEASE.md +104 -0
- package/docs/PRODUCT_UPDATE_DESKTOP_TOOLBOX.md +429 -0
- package/package.json +71 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.docs-page{gap:14px;display:grid}.docs-page-head{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.docs-page-kicker{color:var(--brand);align-items:center;gap:8px;font-size:12px;font-weight:800;display:inline-flex}.docs-page-head h2{margin:6px 0 0;font-size:22px;line-height:1.2}.docs-page-head p{color:var(--text-muted);margin:8px 0 0;font-size:13px;line-height:1.6}.docs-page-actions{flex-wrap:wrap;justify-content:flex-end;gap:10px;display:flex}.docs-summary{grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;display:grid}.docs-summary-item{border:1px solid var(--line);min-width:0;box-shadow:var(--shadow-sm);background:#ffffffeb;border-radius:14px;padding:12px 13px}.docs-summary-item span{color:var(--text-muted);font-size:11px;font-weight:800;line-height:1.3;display:block}.docs-summary-item strong{color:var(--text);overflow-wrap:anywhere;margin-top:5px;font-size:13px;line-height:1.45;display:block}.docs-layout{grid-template-columns:minmax(0,1fr);align-items:start;gap:12px;display:grid}.docs-main{gap:12px;min-width:0;display:grid}.docs-tab-bar{border:1px solid var(--line);background:#ffffffe6;border-radius:14px;align-items:center;gap:6px;width:fit-content;padding:4px;display:inline-flex}.docs-tab-bar button{min-height:34px;color:var(--text-muted);background:0 0;border:0;border-radius:10px;padding:0 12px;font-size:13px;font-weight:800}.docs-tab-bar button.is-active{color:var(--brand);background:#635bff1a}.docs-panel,.docs-snippet{border:1px solid var(--line);box-shadow:var(--shadow-sm);background:#fffffff0;border-radius:16px}.docs-panel{padding:16px}.docs-panel-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;display:grid}.docs-panel-wide{grid-column:1/-1}.docs-panel-head{justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:14px;display:flex}.docs-panel-head h3{margin:0;font-size:15px;line-height:1.25}.docs-panel-head p,.docs-snippet p{color:var(--text-muted);margin:6px 0 0;font-size:12px;line-height:1.55}.docs-step-list{gap:12px;margin:0;padding-left:18px;display:grid}.docs-step-list li{gap:6px;display:grid}.docs-step-list strong{font-size:13px;line-height:1.3}.docs-step-list span{color:var(--text-muted);font-size:12px;line-height:1.55}.docs-step-list code,.docs-code-sample code,.docs-markdown p code,.docs-markdown li code{color:var(--brand-strong);background:#635bff14;border-radius:7px;padding:1px 5px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}.docs-mini-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-bottom:12px;display:grid}.docs-mini-copy{border:1px solid var(--line);text-align:left;min-width:0;color:var(--text);background:#f8fafc;border-radius:14px;gap:6px;padding:12px;display:grid}.docs-mini-copy span{color:var(--text-muted);font-size:11px;font-weight:800}.docs-mini-copy strong{overflow-wrap:anywhere;min-width:0;font-size:12px;line-height:1.4}.docs-mini-copy svg{color:var(--brand);justify-self:end}.docs-code-sample,.docs-snippet pre,.docs-code{border:1px solid var(--line);color:var(--text);background:#f8fafc;border-radius:14px;margin:0;overflow:auto}.docs-code-sample{padding:14px;font-size:12px;line-height:1.6}.docs-endpoint-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;display:grid}.docs-endpoint{border:1px solid var(--line);background:#f8fafc;border-radius:12px;justify-content:space-between;align-items:center;gap:8px;min-height:44px;padding:0 12px;font-size:12px;font-weight:800;display:flex}.docs-preview-panel{gap:14px;display:grid}.docs-skill-summary{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;display:grid}.docs-summary-tile{border:1px solid var(--line);background:#f8fafc;border-radius:14px;min-width:0;padding:12px 13px}.docs-summary-tile span{color:var(--text-muted);font-size:11px;font-weight:800;line-height:1.25;display:block}.docs-summary-tile strong{color:var(--text);overflow-wrap:anywhere;margin-top:5px;font-size:13px;line-height:1.45;display:block}.docs-source-fold{border:1px solid var(--line);background:#fff;border-radius:14px;overflow:hidden}.docs-source-fold summary{cursor:pointer;color:var(--text);justify-content:space-between;align-items:center;gap:12px;padding:12px 14px;font-size:13px;font-weight:800;list-style:none;display:flex}.docs-source-fold summary::-webkit-details-marker{display:none}.docs-source-fold summary span{color:var(--text-muted);font-size:11px;font-weight:700}.docs-source{border-top:1px solid var(--line);max-height:420px;color:var(--text);background:#f8fafc;margin:0;padding:14px;font-size:12px;line-height:1.65;overflow:auto}.docs-source code{white-space:pre;overflow-wrap:normal;word-break:normal;color:inherit;background:0 0;padding:0}.docs-markdown{gap:10px;max-height:620px;padding-right:4px;display:grid;overflow:auto}.docs-markdown h1,.docs-markdown h2,.docs-markdown h3,.docs-markdown h4{margin:12px 0 0;line-height:1.3}.docs-markdown h1{font-size:22px}.docs-markdown h2{font-size:18px}.docs-markdown h3{font-size:15px}.docs-markdown h4{font-size:13px}.docs-markdown p,.docs-markdown li{color:var(--text-soft);margin:0;font-size:13px;line-height:1.7}.docs-markdown ul{gap:6px;margin:0;padding-left:20px;display:grid}.docs-code{padding:14px 14px 12px;font-size:12px;line-height:1.6;position:relative}.docs-code-lang{color:var(--text-muted);text-transform:uppercase;letter-spacing:0;margin-bottom:10px;font-size:11px;font-weight:800;display:inline-flex}.docs-code code{color:var(--text);white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;background:0 0;margin:0;padding:0;display:block}.docs-example-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;display:grid}.docs-note-panel{grid-column:1/-1;padding:16px}.docs-note-panel ul{color:var(--text-soft);gap:8px;margin:10px 0 0;padding-left:18px;font-size:13px;line-height:1.6;display:grid}.docs-action-row{flex-wrap:wrap;gap:10px;margin-top:14px;display:flex}.docs-snippet{padding:16px}.docs-snippet-head{justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:12px;display:flex}.docs-snippet-head strong{font-size:14px;line-height:1.3;display:block}.docs-snippet pre{padding:14px;font-size:12px;line-height:1.6}@media (width<=1280px){.docs-layout{grid-template-columns:minmax(0,1fr)}}@media (width<=1120px){.docs-page-head,.docs-layout,.docs-panel-grid,.docs-example-grid,.docs-summary,.docs-mini-grid,.docs-endpoint-grid{grid-template-columns:1fr}.docs-page-actions{justify-content:flex-start}}:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--bg:#f8fafc;--panel:#fff;--panel-soft:#f8fafc;--line:#e2e8f0;--line-strong:#cbd5e1;--text:#0f172a;--text-soft:#334155;--text-muted:#64748b;--brand:#635bff;--brand-strong:#4f46e5;--brand-soft:#635bff1a;--blue:#3b82f6;--blue-soft:#3b82f61f;--green:#22c55e;--green-soft:#22c55e1f;--orange:#f59e0b;--orange-soft:#f59e0b1f;--red:#ef4444;--red-soft:#ef44441f;--plan-color:#94a3b8;--plan-soft:#94a3b81f;--plan-border:var(--line);--shadow:0 2px 10px #0f172a0f;--shadow-sm:var(--shadow);--radius:16px;--radius-sm:12px;--radius-xs:10px}*{box-sizing:border-box}html{background:var(--bg)}body{min-width:0;min-height:100vh;color:var(--text);background:radial-gradient(circle at top left, #635bff14, transparent 28%), radial-gradient(circle at right top, #3b82f60f, transparent 32%), var(--bg);margin:0;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,sans-serif}button,input,select,textarea{font:inherit}button,a{-webkit-tap-highlight-color:transparent}button{cursor:pointer}a{color:inherit;text-decoration:none}.app-shell{grid-template-columns:236px minmax(0,1fr);align-items:start;gap:14px;width:min(100vw - 32px,1540px);max-width:none;margin:6px auto 18px;display:grid}.sidebar,.card,.trend-card,.summary-card,.account-card,.log-table-wrap,.tester-tabs,.service-card,.update-panel{border:1px solid var(--line);box-shadow:var(--shadow-sm);background:#fffffff0}.sidebar{border-radius:18px;gap:14px;min-width:0;max-height:calc(100vh - 20px);padding:14px 12px;display:grid;position:sticky;top:10px;overflow:auto}.brand{align-items:center;gap:12px;padding:2px 4px 4px;display:flex}.brand-mark{width:34px;height:34px;color:var(--brand);background:linear-gradient(#635bff24,#635bff0a);border:1px solid #635bff33;border-radius:12px;flex:none;place-items:center;display:grid;overflow:hidden}.brand-mark img{width:100%;height:100%;display:block}.brand strong{font-size:14px;line-height:1.25;display:block}.brand span{color:var(--text-muted);margin-top:5px;font-size:10px;line-height:1.5;display:block}.nav{gap:4px;display:grid}.nav-item{width:100%;min-height:36px;color:var(--text-soft);text-align:left;background:0 0;border:1px solid #0000;border-radius:11px;align-items:center;gap:10px;padding:0 11px;display:flex}.nav-item.is-active,.nav-item:hover{color:var(--brand);background:#635bff14;border-color:#635bff1a;font-weight:650}.sidebar-links{gap:12px;margin-top:auto}.sidebar-status{gap:12px;padding:11px}.sidebar-status-summary{grid-template-columns:16px minmax(0,1fr);align-items:center;gap:9px;min-width:0;display:grid}.sidebar-status-summary strong{font-size:14px;line-height:1.25;display:block}.sidebar-status-summary span:last-child{color:var(--text-muted);text-overflow:ellipsis;white-space:nowrap;margin-top:2px;font-size:11px;line-height:1.3;display:block;overflow:hidden}.sidebar-base-url{border:1px solid var(--line);width:100%;min-width:0;color:var(--text);text-align:left;background:#f8fafc;border-radius:11px;gap:5px;padding:9px;display:grid}.sidebar-base-url span{color:var(--text-muted);font-size:10px;font-weight:800}.sidebar-base-url code{min-width:0;color:var(--text);overflow-wrap:anywhere;font-size:11px;line-height:1.45}.sidebar-kv-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.sidebar-kv{background:#f8fafcd1;border-radius:11px;min-width:0;padding:8px 9px}.sidebar-kv.wide{grid-column:1/-1}.sidebar-kv span{color:var(--text-muted);font-size:10px;font-weight:800;line-height:1.2;display:block}.sidebar-kv strong{color:var(--text);overflow-wrap:anywhere;margin-top:4px;font-size:12px;line-height:1.35;display:block}.sidebar-link-list{gap:7px;display:grid}.sidebar-link{border:1px solid var(--line);width:100%;min-height:34px;color:var(--text-soft);text-align:left;background:#fff;border-radius:11px;justify-content:flex-start;align-items:center;gap:8px;padding:0 12px;font-size:12px;font-weight:700;display:inline-flex}.sidebar-link:hover{border-color:var(--line-strong);background:#f8fafc}.main{gap:16px;min-width:0;display:grid}.page-actions{flex-wrap:wrap;justify-content:flex-end;gap:10px;margin-bottom:2px;display:flex}.topbar{grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:12px;display:grid}.page-title h1{margin:0;font-size:27px;line-height:1.15}.page-kicker{color:var(--brand);letter-spacing:0;margin-bottom:8px;font-size:12px;font-weight:850;display:inline-flex}.page-title p{color:var(--text-muted);margin:8px 0 0;font-size:14px;line-height:1.6}.top-actions,.section-actions,.button-row,.example-row{flex-wrap:wrap;justify-content:flex-end;gap:10px;display:flex}.top-actions .btn-primary,.top-actions .btn-secondary,.top-actions .btn-danger{border-radius:10px;min-height:36px;padding:0 12px;font-size:12px}.btn-primary,.btn-secondary,.btn-danger{border:1px solid var(--line);white-space:nowrap;min-height:40px;color:var(--text-soft);background:#fff;border-radius:12px;justify-content:center;align-items:center;gap:8px;padding:0 14px;font-size:13px;font-weight:700;display:inline-flex;box-shadow:0 1px #0f172a05}.btn-primary{color:#fff;border-color:var(--brand);background:var(--brand)}.btn-primary:hover{background:var(--brand-strong)}.btn-secondary:hover{border-color:var(--line-strong);background:#f8fafc}.btn-danger{color:#ef4444;background:var(--red-soft);border-color:#ef44442e}.btn-danger:hover{background:#ef444429}.btn-primary:disabled,.btn-secondary:disabled,.btn-danger:disabled{cursor:not-allowed;opacity:.58}.icon-only{width:42px;padding:0}.summary-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;display:grid}.overview-summary-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.summary-card{background:linear-gradient(145deg, #fffffffa, #f8fbfff5), var(--surface);border-color:#94a3b83d;border-radius:12px;grid-template-columns:24px minmax(0,1fr);align-content:start;gap:9px;min-height:84px;padding:12px 13px;display:grid;position:relative;overflow:hidden;box-shadow:0 14px 28px #0f172a12,inset 0 1px #ffffffe6}.summary-card:before{content:"";background:var(--brand);opacity:.75;width:3px;position:absolute;inset:0 auto 0 0}.summary-card:after{content:"";pointer-events:none;background:radial-gradient(circle at 12% 0,#4053ff14,#0000 34%);position:absolute;inset:0}.summary-card.tone-blue:before{background:var(--blue)}.summary-card.tone-green:before{background:var(--green)}.summary-card.tone-orange:before{background:var(--orange)}.summary-card.tone-red:before{background:var(--red)}.summary-card.tone-slate:before{background:var(--text-muted)}.summary-card>div:last-child{z-index:1;gap:4px;min-width:0;display:grid;position:relative}.summary-card span{color:var(--text-muted);font-size:11px;font-weight:760;line-height:1.2}.summary-card strong{letter-spacing:0;overflow-wrap:break-word;min-width:0;font-size:19px;line-height:1.08;display:block}.summary-card.compact-value strong{overflow-wrap:anywhere;font-size:16px;line-height:1.15}.usage-summary{gap:5px;min-width:0;display:grid}.usage-summary-row{grid-template-columns:42px minmax(0,1fr);align-items:center;gap:7px;min-width:0;display:grid}.usage-summary-row span{min-width:0;height:auto;color:var(--blue);background:0 0;border-radius:0;justify-content:flex-start;align-items:center;font-size:10px;font-weight:850;display:inline-flex}.usage-summary-row span:before{content:"";width:5px;height:5px;box-shadow:0 0 0 3px color-mix(in srgb, currentColor 14%, transparent);background:currentColor;border-radius:999px;margin-right:4px}.usage-summary-row:nth-child(2) span{color:var(--green)}.summary-card.compact-value .usage-summary-row strong{white-space:nowrap;text-overflow:ellipsis;overflow-wrap:normal;min-width:0;font-size:12px;line-height:1.25;overflow:hidden}.summary-card p{color:var(--text-muted);margin:0;font-size:11px;line-height:1.32}.summary-card p:empty{display:none}.summary-icon{z-index:1;width:20px;height:20px;color:var(--brand);background:var(--brand-soft);border-radius:999px;place-items:center;display:grid;position:relative;box-shadow:inset 0 0 0 1px #ffffffb8}.summary-icon.blue{color:var(--blue);background:var(--blue-soft)}.summary-icon.green{color:var(--green);background:var(--green-soft)}.summary-icon.orange{color:var(--orange);background:var(--orange-soft)}.main-grid{grid-template-columns:minmax(0,1.8fr) minmax(360px,.95fr);align-items:start;gap:22px;display:grid}.overview-grid,.tester-route-grid{grid-template-columns:minmax(0,1.18fr) minmax(420px,.82fr);align-items:start;gap:22px;display:grid}.tester-route-grid{grid-template-columns:minmax(0,1fr)}.tester-route-grid .tester-card{width:100%}.card,.trend-card,.log-table-wrap,.update-panel{border-radius:20px;padding:20px}.section-head{justify-content:space-between;align-items:flex-start;gap:16px;margin-bottom:18px;display:flex}.section-head.compact{margin-bottom:14px}.section-head h2,.section-head h3{margin:0;font-size:20px;line-height:1.25}.section-head p{color:var(--text-muted);margin:7px 0 0;font-size:13px;line-height:1.6}.trend-card{gap:14px;display:grid}.chart-wrap{border:1px solid var(--line);background:linear-gradient(#f8fafc99,#fff);border-radius:16px;width:100%;padding:14px;overflow:hidden}.chart-legend{color:var(--text-muted);flex-wrap:wrap;gap:14px;font-size:12px;display:flex}.legend-item{align-items:center;gap:8px;display:inline-flex}.legend-swatch{border-radius:999px;width:10px;height:10px}.legend-swatch.purple{background:#635bff}.legend-swatch.blue{background:#3b82f6}.trend-svg{width:100%;height:210px;margin-top:10px;display:block}.trend-labels{color:var(--text-muted);grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;margin-top:10px;font-size:11px;line-height:1.4;display:grid}.filter-row{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:18px;display:flex}.search-box{border:1px solid var(--line);min-width:0;min-height:40px;color:var(--text-muted);background:#fff;border-radius:12px;flex:220px;align-items:center;gap:10px;padding:0 12px;display:flex}.search-box input{width:100%;min-width:0;color:var(--text);background:0 0;border:0;outline:none}.input,.control,.textarea{border:1px solid var(--line);width:100%;color:var(--text);background:#fff;border-radius:12px;outline:none}.input,.control{min-height:40px;padding:0 12px}.filter-row .control{flex:0 0 156px;min-width:156px}.textarea{resize:vertical;min-height:180px;padding:14px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.55}.field{color:var(--text-soft);gap:8px;font-size:13px;font-weight:700;display:grid}.hint,.status-inline{color:var(--text-muted);margin:9px 0;font-size:13px;line-height:1.6}.account-selected-count{color:var(--text-muted);white-space:nowrap;font-size:12px;font-weight:700}.service-card{border-radius:16px;gap:12px;padding:14px;display:grid}.sidebar-status{border:1px solid var(--line);background:#fff}.sidebar-status.tone-green{border-color:#22c55e2e}.sidebar-status.tone-orange{background:#fffbebf2;border-color:#f59e0b3d}.sidebar-status.tone-red{background:#fef2f2f2;border-color:#ef444433}.sidebar-status .service-head span{color:var(--text-muted);font-size:11px;font-weight:700}.sidebar-meta-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.sidebar-meta{background:#f8fafce0;border-radius:11px;min-width:0;padding:8px 10px}.sidebar-meta span{color:var(--text-muted);font-size:10px;font-weight:800;line-height:1.2;display:block}.sidebar-meta strong{color:var(--text);overflow-wrap:anywhere;margin-top:4px;font-size:12px;line-height:1.35;display:block}.endpoint-card .compact-grid{grid-template-columns:1fr;gap:12px}.service-head{justify-content:space-between;align-items:center;gap:12px;display:flex}.service-list{gap:10px;display:grid}.compact-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.service-row{min-width:0}.service-row label{color:var(--text-muted);margin-bottom:4px;font-size:12px;display:block}.service-row strong,.service-row code{color:var(--text);word-break:break-word;overflow-wrap:anywhere;font-size:13px;line-height:1.5}.meta-grid .service-row label{margin-bottom:0;font-size:10px;line-height:1.4}.meta-grid .service-row strong,.meta-grid .service-row code{color:var(--text-soft);font-size:11px;line-height:1.45}.status-dot{background:var(--green);border-radius:999px;width:8px;height:8px;box-shadow:0 0 0 4px #22c55e24}.status-dot.offline{background:var(--orange);box-shadow:0 0 0 4px #f59e0b24}.update-panel{background:#fffbebf0;border-color:#f59e0b40;justify-content:space-between;align-items:center;gap:16px;display:flex}.update-panel div{gap:5px;display:grid}.update-panel span{color:var(--text-muted);font-size:13px}.update-panel code{color:#92400e;overflow-wrap:anywhere;background:#fff;border-radius:10px;padding:9px 11px}.modal-backdrop,.drawer-backdrop,.loading-cover{z-index:20;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background:#0f172a6b;place-items:center;padding:20px;display:grid;position:fixed;inset:0}.loading-cover{z-index:30;color:var(--text);background:#f8fafcc2;gap:12px}.modal-card{border:1px solid var(--line);width:min(760px,100%);max-height:min(760px,100vh - 40px);box-shadow:var(--shadow);background:#fff;border-radius:18px;overflow:auto}.modal-card.wide{width:min(980px,100%)}.modal-head{z-index:1;border-bottom:1px solid var(--line);background:#fffffff5;justify-content:space-between;align-items:center;gap:12px;padding:18px 20px;display:flex;position:sticky;top:0}.modal-head h3{margin:0}.modal-body{padding:20px}.modal-grid{gap:16px;display:grid}.modal-section{border:1px solid var(--line);background:#f8fafc;border-radius:14px;gap:12px;padding:16px;display:grid}.modal-section h4{margin:0}.modal-section p,.contact-notes span{color:var(--text-muted);margin:0;font-size:13px;line-height:1.6}.import-textarea{min-height:220px}.contact-notes{gap:14px;display:grid}.contact-note,.contact-qr{gap:6px;display:grid}.contact-note{border:1px solid var(--line);background:var(--panel-soft);border-radius:16px;padding:14px 16px}.contact-note strong{font-size:14px;line-height:1.4}.contact-note span,.contact-note a,.contact-note code{color:var(--text-soft);word-break:break-word;font-size:13px;line-height:1.6}.contact-qr{border:1px solid var(--line);background:#fff;border-radius:20px;gap:10px;padding:14px}.contact-qr img{border:1px solid var(--line);background:var(--panel-soft);border-radius:16px;width:100%;max-width:420px;height:auto;display:block}.contact-qr span{color:var(--text-muted);font-size:12px;line-height:1.6}.image-preview-stage{background:#0f172a;border-radius:14px;place-items:center;min-height:320px;display:grid;overflow:hidden}.image-preview-stage img{max-width:100%;max-height:min(70vh,680px);display:block}.preview-modal-meta{color:var(--text-soft);word-break:break-word;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;font-size:13px;line-height:1.7;display:flex}.spin{animation:.9s linear infinite spin}@keyframes spin{to{transform:rotate(360deg)}}.launch-page{grid-template-columns:minmax(420px,.82fr) minmax(520px,1.18fr);align-items:stretch;gap:16px;min-height:calc(100vh - 172px);display:grid}.launch-copy,.launch-visual{border:1px solid var(--line);min-width:0;box-shadow:var(--shadow-sm);background:#fffffff0;border-radius:20px}.launch-copy{align-content:center;gap:20px;padding:28px;display:grid}.launch-identity{gap:18px;display:grid}.launch-app-icon{background:linear-gradient(180deg, #ffffffe6, #f8fafcd1), var(--surface);border:1px solid #635bff2e;border-radius:24px;place-items:center;width:96px;height:96px;display:grid;box-shadow:0 22px 44px #0f172a1f,0 0 0 8px #635bff0f}.launch-app-icon img{width:100%;height:100%;display:block}.launch-copy h2{max-width:560px;color:var(--text);letter-spacing:0;margin:0;font-size:44px;line-height:1.02}.launch-copy p{max-width:520px;color:var(--text-soft);margin:0;font-size:15px;line-height:1.8}.launch-metrics{grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;display:grid}.launch-metrics div{border:1px solid var(--line);background:#f8fafcd6;border-radius:14px;gap:8px;min-width:0;padding:14px;display:grid}.launch-metrics svg{color:var(--brand)}.launch-metrics span{color:var(--text-muted);font-size:11px;font-weight:800}.launch-metrics strong{min-width:0;color:var(--text);overflow-wrap:anywhere;font-size:18px;line-height:1.2}.launch-actions{flex-wrap:wrap;gap:10px;display:flex}.launch-actions .btn-secondary,.launch-actions a.btn-secondary{text-decoration:none}.launch-download-note{color:var(--text-muted);margin:-2px 0 0;font-size:12px;line-height:1.6}.launch-pills{flex-wrap:wrap;gap:8px;display:flex}.launch-pills span{min-height:28px;color:var(--text-muted);background:#635bff14;border-radius:999px;align-items:center;padding:0 10px;font-size:12px;font-weight:700;display:inline-flex}.launch-status{border-top:1px solid var(--line);grid-template-columns:repeat(2,minmax(0,1fr));gap:14px 18px;margin-top:8px;padding-top:20px;display:grid}.launch-visual{background:radial-gradient(circle at 24% 22%,#635bff26,#0000 34%),radial-gradient(circle at 82% 78%,#22c55e1f,#0000 32%),linear-gradient(#fffffff5,#f8fafcf0),#fff;place-items:center;padding:18px;display:grid;overflow:hidden}.launch-visual-stage{aspect-ratio:1.2;place-items:center;width:min(760px,100%);display:grid;position:relative}.launch-visual-mark{z-index:2;border-radius:34px;width:min(160px,24%);position:absolute;top:5%;left:4%;box-shadow:0 24px 54px #0f172a2e,0 0 0 10px #ffffffc7}.launch-visual-dashboard{object-fit:contain;filter:drop-shadow(0 28px 50px #0f172a24);width:92%;max-height:min(560px,100vh - 250px);display:block}.left-column,.right-column{gap:18px;min-width:0;display:grid}.account-grid{justify-content:stretch;align-items:start;gap:16px;width:100%;display:grid}.account-grid.profile-count-1{grid-template-columns:minmax(320px,520px)}.account-grid.profile-count-2{grid-template-columns:repeat(2,minmax(320px,1fr))}.account-grid.profile-count-3{grid-template-columns:repeat(3,minmax(300px,1fr))}.account-grid.profile-count-many{grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}.account-card{--plan-color:#94a3b8;--plan-soft:#94a3b81f;--plan-border:var(--line);--usage-color:#16a34a;--usage-soft:#16a34a1f;border-color:var(--plan-border);border-radius:16px;grid-template-rows:none;align-content:start;align-self:start;gap:12px;min-width:0;padding:14px;display:grid;position:relative;overflow:hidden}.account-card:before{content:"";background:var(--plan-color);height:3px;position:absolute;inset:0 0 auto}.account-card.plan-free{--plan-color:#94a3b8;--plan-soft:#94a3b81f;--plan-border:var(--line)}.account-card.plan-plus{--plan-color:#635bff;--plan-soft:#635bff1c;--plan-border:#635bff33}.account-card.plan-pro{--plan-color:#4f46e5;--plan-soft:#4f46e51c;--plan-border:#4f46e538}.account-card.plan-team{--plan-color:#0f766e;--plan-soft:#0f766e1c;--plan-border:#0f766e38}.account-card.plan-premium{--plan-color:#d97706;--plan-soft:#d977061f;--plan-border:#d9770657;box-shadow:0 10px 26px #b453091a, var(--shadow)}.account-card.plan-enterprise{--plan-color:#a16207;--plan-soft:#a1620724;--plan-border:#47556947;box-shadow:0 12px 28px #0f172a1a, var(--shadow)}.account-card.is-auth-invalid{--plan-color:#ef4444;--plan-soft:#ef444417;--plan-border:#ef444452;border-color:var(--plan-border);box-shadow:0 12px 28px #b91c1c14, var(--shadow);background:linear-gradient(#fef2f2e6,#fffffffa 46%),#fff}.account-card.is-auth-invalid:before{background:#ef4444;height:3px}.account-card.is-auth-invalid .usage-corner{color:#991b1b;background:linear-gradient(135deg,#fef2f2,#fee2e2);border-color:#ef444447;box-shadow:0 8px 18px #b91c1c1a,inset 0 1px #ffffffd1}.account-card.is-auth-invalid .avatar{color:#dc2626;background:#fff;border-color:#ef444475;box-shadow:0 0 0 3px #ef44441a}.account-card.is-auth-invalid .progress-bar{opacity:.72}.account-card.is-auth-invalid .usage-status-row,.account-card.is-auth-invalid .progress-track{background:#fef2f2ad}.account-card.is-auth-invalid .account-actions .btn-secondary:disabled{opacity:.86;color:#991b1b;background:#fff;border-color:#ef44442e}.account-head{justify-content:space-between;align-items:flex-start;gap:12px;padding-top:8px;display:flex}.account-title{flex:1;gap:6px;min-width:0;display:grid}.account-name{align-items:center;gap:8px;min-width:0;display:flex}.account-name strong{-webkit-line-clamp:2;text-overflow:ellipsis;white-space:normal;overflow-wrap:anywhere;-webkit-box-orient:vertical;min-width:0;font-size:13px;line-height:1.35;display:-webkit-box;overflow:hidden}.account-icon-btn{border:1px solid var(--line);width:24px;height:24px;color:var(--text-muted);background:#fff;border-radius:999px;flex:none;justify-content:center;align-items:center;padding:0;display:flex}.account-icon-btn:hover{color:var(--plan-color);border-color:var(--plan-color);background:var(--plan-soft)}.avatar{background:var(--panel-soft);border:1px solid var(--plan-color);width:24px;height:24px;box-shadow:0 0 0 3px var(--plan-soft);color:var(--plan-color);border-radius:999px;flex:none;place-items:center;font-size:11px;font-weight:700;display:grid}.badge-row{flex-wrap:wrap;gap:6px;display:flex}.account-select{border:1px solid var(--line);min-height:28px;color:var(--text-muted);cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap;background:#fff;border-radius:8px;align-items:center;gap:6px;margin-top:28px;padding:0 8px;font-size:12px;font-weight:600;display:inline-flex}.account-select input{width:14px;height:14px;margin:0}.badge{white-space:nowrap;border-radius:999px;justify-content:center;align-items:center;min-height:22px;padding:0 8px;font-size:11px;font-weight:600;display:inline-flex}.badge.brand{color:var(--plan-color);background:var(--plan-soft)}.usage-corner{color:#047857;letter-spacing:0;pointer-events:none;z-index:1;background:linear-gradient(135deg,#ecfdf5,#d1fae5);border:1px solid #10b98147;border-radius:999px;align-items:center;gap:5px;min-height:24px;padding:0 10px 0 8px;font-size:10px;font-weight:800;line-height:22px;display:inline-flex;position:absolute;top:10px;right:12px;box-shadow:0 8px 18px #10b9811f,inset 0 1px #ffffffd1}.usage-corner:before{content:"";background:currentColor;border-radius:999px;flex:none;width:6px;height:6px;box-shadow:0 0 0 3px #10b9811f}.usage-corner span{line-height:1}.usage-corner.codex-only{color:#1d4ed8;background:linear-gradient(135deg,#eff6ff,#dbeafe);border-color:#2563eb3d;box-shadow:0 8px 18px #2563eb1f,inset 0 1px #ffffffd1}.usage-corner.codex-only:before{box-shadow:0 0 0 3px #2563eb1f}.usage-corner.dual{color:#4f46e5;background:linear-gradient(135deg,#f5f3ff,#ede9fe);border-color:#635bff40;box-shadow:0 8px 18px #635bff21,inset 0 1px #ffffffd1}.usage-corner.dual:before{box-shadow:0 0 0 3px #635bff1f}.badge.green{color:#15803d;background:var(--green-soft)}.badge.orange{color:#b45309;background:var(--orange-soft)}.badge.red{color:#dc2626;background:var(--red-soft)}.badge.muted{color:var(--text-muted);background:#f1f5f9}.account-metrics{gap:10px;display:grid}.quota-row{gap:6px;display:grid}.quota-line{color:var(--text-soft);justify-content:space-between;align-items:center;gap:10px;font-size:11px;line-height:1.45;display:flex}.quota-line span{min-width:0}.quota-line strong{color:var(--text);flex-shrink:0;font-size:12px}.progress-track{background:#eef2f7;border-radius:999px;width:100%;height:5px;overflow:hidden}.progress-bar{border-radius:inherit;background:var(--brand);height:100%}.progress-bar.blue{background:var(--blue)}.progress-bar.orange{background:var(--orange)}.progress-bar.red{background:#f43f5e}.usage-status-row{background:var(--panel-soft);color:var(--text-muted);border-radius:10px;flex-wrap:nowrap;justify-content:space-between;align-items:center;gap:8px;padding:8px 10px;font-size:11px;line-height:1.4;display:flex}.usage-status{white-space:nowrap;align-items:center;gap:5px;min-width:0;font-weight:700;display:inline-flex}.usage-status svg{width:12px;height:12px;color:var(--text-muted);flex:none}.usage-dot{background:#cbd5e1;border-radius:999px;flex:none;width:6px;height:6px}.usage-dot.active{background:#22c55e;box-shadow:0 0 0 3px #22c55e1f}.usage-state-text{color:var(--text-muted);font-weight:700}.usage-status.is-active .usage-state-text{color:#15803d}.compact-meta-row{min-width:0;color:var(--text-muted);gap:8px;font-size:11px;line-height:1.45;display:grid}.compact-reset-list{flex-wrap:nowrap;align-items:center;gap:10px;min-width:0;display:flex}.compact-meta-item{flex:1 1 0;align-items:baseline;gap:5px;min-width:0;display:flex}.compact-meta-item label{color:var(--text-muted);white-space:nowrap;font-size:10px;line-height:1.4}.compact-meta-item strong{color:var(--text-soft);text-align:left;overflow-wrap:anywhere;font-size:11px;line-height:1.4}.compact-meta-actions{justify-content:center;align-items:center;gap:10px;margin-top:2px;display:flex}.compact-meta-actions:before,.compact-meta-actions:after{content:"";background:var(--line);flex:auto;min-width:18px;height:1px}.details-toggle{min-height:24px;color:var(--brand);white-space:nowrap;cursor:pointer;background:0 0;border:0;justify-content:center;align-items:center;gap:5px;padding:0 6px;font-size:11px;font-weight:700;display:inline-flex}.details-toggle:hover{color:#4338ca}.details-toggle svg{width:12px;height:12px;transition:transform .16s}.details-toggle.is-expanded svg{transform:rotate(180deg)}.meta-grid{border-top:1px solid var(--line);grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 12px;padding-top:10px;display:grid}.meta-item{gap:3px;min-width:0;display:grid}.meta-grid .service-row label{color:var(--text-muted);font-size:10px;line-height:1.4}.meta-grid .service-row strong,.meta-grid .service-row span,.meta-grid .service-row code{color:var(--text-soft);word-break:break-word;overflow-wrap:anywhere;font-size:11px;line-height:1.45}.account-actions{flex-wrap:wrap;gap:8px;margin-top:0;display:flex}.account-actions .btn-secondary,.account-actions .btn-danger{border-radius:10px;flex:120px;min-height:36px;padding:0 12px;font-size:12px}.account-actions .btn-secondary.is-current{opacity:1;color:#047857;cursor:default;background:linear-gradient(135deg,#f0fdf4,#dcfce7);border-color:#10b9815c;position:relative;box-shadow:inset 0 1px #fffc,0 6px 14px #10b98114}.account-actions .btn-secondary.is-current:before{content:"";background:#22c55e;border-radius:999px;flex:none;width:7px;height:7px;box-shadow:0 0 0 3px #22c55e1f}.account-actions .btn-secondary.is-current.codex{color:#1d4ed8;background:linear-gradient(135deg,#eff6ff,#dbeafe);border-color:#2563eb52;box-shadow:inset 0 1px #fffc,0 6px 14px #2563eb14}.account-actions .btn-secondary.is-current.codex:before{background:#3b82f6;box-shadow:0 0 0 3px #3b82f61f}.tester-card{gap:16px;display:grid}.tester-tabs,.tester-result-tabs{border-radius:14px;gap:4px;padding:6px;display:flex;overflow-x:auto}.tester-tabs{grid-template-columns:repeat(5,minmax(92px,1fr));gap:6px;display:grid}.tester-result-tabs{box-shadow:none;background:0 0;border:0;padding:0}.tab-btn{min-height:38px;color:var(--text-muted);white-space:nowrap;background:0 0;border:0;border-radius:10px;padding:0 14px;font-weight:800}.tab-btn.is-active{color:var(--brand);background:#fff;box-shadow:0 6px 16px #0f172a0f}.tester-workbench{grid-template-columns:minmax(360px,.92fr) minmax(360px,1.08fr);align-items:stretch;gap:16px;display:grid}.tester-pane{border:1px solid var(--line);background:linear-gradient(#fff,#f8fafc9e);border-radius:16px;align-content:start;gap:12px;min-width:0;padding:14px;display:grid}.tester-request-pane{grid-template-rows:auto auto minmax(180px,1fr) auto auto}.tester-body-field{grid-template-rows:auto minmax(0,1fr);min-height:0;display:grid}.tester-textarea{resize:vertical;height:100%;min-height:180px}.tester-actions-bar{grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:10px;display:grid}.tester-actions-group{gap:10px;min-width:0;display:grid}.tester-actions-bar .example-row{justify-content:flex-start;gap:8px}.tester-copy-row{flex-wrap:wrap;gap:8px;display:flex}.tester-copy-row-top{margin-top:-4px}.tester-actions-bar .btn-secondary{border-radius:10px;min-height:34px;padding:0 10px;font-size:12px}.tester-response-pane{grid-template-rows:auto minmax(0,1fr)}.tester-result-head{grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;display:grid}.tester-result-head .status-inline{text-align:right;white-space:nowrap;text-overflow:ellipsis;max-width:220px;margin:0;overflow:hidden}.edit-upload-row{flex-wrap:wrap;align-items:center;gap:10px;margin-top:-2px;display:flex}.upload-btn{cursor:pointer}.upload-btn input{display:none}.edit-upload-row span{color:var(--text-muted);font-size:12px;line-height:1.5}.pre{color:#e2e8f0;white-space:pre-wrap;overflow-wrap:anywhere;background:#0f172a;border-radius:12px;width:100%;min-height:312px;max-height:420px;margin:0;padding:16px;font-size:12px;line-height:1.6;overflow:auto}.preview-panel{min-height:312px}.preview-empty,.empty-state{border:1px dashed var(--line-strong);text-align:center;min-height:140px;color:var(--text-muted);background:#f8fafc;border-radius:14px;place-items:center;padding:20px;display:grid}.preview-grid{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:12px;display:grid}.preview-card{gap:8px;margin:0;display:grid}.preview-card button{border:1px solid var(--line);background:#fff;border-radius:12px;padding:0;overflow:hidden}.preview-card img{aspect-ratio:1;object-fit:cover;width:100%;display:block}.preview-card figcaption{color:var(--text-muted);overflow-wrap:anywhere;font-size:12px}.preview-actions{flex-wrap:wrap;gap:8px;display:flex}.preview-actions a{border:1px solid var(--line);min-height:34px;color:var(--text-soft);border-radius:10px;justify-content:center;align-items:center;padding:0 12px;font-size:12px;font-weight:600;display:inline-flex}.pre.tester-log{min-height:200px}.network-page{gap:16px;display:grid}.network-toolbar{justify-content:space-between;align-items:center;gap:12px;display:flex}.network-updated{color:var(--text-muted);align-items:center;gap:8px;font-size:12px;font-weight:650;display:inline-flex}.network-toolbar-status{color:var(--text-soft);font-size:12px;font-weight:700}.network-error{color:var(--red);margin:0;font-size:12px;font-weight:700}.network-summary-grid{grid-template-columns:repeat(4,minmax(0,1fr))}.network-grid{grid-template-columns:minmax(0,1.2fr) minmax(340px,.8fr);align-items:start;gap:18px;display:grid}.network-stack,.network-side{gap:18px;min-width:0;display:grid}.network-section{gap:14px;display:grid}.network-glass-panel{-webkit-backdrop-filter:blur(22px)saturate(140%);background:radial-gradient(circle at 12% 0,#60a5fa2e,#0000 34%),radial-gradient(circle at 88% 8%,#a78bfa29,#0000 36%),linear-gradient(135deg,#eff6ffeb,#f5f3ffb8 48%,#ecfdf5bd),#ffffffad;border-color:#818cf833;border-radius:24px;padding:26px;position:relative;overflow:hidden;box-shadow:inset 0 1px #ffffffe0,0 1px #0f172a0a,0 26px 60px #1e293b1c}.network-glass-panel:before{content:"";pointer-events:none;border:1px solid #ffffff8a;border-radius:20px;position:absolute;inset:10px}.network-glass-panel>*{position:relative}.network-dual{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;display:grid}.network-three-up{grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;display:grid}.network-metric,.network-block{border:1px solid var(--line);background:linear-gradient(#f8fafcad,#fffffffa);border-radius:14px;min-width:0;padding:12px 13px}.network-metric span,.network-block span{color:var(--text-muted);letter-spacing:0;font-size:10px;font-weight:800;line-height:1.2;display:block}.network-metric strong,.network-block strong{color:var(--text);overflow-wrap:anywhere;margin-top:5px;font-size:14px;line-height:1.25;display:block}.network-metric p,.network-block p{color:var(--text-muted);overflow-wrap:anywhere;margin:6px 0 0;font-size:12px;line-height:1.45}.network-chip{white-space:nowrap;border:1px solid #0000;border-radius:999px;align-items:center;min-height:26px;padding:0 10px;font-size:11px;font-weight:800;display:inline-flex}.network-chip.green,.network-metric.green,.network-block.green{background-color:#22c55e14;border-color:#22c55e29}.network-chip.green{color:var(--green)}.network-chip.orange,.network-metric.orange,.network-block.orange{background-color:#f59e0b14;border-color:#f59e0b29}.network-chip.orange{color:var(--orange)}.network-chip.red,.network-metric.red,.network-block.red{background-color:#ef444414;border-color:#ef444429}.network-chip.red{color:var(--red)}.network-chip.blue,.network-metric.blue,.network-block.blue{background-color:#3b82f614;border-color:#3b82f629}.network-chip.blue{color:var(--blue)}.network-chip.slate,.network-metric.slate,.network-block.slate{background-color:#64748b14;border-color:#64748b29}.network-chip.slate{color:var(--text-muted)}.network-list,.network-probe-list{gap:10px;display:grid}.platform-grid{grid-template-columns:1fr;gap:16px;display:grid}.platform-summary-strip{flex-wrap:wrap;justify-content:flex-end;gap:10px;margin-left:auto;display:inline-flex}.platform-summary-chip{-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);background:#fff9;border:1px solid #ffffff8a;border-radius:16px;gap:2px;min-width:74px;padding:12px 14px;display:inline-grid;box-shadow:inset 0 1px #ffffffc7,0 10px 24px #0f172a0f}.platform-summary-chip strong{color:var(--text);font-size:20px;font-weight:900;line-height:1}.platform-summary-chip span{color:var(--text-muted);font-size:10px;font-weight:800;line-height:1.1}.platform-summary-chip.green{background:linear-gradient(#dcfce7db,#f0fdf494);border-color:#4ade8057}.platform-summary-chip.orange{background:linear-gradient(#fef3c7e0,#fffbeb94);border-color:#fbbf245c}.platform-summary-chip.red{background:linear-gradient(#fee2e2e0,#fef2f294);border-color:#f871715c}.platform-summary-chip.slate{background:linear-gradient(#f1f5f9e6,#ffffff8f)}.platform-card{-webkit-backdrop-filter:blur(18px)saturate(135%);background:linear-gradient(#ffffffc2,#f8fafc8f),#ffffff80;border:1px solid #ffffff85;border-radius:20px;min-width:0;padding:20px 22px 18px;position:relative;overflow:hidden;box-shadow:inset 0 1px #ffffffdb,0 14px 34px #1e293b14}.platform-card:before{content:"";background:#94a3b852;width:5px;position:absolute;inset:0 auto 0 0}.platform-card.green:before{background:linear-gradient(#22c55e,#22c55e33)}.platform-card.green{background:radial-gradient(circle at 0 0,#22c55e24,#0000 34%),linear-gradient(#ffffffc7,#f0fdf485);border-color:#4ade803d}.platform-card.orange:before{background:linear-gradient(#f59e0b,#f59e0b33)}.platform-card.orange{background:radial-gradient(circle at 0 0,#f59e0b26,#0000 34%),linear-gradient(#ffffffc7,#fffbeb8c);border-color:#fbbf2442}.platform-card.red:before{background:linear-gradient(#ef4444,#ef444433)}.platform-card.red{background:radial-gradient(circle at 0 0,#ef444426,#0000 34%),linear-gradient(#ffffffc7,#fef2f28c);border-color:#f8717147}.platform-card-head{justify-content:space-between;align-items:center;gap:18px;display:flex}.platform-card-left{align-items:center;gap:18px;min-width:0;display:flex}.platform-card-left>div{gap:4px;min-width:0;display:grid}.platform-card-left strong{color:var(--text);font-size:17px;font-weight:900;line-height:1.2}.platform-card-left span{color:var(--text-muted);font-size:13px;font-weight:700;line-height:1.35}.platform-icon{color:#fff;letter-spacing:0;background:linear-gradient(135deg,#64748b,#334155);border-radius:15px;flex:none;place-items:center;width:46px;height:46px;font-size:14px;font-weight:900;display:grid;box-shadow:inset 0 1px #ffffff47,0 10px 22px #0f172a1f}.platform-icon img{filter:brightness(0)invert();width:21px;height:21px;display:block}.platform-icon.green{color:#fff;background:linear-gradient(135deg,#22c55e,#16a34a)}.platform-icon.orange{color:#fff;background:linear-gradient(135deg,#f59e0b,#d97706)}.platform-icon.red{color:#fff;background:linear-gradient(135deg,#ef4444,#dc2626)}.platform-icon.platform-google{background:radial-gradient(circle at 28% 25%,#fffffff2,#0000 0 16%,#0000 17%),conic-gradient(from 35deg,#4285f4,#34a853 32%,#fbbc05 58%,#ea4335 78%,#4285f4)}.platform-icon.platform-google img{width:23px;height:23px}.platform-icon.platform-chatgpt{background:linear-gradient(135deg,#10a37f,#0f766e 62%,#064e3b)}.platform-icon.platform-claude{background:linear-gradient(135deg,#d97706,#b45309 58%,#78350f)}.platform-icon.platform-youtube{background:linear-gradient(135deg,#f03,#dc2626 58%,#991b1b)}.platform-icon.platform-x{background:linear-gradient(135deg,#020617,#111827 58%,#475569)}.platform-state{flex:none;align-items:center;gap:14px;display:inline-flex}.platform-state-dot{background:#94a3b8;border:0;border-radius:999px;width:14px;height:14px;box-shadow:0 0 0 6px #94a3b81a,0 0 16px #94a3b838}.platform-state-dot.green{background:#22c55e;box-shadow:0 0 0 6px #22c55e29,0 0 16px #22c55e57}.platform-state-dot.orange{background:#f59e0b;box-shadow:0 0 0 6px #f59e0b29,0 0 16px #f59e0b57}.platform-state-dot.red{background:#ef4444;box-shadow:0 0 0 6px #ef444429,0 0 16px #ef444457}.platform-card .network-chip{min-height:24px;padding:0 9px;font-size:10px}.platform-state-copy{text-align:right;gap:1px;display:grid}.platform-state-copy strong{color:var(--text);font-size:17px;font-weight:900;line-height:1.15}.platform-state-copy span{color:var(--text-muted);font-size:12px;font-weight:800;line-height:1.2}.platform-state-copy.green strong,.platform-state-copy.green span{color:var(--green)}.platform-state-copy.orange strong,.platform-state-copy.orange span{color:var(--orange)}.platform-state-copy.red strong,.platform-state-copy.red span{color:var(--red)}.platform-card-foot{border-top:1px solid #94a3b81f;flex-wrap:wrap;align-items:center;gap:10px;margin-top:18px;padding-top:14px;display:flex}.platform-url-chip,.platform-latency-chip{min-height:28px;color:var(--text);white-space:nowrap;background:#ffffffa8;border:1px solid #ffffff85;border-radius:999px;align-items:center;padding:0 12px;font-size:12px;font-weight:800;display:inline-flex;box-shadow:inset 0 1px #ffffffb8}.platform-latency-chip{color:var(--text)}.platform-detail-copy{color:var(--text-muted);font-size:13px;font-weight:700;line-height:1.35}.platform-card.green .platform-url-chip,.platform-card.green .platform-latency-chip{border-color:#22c55e29}.platform-card.orange .platform-url-chip,.platform-card.orange .platform-latency-chip{border-color:#f59e0b29}.platform-card.red .platform-url-chip,.platform-card.red .platform-latency-chip{border-color:#ef444429}.dns-chip-row{flex-wrap:wrap;gap:8px;display:flex}.dns-chip{border:1px solid #0000;border-radius:999px;align-items:center;min-height:28px;padding:0 10px;font-size:11px;font-weight:800;display:inline-flex}.dns-chip.green{color:var(--green);background:#22c55e14;border-color:#22c55e29}.dns-chip.orange{color:var(--orange);background:#f59e0b14;border-color:#f59e0b29}.network-list-item{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffffc7;border:1px solid #94a3b824;border-radius:14px;gap:8px;padding:12px 13px;display:grid}.network-list-item p{color:var(--text-soft);margin:0;font-size:12px;line-height:1.45}.network-probe-item{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffffc7;border:1px solid #94a3b824;border-radius:14px;justify-content:space-between;align-items:center;gap:12px;padding:12px 13px;display:flex}.network-probe-item strong{color:var(--text);font-size:13px;line-height:1.3;display:block}.network-probe-item span{color:var(--text-muted);margin-top:4px;font-size:11px;line-height:1.35;display:block}.network-summary-grid .summary-card strong{font-size:18px}.network-summary-grid .summary-card p{font-size:11px}.network-dns-grid .network-block strong{font-size:15px}.network-page .section-head.compact h3{font-size:18px}@media (width<=1120px){.network-summary-grid,.network-grid{grid-template-columns:1fr}.network-toolbar{flex-direction:column;align-items:flex-start}.platform-summary-strip{justify-content:flex-start;margin-left:0}}@media (width<=900px){.network-dual,.network-three-up{grid-template-columns:1fr}.network-probe-item,.platform-card-head{flex-direction:column;align-items:flex-start}.platform-state{justify-content:space-between;align-self:flex-start;width:100%}.platform-state-copy{text-align:left}}.table-scroller{border:1px solid var(--line);background:#fff;border-radius:14px;overflow-x:auto}table{border-collapse:collapse;width:100%;min-width:720px}th,td{border-bottom:1px solid var(--line);text-align:left;vertical-align:top;color:var(--text-soft);padding:13px 12px;font-size:13px}th{color:var(--text-muted);background:#f8fafc;font-size:12px;font-weight:800}tbody tr:last-child td{border-bottom:0}td code{color:var(--text)}.method-pill,.status-pill{white-space:nowrap;border-radius:999px;justify-content:center;align-items:center;min-height:24px;padding:0 8px;font-size:11px;font-weight:800;display:inline-flex}.method-pill{color:#334155;background:#f1f5f9}.method-post{color:#4f46e5;background:#635bff1a}.method-get,.status-pill.is-ok{color:#047857;background:#22c55e1f}.status-pill.is-error{color:#dc2626;background:#ef44441f}.table-footer{color:var(--text-muted);margin-top:10px;font-size:12px}.log-toolbar{flex-wrap:wrap;gap:10px;margin-bottom:16px;display:flex}.log-search{flex:280px}.filter-chip{border:1px solid var(--line);background:#fff;border-radius:12px;align-items:center;gap:8px;min-height:40px;padding:0 12px;display:inline-flex}.filter-chip select{color:var(--text-soft);background:0 0;border:0;outline:none;font-size:13px;font-weight:700}.table-scroller tbody tr{cursor:pointer}.table-scroller tbody tr.is-selected{background:#635bff0a}.log-detail-panel{border:1px solid var(--line);background:#fffffff0;border-radius:16px;margin-top:16px;padding:18px 20px}.log-detail-head{justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:14px;display:flex}.log-detail-head h3{margin:0}.log-detail-head p{color:var(--text-muted);margin:6px 0 0;font-size:13px}.log-detail-grid{grid-template-columns:minmax(220px,.7fr) minmax(0,1.3fr);gap:14px;display:grid}.log-detail-meta{gap:10px;display:grid}.log-detail-meta div{gap:4px;display:grid}.log-detail-meta span{color:var(--text-muted);font-size:12px}.log-detail-meta strong{color:var(--text-soft);font-size:13px}.log-detail-pre{min-height:220px;max-height:320px}.switch-line{color:var(--text-soft);align-items:flex-start;gap:10px;font-size:13px;font-weight:700;line-height:1.5;display:flex}.settings-page{gap:18px;display:grid}.settings-page-head{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.settings-page-kicker{color:var(--brand);align-items:center;gap:6px;font-size:12px;font-weight:850;display:inline-flex}.settings-page-head h2{margin:8px 0 0;font-size:28px;line-height:1.15}.settings-page-head p{color:var(--text-muted);margin:8px 0 0;font-size:14px;line-height:1.6}.settings-page-actions{flex-wrap:wrap;justify-content:flex-end;gap:10px;display:flex}.settings-grid{grid-template-columns:repeat(4,minmax(220px,1fr));gap:14px;display:grid}.settings-section{border:1px solid var(--line);box-shadow:var(--shadow-sm);background:#fffffff0;border-radius:16px;gap:13px;padding:18px 20px;display:grid}.settings-section h4{margin:0;font-size:16px}@media (width<=1380px){.main-grid{grid-template-columns:1fr}.right-column{grid-template-columns:minmax(0,1fr)}.tester-card{order:-1}}@media (width<=1120px){.app-shell{grid-template-columns:1fr;width:calc(100vw - 20px);padding:10px}.sidebar{max-height:none;position:relative;top:0}.nav{grid-template-columns:repeat(6,minmax(max-content,1fr));padding-bottom:2px;overflow-x:auto}.nav-item{justify-content:center}.sidebar-status{display:none}.topbar{grid-template-columns:1fr}.top-actions{justify-content:flex-start}}@media (width<=900px){.app-shell{gap:12px;padding:0}.sidebar,.card,.trend-card,.log-table-wrap,.update-panel{border-left:0;border-right:0;border-radius:0}.sidebar{padding:16px 12px}.brand span,.sidebar-status{display:none}.nav{grid-template-columns:none;grid-auto-columns:max-content;grid-auto-flow:column}.nav-item{width:auto}.main{gap:12px}.topbar{padding:0 12px}.page-title h1{font-size:25px}.top-actions{flex-wrap:nowrap;padding-bottom:4px;overflow-x:auto}.summary-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;padding:0 12px}.summary-card{min-height:82px;padding:14px}.summary-card strong{font-size:19px}.main-grid{gap:12px}.section-head,.update-panel,.settings-drawer-footer{display:grid}.section-actions,.button-row,.example-row{justify-content:flex-start}.filter-row{display:flex}.filter-row .control{flex:100%;min-width:0}.account-grid,.account-grid.profile-count-1,.account-grid.profile-count-2,.account-grid.profile-count-3,.account-grid.profile-count-many,.meta-grid{grid-template-columns:1fr}.account-card{border-radius:14px;padding:16px}.account-head{flex-direction:column;align-items:stretch}.account-select{width:max-content;margin-top:0}.compact-grid{grid-template-columns:1fr}.account-actions{display:flex}.tester-textarea{min-height:150px}.pre{max-height:320px}.modal-backdrop,.drawer-backdrop{padding:0}.modal-card,.settings-drawer{border-radius:0;width:100%;height:100%;max-height:none}.modal-body,.settings-drawer-body,.settings-drawer-head,.settings-drawer-footer{padding:16px}}@media (width<=460px){.summary-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.btn-primary,.btn-secondary,.btn-danger{min-height:40px;padding:0 12px}.card,.trend-card,.log-table-wrap{padding:16px 12px}}@media (width<=1120px){body{min-width:0}.app-shell{grid-template-columns:1fr;gap:14px;width:calc(100vw - 24px);padding:0}.sidebar{border-radius:24px;gap:14px;max-height:none;padding:22px 18px;position:relative;top:0}.brand span,.sidebar-status{display:none}.nav{grid-template-columns:repeat(5,minmax(max-content,1fr));grid-auto-columns:max-content;grid-auto-flow:column;padding-bottom:2px;overflow-x:auto}.nav-item{justify-content:center;width:auto}.topbar{grid-template-columns:1fr;padding:0}.page-title p{max-width:680px}.top-actions{flex-wrap:wrap;justify-content:flex-start;overflow:visible}.summary-grid{grid-template-columns:repeat(2,minmax(0,1fr));padding:0}.overview-grid,.tester-route-grid,.main-grid{grid-template-columns:1fr}.sidebar,.card,.trend-card,.log-table-wrap,.update-panel{border-left:1px solid var(--line);border-right:1px solid var(--line);border-radius:20px}.launch-page{grid-template-columns:1fr}.launch-visual{min-height:420px}.account-grid.profile-count-2{grid-template-columns:repeat(2,minmax(340px,1fr))}.account-grid.profile-count-3{grid-template-columns:repeat(3,minmax(320px,1fr))}.account-grid.profile-count-many{grid-template-columns:repeat(auto-fit,minmax(340px,1fr))}.meta-grid,.compact-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="/assets/app-mark-Gd2QnHMO.svg" />
|
|
7
|
+
<title>AI Zero Token</title>
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CCywUil_.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DNzR8XR7.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="root"></div>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
package/build/icon.icns
ADDED
|
Binary file
|
package/build/icon.ico
ADDED
|
Binary file
|
package/build/icon.png
ADDED
|
Binary file
|
package/dist/core/context.js
CHANGED
|
@@ -4,12 +4,14 @@ import { AuthService } from "./services/auth-service.js";
|
|
|
4
4
|
import { ChatService } from "./services/chat-service.js";
|
|
5
5
|
import { ImageService } from "./services/image-service.js";
|
|
6
6
|
import { ModelService } from "./services/model-service.js";
|
|
7
|
+
import { NetworkDetectService } from "./services/network-detect-service.js";
|
|
7
8
|
import { VersionService } from "./services/version-service.js";
|
|
8
9
|
function createGatewayContext() {
|
|
9
10
|
const configService = new ConfigService();
|
|
10
11
|
const authService = new AuthService(configService);
|
|
11
12
|
const modelService = new ModelService(configService);
|
|
12
13
|
const versionService = new VersionService();
|
|
14
|
+
const networkDetectService = new NetworkDetectService();
|
|
13
15
|
const chatService = new ChatService({
|
|
14
16
|
authService,
|
|
15
17
|
modelService
|
|
@@ -23,6 +25,7 @@ function createGatewayContext() {
|
|
|
23
25
|
authService,
|
|
24
26
|
modelService,
|
|
25
27
|
versionService,
|
|
28
|
+
networkDetectService,
|
|
26
29
|
chatService,
|
|
27
30
|
imageService
|
|
28
31
|
};
|
|
@@ -19,8 +19,14 @@ function finalizeTiming(startedAt, phases) {
|
|
|
19
19
|
totalMs: roundMs(performance.now() - startedAt)
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
+
function safeConsole(method, message, meta) {
|
|
23
|
+
try {
|
|
24
|
+
console[method](message, meta);
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
}
|
|
22
28
|
function logHttpTiming(params) {
|
|
23
|
-
|
|
29
|
+
safeConsole("info", "[http] request timing", {
|
|
24
30
|
requestId: params.requestId,
|
|
25
31
|
method: params.method,
|
|
26
32
|
url: params.url,
|
|
@@ -127,7 +133,7 @@ async function runCurlRequest(init, params) {
|
|
|
127
133
|
try {
|
|
128
134
|
headers = normalizeCurlHeaders(JSON.parse(headersText));
|
|
129
135
|
} catch (error) {
|
|
130
|
-
|
|
136
|
+
safeConsole("warn", "[http] failed to parse curl response headers", {
|
|
131
137
|
requestId,
|
|
132
138
|
url: init.url,
|
|
133
139
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -160,7 +166,7 @@ async function loadNetworkProxySettings() {
|
|
|
160
166
|
const settings = await loadSettings();
|
|
161
167
|
return settings.networkProxy;
|
|
162
168
|
} catch (error) {
|
|
163
|
-
|
|
169
|
+
safeConsole("warn", "[http] failed to load network proxy settings", {
|
|
164
170
|
error: error instanceof Error ? error.message : String(error)
|
|
165
171
|
});
|
|
166
172
|
return void 0;
|
|
@@ -208,7 +214,7 @@ async function requestText(init) {
|
|
|
208
214
|
};
|
|
209
215
|
} catch (error) {
|
|
210
216
|
const message = error instanceof Error ? error.message : String(error);
|
|
211
|
-
|
|
217
|
+
safeConsole("warn", "[http] fetch attempt failed", {
|
|
212
218
|
requestId,
|
|
213
219
|
method: init.method,
|
|
214
220
|
url: init.url,
|
|
@@ -36,6 +36,24 @@ function parseOptionalText(value) {
|
|
|
36
36
|
const trimmed = value.trim();
|
|
37
37
|
return trimmed ? trimmed : void 0;
|
|
38
38
|
}
|
|
39
|
+
function parseUpstreamErrorBody(body) {
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(body);
|
|
42
|
+
const error = parsed.error;
|
|
43
|
+
if (!error || typeof error !== "object") {
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
const record = error;
|
|
47
|
+
return {
|
|
48
|
+
message: typeof record.message === "string" ? record.message : void 0,
|
|
49
|
+
type: typeof record.type === "string" ? record.type : void 0,
|
|
50
|
+
code: typeof record.code === "string" ? record.code : void 0,
|
|
51
|
+
param: typeof record.param === "string" || record.param === null ? record.param : void 0
|
|
52
|
+
};
|
|
53
|
+
} catch {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
39
57
|
function extractCodexQuotaSnapshot(headers, requestId) {
|
|
40
58
|
const activeLimit = parseOptionalText(headers["x-codex-active-limit"]);
|
|
41
59
|
const planType = parseOptionalText(headers["x-codex-plan-type"]);
|
|
@@ -280,8 +298,13 @@ async function askOpenAICodex(params) {
|
|
|
280
298
|
});
|
|
281
299
|
const quota = extractCodexQuotaSnapshot(response.headers, response.requestId);
|
|
282
300
|
if (response.status < 200 || response.status >= 300) {
|
|
301
|
+
const upstreamError = parseUpstreamErrorBody(response.body);
|
|
283
302
|
const error = new Error(`\u8C03\u7528 Responses API \u5931\u8D25: HTTP ${response.status} via ${response.transport} ${response.body}`);
|
|
284
303
|
error.quota = quota;
|
|
304
|
+
error.upstreamStatus = response.status;
|
|
305
|
+
error.upstreamErrorCode = upstreamError?.code;
|
|
306
|
+
error.upstreamErrorType = upstreamError?.type;
|
|
307
|
+
error.upstreamErrorMessage = upstreamError?.message;
|
|
285
308
|
throw error;
|
|
286
309
|
}
|
|
287
310
|
return {
|
|
@@ -54,6 +54,23 @@ function extractEmailFromPayload(payload) {
|
|
|
54
54
|
}
|
|
55
55
|
return void 0;
|
|
56
56
|
}
|
|
57
|
+
function parseUpstreamErrorBody(body) {
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(body);
|
|
60
|
+
const error = parsed.error;
|
|
61
|
+
if (!error || typeof error !== "object") {
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
const record = error;
|
|
65
|
+
return {
|
|
66
|
+
message: typeof record.message === "string" ? record.message : void 0,
|
|
67
|
+
type: typeof record.type === "string" ? record.type : void 0,
|
|
68
|
+
code: typeof record.code === "string" ? record.code : void 0
|
|
69
|
+
};
|
|
70
|
+
} catch {
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
57
74
|
function parseAuthorizationInput(value) {
|
|
58
75
|
const trimmed = value.trim();
|
|
59
76
|
if (!trimmed) {
|
|
@@ -143,7 +160,13 @@ async function refreshOpenAICodexToken(profile) {
|
|
|
143
160
|
}).toString()
|
|
144
161
|
});
|
|
145
162
|
if (response.status < 200 || response.status >= 300) {
|
|
146
|
-
|
|
163
|
+
const upstreamError = parseUpstreamErrorBody(response.body);
|
|
164
|
+
const error = new Error(`\u5237\u65B0 token \u5931\u8D25: HTTP ${response.status} via ${response.transport} ${response.body}`);
|
|
165
|
+
error.upstreamStatus = response.status;
|
|
166
|
+
error.upstreamErrorCode = upstreamError?.code;
|
|
167
|
+
error.upstreamErrorType = upstreamError?.type;
|
|
168
|
+
error.upstreamErrorMessage = upstreamError?.message;
|
|
169
|
+
throw error;
|
|
147
170
|
}
|
|
148
171
|
const json = JSON.parse(response.body);
|
|
149
172
|
if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
@@ -50,9 +50,44 @@ class AuthService {
|
|
|
50
50
|
expiresAt: profile.expires,
|
|
51
51
|
accessTokenPreview: this.maskSecret(profile.access),
|
|
52
52
|
refreshTokenPreview: this.maskSecret(profile.refresh),
|
|
53
|
-
isActive: profile.profileId === activeProfileId
|
|
53
|
+
isActive: profile.profileId === activeProfileId,
|
|
54
|
+
authStatus: profile.authStatus
|
|
54
55
|
};
|
|
55
56
|
}
|
|
57
|
+
createOkAuthStatus() {
|
|
58
|
+
return {
|
|
59
|
+
state: "ok",
|
|
60
|
+
checkedAt: Date.now()
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
createAuthStatusFromError(error) {
|
|
64
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
65
|
+
const details = normalized;
|
|
66
|
+
const httpStatus = typeof details.upstreamStatus === "number" ? details.upstreamStatus : void 0;
|
|
67
|
+
const code = typeof details.upstreamErrorCode === "string" ? details.upstreamErrorCode : void 0;
|
|
68
|
+
const upstreamMessage = typeof details.upstreamErrorMessage === "string" ? details.upstreamErrorMessage : void 0;
|
|
69
|
+
const message = upstreamMessage || normalized.message;
|
|
70
|
+
const fingerprint = `${code || ""} ${message}`.toLowerCase();
|
|
71
|
+
if (code === "token_invalidated" || fingerprint.includes("token_invalidated") || fingerprint.includes("authentication token has been invalidated")) {
|
|
72
|
+
return {
|
|
73
|
+
state: "token_invalidated",
|
|
74
|
+
checkedAt: Date.now(),
|
|
75
|
+
message,
|
|
76
|
+
code: code || "token_invalidated",
|
|
77
|
+
httpStatus
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (httpStatus === 401 || httpStatus === 403 || /http\s+40[13]/i.test(message) || fingerprint.includes("\u5237\u65B0 token \u5931\u8D25")) {
|
|
81
|
+
return {
|
|
82
|
+
state: "auth_error",
|
|
83
|
+
checkedAt: Date.now(),
|
|
84
|
+
message,
|
|
85
|
+
code,
|
|
86
|
+
httpStatus
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return void 0;
|
|
90
|
+
}
|
|
56
91
|
getQuotaPercents(profile) {
|
|
57
92
|
const quota = profile.quota;
|
|
58
93
|
if (!quota) {
|
|
@@ -67,9 +102,15 @@ class AuthService {
|
|
|
67
102
|
return percents.length > 0 && percents.some((value) => value >= 100);
|
|
68
103
|
}
|
|
69
104
|
hasKnownAvailableQuota(profile) {
|
|
105
|
+
if (profile.authStatus?.state === "token_invalidated" || profile.authStatus?.state === "auth_error") {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
70
108
|
const percents = this.getQuotaPercents(profile);
|
|
71
109
|
return percents.length > 0 && percents.every((value) => value < 100);
|
|
72
110
|
}
|
|
111
|
+
hasInvalidAuthStatus(profile) {
|
|
112
|
+
return profile.authStatus?.state === "token_invalidated" || profile.authStatus?.state === "auth_error";
|
|
113
|
+
}
|
|
73
114
|
getQuotaUsageScore(profile) {
|
|
74
115
|
const percents = this.getQuotaPercents(profile);
|
|
75
116
|
if (percents.length === 0) {
|
|
@@ -77,6 +118,47 @@ class AuthService {
|
|
|
77
118
|
}
|
|
78
119
|
return Math.max(...percents);
|
|
79
120
|
}
|
|
121
|
+
async applyProfileRuntimeUpdate(profileId, provider, updater, options) {
|
|
122
|
+
const updated = await updateProfile(profileId, (profile) => {
|
|
123
|
+
if (profile.provider !== provider) {
|
|
124
|
+
return profile;
|
|
125
|
+
}
|
|
126
|
+
return updater(profile);
|
|
127
|
+
});
|
|
128
|
+
if (!options?.checkAutoSwitch || options.skipAutoSwitch || !updated || updated.provider !== provider || !this.isQuotaExhausted(updated)) {
|
|
129
|
+
return updated;
|
|
130
|
+
}
|
|
131
|
+
const activeProfile = await this.getActiveProfile(provider);
|
|
132
|
+
if (activeProfile?.profileId === updated.profileId) {
|
|
133
|
+
await this.maybeAutoSwitchProfile(updated, provider);
|
|
134
|
+
}
|
|
135
|
+
return updated;
|
|
136
|
+
}
|
|
137
|
+
async refreshStoredProfile(profile, provider) {
|
|
138
|
+
try {
|
|
139
|
+
const refreshed = await refreshOpenAICodexToken(profile);
|
|
140
|
+
const merged = await this.applyProfileRuntimeUpdate(
|
|
141
|
+
profile.profileId,
|
|
142
|
+
provider,
|
|
143
|
+
(current) => ({
|
|
144
|
+
...refreshed,
|
|
145
|
+
email: refreshed.email ?? current.email,
|
|
146
|
+
quota: current.quota,
|
|
147
|
+
authStatus: this.createOkAuthStatus()
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
return this.toManagedProfile(merged ?? {
|
|
151
|
+
...refreshed,
|
|
152
|
+
quota: profile.quota,
|
|
153
|
+
authStatus: this.createOkAuthStatus()
|
|
154
|
+
});
|
|
155
|
+
} catch (error) {
|
|
156
|
+
await this.recordProfileRequestFailure(profile.profileId, error, void 0, provider, {
|
|
157
|
+
skipAutoSwitch: true
|
|
158
|
+
});
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
80
162
|
async maybeAutoSwitchProfile(profile, provider) {
|
|
81
163
|
const settings = await this.configService.getSettings();
|
|
82
164
|
if (!settings.autoSwitch.enabled || !this.isQuotaExhausted(profile)) {
|
|
@@ -237,9 +319,7 @@ class AuthService {
|
|
|
237
319
|
if (Date.now() < profile.expires) {
|
|
238
320
|
return profile;
|
|
239
321
|
}
|
|
240
|
-
|
|
241
|
-
await saveProfile(refreshed);
|
|
242
|
-
return this.toManagedProfile(refreshed);
|
|
322
|
+
return this.refreshStoredProfile(profile, provider);
|
|
243
323
|
}
|
|
244
324
|
async requireFreshProfileWithIdToken(profileId, provider = "openai-codex") {
|
|
245
325
|
const profiles = await listProfiles();
|
|
@@ -250,8 +330,7 @@ class AuthService {
|
|
|
250
330
|
if (profile.idToken && Date.now() < profile.expires) {
|
|
251
331
|
return this.toManagedProfile(profile);
|
|
252
332
|
}
|
|
253
|
-
const refreshed = await
|
|
254
|
-
await saveProfile(refreshed);
|
|
333
|
+
const refreshed = await this.refreshStoredProfile(profile, provider);
|
|
255
334
|
if (!refreshed.idToken) {
|
|
256
335
|
throw new Error("\u5237\u65B0 token \u6210\u529F\uFF0C\u4F46\u4E0A\u6E38\u6CA1\u6709\u8FD4\u56DE id_token\u3002");
|
|
257
336
|
}
|
|
@@ -273,9 +352,43 @@ class AuthService {
|
|
|
273
352
|
throw error;
|
|
274
353
|
}
|
|
275
354
|
const model = await this.configService.getDefaultModel(provider);
|
|
355
|
+
await this.syncQuotaForProfile(profile, model, provider, options);
|
|
356
|
+
}
|
|
357
|
+
async syncProfileQuota(profileId, provider = "openai-codex", options) {
|
|
358
|
+
const profiles = await listProfiles();
|
|
359
|
+
const profile = profiles.find((item) => item.provider === provider && item.profileId === profileId);
|
|
360
|
+
if (!profile) {
|
|
361
|
+
throw new Error(`\u6CA1\u6709\u627E\u5230\u8D26\u53F7: ${profileId}`);
|
|
362
|
+
}
|
|
363
|
+
const model = await this.configService.getDefaultModel(provider);
|
|
364
|
+
await this.syncQuotaForProfile(profile, model, provider, options);
|
|
365
|
+
}
|
|
366
|
+
async syncAllProfileQuotas(provider = "openai-codex", options) {
|
|
367
|
+
const [profiles, activeProfile, model] = await Promise.all([
|
|
368
|
+
listProfiles(),
|
|
369
|
+
this.getActiveProfile(provider),
|
|
370
|
+
this.configService.getDefaultModel(provider)
|
|
371
|
+
]);
|
|
372
|
+
const providerProfiles = profiles.filter((profile) => profile.provider === provider);
|
|
373
|
+
const skipped = providerProfiles.filter((profile) => this.hasInvalidAuthStatus(profile)).length;
|
|
374
|
+
const targets = providerProfiles.filter((profile) => !this.hasInvalidAuthStatus(profile)).sort((left, right) => Number(left.profileId === activeProfile?.profileId) - Number(right.profileId === activeProfile?.profileId));
|
|
375
|
+
const results = [];
|
|
376
|
+
for (const profile of targets) {
|
|
377
|
+
results.push(await this.syncQuotaForProfile(profile, model, provider, options));
|
|
378
|
+
}
|
|
379
|
+
const failed = results.filter((item) => !item.ok).length;
|
|
380
|
+
return {
|
|
381
|
+
total: providerProfiles.length,
|
|
382
|
+
synced: results.length - failed,
|
|
383
|
+
failed,
|
|
384
|
+
skipped
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
async syncQuotaForProfile(profile, model, provider, options) {
|
|
276
388
|
try {
|
|
389
|
+
const usableProfile = Date.now() < profile.expires ? this.toManagedProfile(profile) : await this.refreshStoredProfile(profile, provider);
|
|
277
390
|
const result = await askOpenAICodex({
|
|
278
|
-
profile,
|
|
391
|
+
profile: usableProfile,
|
|
279
392
|
model,
|
|
280
393
|
system: "Reply with OK only.",
|
|
281
394
|
prompt: "ping",
|
|
@@ -283,44 +396,83 @@ class AuthService {
|
|
|
283
396
|
text: { verbosity: "low" }
|
|
284
397
|
}
|
|
285
398
|
});
|
|
286
|
-
await this.
|
|
399
|
+
await this.recordProfileRequestSuccess(usableProfile.profileId, result.quota, provider, {
|
|
287
400
|
skipAutoSwitch: options?.skipAutoSwitch
|
|
288
401
|
});
|
|
402
|
+
return {
|
|
403
|
+
ok: true,
|
|
404
|
+
profileId: usableProfile.profileId
|
|
405
|
+
};
|
|
289
406
|
} catch (error) {
|
|
290
407
|
const quota = error.quota;
|
|
291
|
-
await this.
|
|
408
|
+
await this.recordProfileRequestFailure(profile.profileId, error, quota, provider, {
|
|
292
409
|
skipAutoSwitch: options?.skipAutoSwitch
|
|
293
410
|
});
|
|
294
411
|
if (!options?.suppressErrors) {
|
|
295
412
|
throw error;
|
|
296
413
|
}
|
|
297
|
-
console.warn("[auth] sync
|
|
414
|
+
console.warn("[auth] sync profile quota failed", {
|
|
298
415
|
provider,
|
|
299
416
|
profileId: profile.profileId,
|
|
300
417
|
error: error instanceof Error ? error.message : String(error)
|
|
301
418
|
});
|
|
419
|
+
return {
|
|
420
|
+
ok: false,
|
|
421
|
+
profileId: profile.profileId,
|
|
422
|
+
error: error instanceof Error ? error.message : String(error)
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async recordProfileRequestSuccess(profileId, quota, provider = "openai-codex", options) {
|
|
427
|
+
await this.applyProfileRuntimeUpdate(
|
|
428
|
+
profileId,
|
|
429
|
+
provider,
|
|
430
|
+
(profile) => ({
|
|
431
|
+
...profile,
|
|
432
|
+
...quota ? { quota } : {},
|
|
433
|
+
authStatus: this.createOkAuthStatus()
|
|
434
|
+
}),
|
|
435
|
+
{
|
|
436
|
+
skipAutoSwitch: options?.skipAutoSwitch,
|
|
437
|
+
checkAutoSwitch: Boolean(quota)
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
async recordProfileRequestFailure(profileId, error, quota, provider = "openai-codex", options) {
|
|
442
|
+
const authStatus = this.createAuthStatusFromError(error);
|
|
443
|
+
if (!quota && !authStatus) {
|
|
444
|
+
return;
|
|
302
445
|
}
|
|
446
|
+
await this.applyProfileRuntimeUpdate(
|
|
447
|
+
profileId,
|
|
448
|
+
provider,
|
|
449
|
+
(profile) => ({
|
|
450
|
+
...profile,
|
|
451
|
+
...quota ? { quota } : {},
|
|
452
|
+
...authStatus ? { authStatus } : {}
|
|
453
|
+
}),
|
|
454
|
+
{
|
|
455
|
+
skipAutoSwitch: options?.skipAutoSwitch,
|
|
456
|
+
checkAutoSwitch: Boolean(quota)
|
|
457
|
+
}
|
|
458
|
+
);
|
|
303
459
|
}
|
|
304
460
|
async updateProfileQuota(profileId, quota, provider = "openai-codex", options) {
|
|
305
461
|
if (!quota) {
|
|
306
462
|
return;
|
|
307
463
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return {
|
|
464
|
+
await this.applyProfileRuntimeUpdate(
|
|
465
|
+
profileId,
|
|
466
|
+
provider,
|
|
467
|
+
(profile) => ({
|
|
313
468
|
...profile,
|
|
314
469
|
quota
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (activeProfile?.profileId === updated.profileId) {
|
|
322
|
-
await this.maybeAutoSwitchProfile(updated, provider);
|
|
323
|
-
}
|
|
470
|
+
}),
|
|
471
|
+
{
|
|
472
|
+
skipAutoSwitch: options?.skipAutoSwitch,
|
|
473
|
+
checkAutoSwitch: true
|
|
474
|
+
}
|
|
475
|
+
);
|
|
324
476
|
}
|
|
325
477
|
async getStatus() {
|
|
326
478
|
const [profile, profiles] = await Promise.all([
|
|
@@ -18,7 +18,7 @@ class ChatService {
|
|
|
18
18
|
system: request.system,
|
|
19
19
|
bodyOverride: request.experimental?.codexBody
|
|
20
20
|
});
|
|
21
|
-
await this.deps.authService.
|
|
21
|
+
await this.deps.authService.recordProfileRequestSuccess(profile.profileId, result.quota, provider);
|
|
22
22
|
return {
|
|
23
23
|
provider,
|
|
24
24
|
model,
|
|
@@ -28,7 +28,7 @@ class ChatService {
|
|
|
28
28
|
};
|
|
29
29
|
} catch (error) {
|
|
30
30
|
const quota = error.quota;
|
|
31
|
-
await this.deps.authService.
|
|
31
|
+
await this.deps.authService.recordProfileRequestFailure(profile.profileId, error, quota, provider);
|
|
32
32
|
throw error;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -334,10 +334,10 @@ class ImageService {
|
|
|
334
334
|
include: ["reasoning.encrypted_content"]
|
|
335
335
|
}
|
|
336
336
|
});
|
|
337
|
-
await this.deps.authService.
|
|
337
|
+
await this.deps.authService.recordProfileRequestSuccess(profile.profileId, result.quota, "openai-codex");
|
|
338
338
|
} catch (error) {
|
|
339
339
|
const quota = error.quota;
|
|
340
|
-
await this.deps.authService.
|
|
340
|
+
await this.deps.authService.recordProfileRequestFailure(profile.profileId, error, quota, "openai-codex");
|
|
341
341
|
throw error;
|
|
342
342
|
}
|
|
343
343
|
const raw = isRecord(result.raw) ? result.raw : {};
|