ai-zero-token 1.0.10 → 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 +3 -0
- package/README.zh-CN.md +3 -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/index.html +3 -2
- package/dist/core/context.js +3 -0
- package/dist/core/providers/http-client.js +10 -4
- package/dist/core/services/network-detect-service.js +239 -0
- package/dist/desktop/main.js +17 -1
- package/dist/server/admin-page.js +27 -13
- package/dist/server/app.js +4 -0
- package/docs/DESKTOP_RELEASE.md +40 -0
- package/package.json +3 -2
- package/admin-ui/dist/assets/index-BBXWfa-w.js +0 -11
- package/admin-ui/dist/assets/index-n7rmcV5d.css +0 -1
|
@@ -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))}}
|
package/admin-ui/dist/index.html
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
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" />
|
|
6
7
|
<title>AI Zero Token</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CCywUil_.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DNzR8XR7.css">
|
|
9
10
|
</head>
|
|
10
11
|
<body>
|
|
11
12
|
<div id="root"></div>
|
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,
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { requestText } from "../providers/http-client.js";
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
const PLATFORM_PROBE_HEADERS = {
|
|
7
|
+
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
8
|
+
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
|
|
9
|
+
"cache-control": "no-cache",
|
|
10
|
+
"pragma": "no-cache",
|
|
11
|
+
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
|
|
12
|
+
};
|
|
13
|
+
function parseKeyValueText(input) {
|
|
14
|
+
return Object.fromEntries(
|
|
15
|
+
input.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
16
|
+
const index = line.indexOf("=");
|
|
17
|
+
if (index <= 0) {
|
|
18
|
+
return ["", ""];
|
|
19
|
+
}
|
|
20
|
+
return [line.slice(0, index), line.slice(index + 1)];
|
|
21
|
+
}).filter(([key]) => key)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
function parseDnsServers(text) {
|
|
25
|
+
const servers = /* @__PURE__ */ new Set();
|
|
26
|
+
for (const match of text.matchAll(/nameserver\[\d+\]\s*:\s*([0-9a-fA-F:.]+)/g)) {
|
|
27
|
+
servers.add(match[1]);
|
|
28
|
+
}
|
|
29
|
+
return [...servers];
|
|
30
|
+
}
|
|
31
|
+
function isPrivateOrReservedIp(ip) {
|
|
32
|
+
return /^(10|127|169\.254|172\.(1[6-9]|2\d|3[0-1])|192\.168)\./.test(ip) || /^0\./.test(ip) || /^100\.(6[4-9]|[7-9]\d|1\d\d|2[0-3]\d|24[0-7])\./.test(ip) || /^198\.(18|19)\./.test(ip) || /^203\.0\.113\./.test(ip) || /^192\.0\.2\./.test(ip) || /^198\.51\.100\./.test(ip) || /^fc00:/i.test(ip) || /^fd00:/i.test(ip) || /^fe80:/i.test(ip) || /^::1$/.test(ip);
|
|
33
|
+
}
|
|
34
|
+
function toCountryName(code) {
|
|
35
|
+
if (!code) {
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return new Intl.DisplayNames(["zh-CN"], { type: "region" }).of(code) ?? code;
|
|
40
|
+
} catch {
|
|
41
|
+
return code;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function runCommand(command, args, timeoutMs = 2500) {
|
|
45
|
+
const result = await execFileAsync(command, args, {
|
|
46
|
+
timeout: timeoutMs,
|
|
47
|
+
maxBuffer: 1024 * 128
|
|
48
|
+
});
|
|
49
|
+
return String(result.stdout || "");
|
|
50
|
+
}
|
|
51
|
+
async function readDnsServers() {
|
|
52
|
+
const servers = /* @__PURE__ */ new Set();
|
|
53
|
+
let source = "\u7CFB\u7EDF DNS";
|
|
54
|
+
try {
|
|
55
|
+
const scutil = await runCommand("scutil", ["--dns"], 2500);
|
|
56
|
+
parseDnsServers(scutil).forEach((item) => servers.add(item));
|
|
57
|
+
source = "scutil --dns";
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const wifiDns = await runCommand("networksetup", ["-getdnsservers", "Wi-Fi"], 2500);
|
|
62
|
+
for (const line of wifiDns.split(/\r?\n/)) {
|
|
63
|
+
const value = line.trim();
|
|
64
|
+
if (value && /^[0-9a-fA-F:.]+$/.test(value)) {
|
|
65
|
+
servers.add(value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (servers.size > 0) {
|
|
69
|
+
source = `${source} + networksetup`;
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
servers: [...servers],
|
|
75
|
+
source
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function probePublicIpv4(proxy) {
|
|
79
|
+
const startedAt = performance.now();
|
|
80
|
+
const response = await requestText({
|
|
81
|
+
method: "GET",
|
|
82
|
+
url: "https://ifconfig.me/ip",
|
|
83
|
+
timeoutMs: 8e3,
|
|
84
|
+
proxyOverride: proxy
|
|
85
|
+
});
|
|
86
|
+
if (response.status < 200 || response.status >= 500) {
|
|
87
|
+
throw new Error(`\u516C\u7F51 IPv4 \u63A2\u6D4B\u5931\u8D25: HTTP ${response.status}`);
|
|
88
|
+
}
|
|
89
|
+
const ip = response.body.trim();
|
|
90
|
+
let trace = {};
|
|
91
|
+
try {
|
|
92
|
+
const traceResponse = await requestText({
|
|
93
|
+
method: "GET",
|
|
94
|
+
url: "https://www.cloudflare.com/cdn-cgi/trace",
|
|
95
|
+
timeoutMs: 8e3,
|
|
96
|
+
proxyOverride: proxy
|
|
97
|
+
});
|
|
98
|
+
trace = parseKeyValueText(traceResponse.body);
|
|
99
|
+
} catch {
|
|
100
|
+
trace = {};
|
|
101
|
+
}
|
|
102
|
+
const countryCode = trace.loc;
|
|
103
|
+
return {
|
|
104
|
+
ip,
|
|
105
|
+
countryCode,
|
|
106
|
+
countryName: toCountryName(countryCode),
|
|
107
|
+
colo: trace.colo,
|
|
108
|
+
source: "ifconfig.me + cloudflare trace",
|
|
109
|
+
elapsedMs: Math.round(performance.now() - startedAt)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async function probePublicIpv6(proxy) {
|
|
113
|
+
const startedAt = performance.now();
|
|
114
|
+
const ipv6Candidates = [
|
|
115
|
+
"https://ifconfig.me/ipv6",
|
|
116
|
+
"https://ipv6.test-ipv6.com/"
|
|
117
|
+
];
|
|
118
|
+
for (const url of ipv6Candidates) {
|
|
119
|
+
try {
|
|
120
|
+
const response = await requestText({
|
|
121
|
+
method: "GET",
|
|
122
|
+
url,
|
|
123
|
+
timeoutMs: 6e3,
|
|
124
|
+
proxyOverride: proxy
|
|
125
|
+
});
|
|
126
|
+
const body = response.body.trim();
|
|
127
|
+
if (!body) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (/^[0-9a-fA-F:]+$/.test(body) && body.includes(":")) {
|
|
131
|
+
return {
|
|
132
|
+
available: true,
|
|
133
|
+
ip: body,
|
|
134
|
+
source: url,
|
|
135
|
+
detail: "\u68C0\u6D4B\u5230\u72EC\u7ACB IPv6 \u51FA\u53E3\u3002",
|
|
136
|
+
elapsedMs: Math.round(performance.now() - startedAt)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
available: false,
|
|
144
|
+
source: "ifconfig.me / test-ipv6.com",
|
|
145
|
+
detail: "\u672A\u68C0\u6D4B\u5230\u72EC\u7ACB IPv6 \u51FA\u53E3\uFF0C\u5F53\u524D\u8FDE\u63A5\u53EF\u80FD\u53EA\u8D70 IPv4\u3002"
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
async function probePlatform(url, label, proxy) {
|
|
149
|
+
const startedAt = performance.now();
|
|
150
|
+
try {
|
|
151
|
+
const response = await requestText({
|
|
152
|
+
method: "GET",
|
|
153
|
+
url,
|
|
154
|
+
headers: PLATFORM_PROBE_HEADERS,
|
|
155
|
+
timeoutMs: 8e3,
|
|
156
|
+
proxyOverride: proxy
|
|
157
|
+
});
|
|
158
|
+
const elapsedMs = Math.round(performance.now() - startedAt);
|
|
159
|
+
if (response.status >= 200 && response.status < 400) {
|
|
160
|
+
return {
|
|
161
|
+
key: label,
|
|
162
|
+
label,
|
|
163
|
+
url,
|
|
164
|
+
status: "\u53EF\u8FBE",
|
|
165
|
+
detail: `HTTP ${response.status} \u53EF\u6B63\u5E38\u8BBF\u95EE`,
|
|
166
|
+
tone: "green",
|
|
167
|
+
httpStatus: response.status,
|
|
168
|
+
elapsedMs
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (response.status >= 400 && response.status < 500) {
|
|
172
|
+
return {
|
|
173
|
+
key: label,
|
|
174
|
+
label,
|
|
175
|
+
url,
|
|
176
|
+
status: "\u53D7\u9650",
|
|
177
|
+
detail: `HTTP ${response.status}\uFF0C\u7F51\u7EDC\u53EF\u8FBE\u4F46\u7AD9\u70B9\u8FD4\u56DE\u9650\u5236`,
|
|
178
|
+
tone: "orange",
|
|
179
|
+
httpStatus: response.status,
|
|
180
|
+
elapsedMs
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
key: label,
|
|
185
|
+
label,
|
|
186
|
+
url,
|
|
187
|
+
status: "\u4E0D\u53EF\u7528",
|
|
188
|
+
detail: `HTTP ${response.status}\uFF0C\u7AD9\u70B9\u4E0D\u53EF\u7528`,
|
|
189
|
+
tone: "red",
|
|
190
|
+
httpStatus: response.status,
|
|
191
|
+
elapsedMs
|
|
192
|
+
};
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return {
|
|
195
|
+
key: label,
|
|
196
|
+
label,
|
|
197
|
+
url,
|
|
198
|
+
status: "\u4E0D\u53EF\u7528",
|
|
199
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
200
|
+
tone: "red",
|
|
201
|
+
elapsedMs: Math.round(performance.now() - startedAt)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
class NetworkDetectService {
|
|
206
|
+
async collectReport(proxy) {
|
|
207
|
+
const [publicIpv4, publicIpv6, dns, platforms] = await Promise.all([
|
|
208
|
+
probePublicIpv4(proxy),
|
|
209
|
+
probePublicIpv6(proxy),
|
|
210
|
+
readDnsServers(),
|
|
211
|
+
Promise.all([
|
|
212
|
+
probePlatform("https://www.google.com/generate_204", "Google", proxy),
|
|
213
|
+
probePlatform("https://chatgpt.com/", "ChatGPT", proxy),
|
|
214
|
+
probePlatform("https://claude.ai/", "Claude", proxy),
|
|
215
|
+
probePlatform("https://www.youtube.com/", "YouTube", proxy),
|
|
216
|
+
probePlatform("https://x.com/", "X", proxy)
|
|
217
|
+
])
|
|
218
|
+
]);
|
|
219
|
+
const dnsDetail = dns.servers.length > 0 ? `\u7CFB\u7EDF DNS: ${dns.servers.join(" / ")}` : "\u672A\u8BFB\u53D6\u5230\u7CFB\u7EDF DNS\u3002";
|
|
220
|
+
return {
|
|
221
|
+
checkedAt: Date.now(),
|
|
222
|
+
publicIpv4,
|
|
223
|
+
publicIpv6,
|
|
224
|
+
dns: {
|
|
225
|
+
servers: dns.servers,
|
|
226
|
+
source: dns.source,
|
|
227
|
+
detail: dnsDetail
|
|
228
|
+
},
|
|
229
|
+
proxy: {
|
|
230
|
+
enabled: Boolean(proxy?.enabled && proxy.url.trim()),
|
|
231
|
+
url: proxy?.url?.trim() || void 0
|
|
232
|
+
},
|
|
233
|
+
platforms
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
export {
|
|
238
|
+
NetworkDetectService
|
|
239
|
+
};
|
package/dist/desktop/main.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { app as electronApp, BrowserWindow, dialog, shell } from "electron";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
3
5
|
import { startServer } from "../server/index.js";
|
|
4
6
|
let gatewayServer = null;
|
|
5
7
|
let mainWindow = null;
|
|
6
8
|
let isQuitting = false;
|
|
9
|
+
const desktopDir = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const appIconPath = path.resolve(desktopDir, "../../build/icon.png");
|
|
11
|
+
electronApp.setName("AI Zero Token");
|
|
7
12
|
function createBrowserUrl(host, port) {
|
|
8
13
|
if (host === "0.0.0.0" || host === "::") {
|
|
9
14
|
return `http://127.0.0.1:${port}`;
|
|
@@ -45,6 +50,7 @@ async function createMainWindow() {
|
|
|
45
50
|
minWidth: 1100,
|
|
46
51
|
minHeight: 720,
|
|
47
52
|
title: "AI Zero Token",
|
|
53
|
+
icon: appIconPath,
|
|
48
54
|
backgroundColor: "#f8fafc",
|
|
49
55
|
webPreferences: {
|
|
50
56
|
contextIsolation: true,
|
|
@@ -101,7 +107,17 @@ if (!hasSingleInstanceLock) {
|
|
|
101
107
|
electronApp.on("second-instance", () => {
|
|
102
108
|
focusMainWindow();
|
|
103
109
|
});
|
|
104
|
-
electronApp.whenReady().then(
|
|
110
|
+
electronApp.whenReady().then(() => {
|
|
111
|
+
if (process.platform === "darwin") {
|
|
112
|
+
electronApp.dock?.setIcon(appIconPath);
|
|
113
|
+
}
|
|
114
|
+
electronApp.setAboutPanelOptions({
|
|
115
|
+
applicationName: "AI Zero Token",
|
|
116
|
+
applicationVersion: electronApp.getVersion(),
|
|
117
|
+
iconPath: appIconPath
|
|
118
|
+
});
|
|
119
|
+
return createMainWindow();
|
|
120
|
+
}).catch(handleStartupError);
|
|
105
121
|
electronApp.on("activate", () => {
|
|
106
122
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
107
123
|
focusMainWindow();
|
|
@@ -2648,6 +2648,10 @@ function renderAdminPage() {
|
|
|
2648
2648
|
return Math.max(0, Math.min(100, value));
|
|
2649
2649
|
}
|
|
2650
2650
|
|
|
2651
|
+
function getPrimaryRemaining(profile) {
|
|
2652
|
+
return 100 - getPrimaryUsage(profile);
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2651
2655
|
function getSecondaryUsage(profile) {
|
|
2652
2656
|
const value = profile && profile.quota && typeof profile.quota.secondaryUsedPercent === "number"
|
|
2653
2657
|
? profile.quota.secondaryUsedPercent
|
|
@@ -2744,6 +2748,22 @@ function renderAdminPage() {
|
|
|
2744
2748
|
};
|
|
2745
2749
|
}
|
|
2746
2750
|
|
|
2751
|
+
function isProfileUnavailable(profile) {
|
|
2752
|
+
const health = getProfileHealth(profile);
|
|
2753
|
+
return health.key === "invalid" || health.key === "expired";
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
function getProfileSortGroup(profile, codexAccountId) {
|
|
2757
|
+
const isCodexActive = Boolean(codexAccountId && profile.accountId === codexAccountId);
|
|
2758
|
+
if (profile.isActive || isCodexActive) {
|
|
2759
|
+
return 0;
|
|
2760
|
+
}
|
|
2761
|
+
if (isProfileUnavailable(profile)) {
|
|
2762
|
+
return 2;
|
|
2763
|
+
}
|
|
2764
|
+
return 1;
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2747
2767
|
function describeReset(profile, slot) {
|
|
2748
2768
|
if (!profile || !profile.quota) {
|
|
2749
2769
|
return "\u6682\u65E0\u6570\u636E";
|
|
@@ -3226,24 +3246,18 @@ function renderAdminPage() {
|
|
|
3226
3246
|
});
|
|
3227
3247
|
|
|
3228
3248
|
filtered.sort(function (a, b) {
|
|
3229
|
-
const
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
if (activeDiff !== 0) {
|
|
3233
|
-
return activeDiff;
|
|
3234
|
-
}
|
|
3235
|
-
const gatewayDiff = Number(b.isActive) - Number(a.isActive);
|
|
3236
|
-
if (gatewayDiff !== 0) {
|
|
3237
|
-
return gatewayDiff;
|
|
3238
|
-
}
|
|
3239
|
-
const codexDiff = Number(bCodexActive) - Number(aCodexActive);
|
|
3240
|
-
if (codexDiff !== 0) {
|
|
3241
|
-
return codexDiff;
|
|
3249
|
+
const groupDiff = getProfileSortGroup(a, codexAccountId) - getProfileSortGroup(b, codexAccountId);
|
|
3250
|
+
if (groupDiff !== 0) {
|
|
3251
|
+
return groupDiff;
|
|
3242
3252
|
}
|
|
3243
3253
|
const planDiff = getPlanRank(b) - getPlanRank(a);
|
|
3244
3254
|
if (planDiff !== 0) {
|
|
3245
3255
|
return planDiff;
|
|
3246
3256
|
}
|
|
3257
|
+
const primaryRemainingDiff = getPrimaryRemaining(b) - getPrimaryRemaining(a);
|
|
3258
|
+
if (primaryRemainingDiff !== 0) {
|
|
3259
|
+
return primaryRemainingDiff;
|
|
3260
|
+
}
|
|
3247
3261
|
if (sort === "latency-asc") {
|
|
3248
3262
|
const aCapturedAt = getQuotaSnapshotTime(a) || 0;
|
|
3249
3263
|
const bCapturedAt = getQuotaSnapshotTime(b) || 0;
|
package/dist/server/app.js
CHANGED
|
@@ -805,6 +805,10 @@ function createApp(params) {
|
|
|
805
805
|
};
|
|
806
806
|
}
|
|
807
807
|
});
|
|
808
|
+
app.get("/_gateway/admin/network-detect", async () => {
|
|
809
|
+
const settings = await ctx.configService.getSettings();
|
|
810
|
+
return ctx.networkDetectService.collectReport(settings.networkProxy);
|
|
811
|
+
});
|
|
808
812
|
app.get("/v1/models", async () => ({
|
|
809
813
|
object: "list",
|
|
810
814
|
data: (await ctx.modelService.listModels()).map((model) => ({
|
package/docs/DESKTOP_RELEASE.md
CHANGED
|
@@ -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.0 Release Notes
|
|
6
|
+
|
|
7
|
+
Version `2.0.0` is the first desktop-focused major release. It includes:
|
|
8
|
+
|
|
9
|
+
- Electron desktop packaging for macOS and Windows.
|
|
10
|
+
- The embedded React management UI under `admin-ui/`.
|
|
11
|
+
- Desktop launch, overview, account, tester, docs, network, logs, and settings pages.
|
|
12
|
+
- Release links in the app shell and README that point to GitHub Releases.
|
|
13
|
+
|
|
5
14
|
## Build Commands
|
|
6
15
|
|
|
7
16
|
```bash
|
|
@@ -32,6 +41,21 @@ npm run dist:win
|
|
|
32
41
|
|
|
33
42
|
Creates macOS and Windows distributables. macOS builds should be produced on macOS. Windows builds are best produced on Windows CI or a runner with a complete Windows packaging environment.
|
|
34
43
|
|
|
44
|
+
## UI Engineering Standards
|
|
45
|
+
|
|
46
|
+
Before building release artifacts, the desktop React UI should follow:
|
|
47
|
+
|
|
48
|
+
- [Frontend Architecture Guide](FRONTEND_ARCHITECTURE.md)
|
|
49
|
+
- [Desktop Design System](DESIGN_SYSTEM.md)
|
|
50
|
+
|
|
51
|
+
At minimum, verify:
|
|
52
|
+
|
|
53
|
+
- `App.tsx` only composes the application root.
|
|
54
|
+
- Page modules live under `admin-ui/src/pages`.
|
|
55
|
+
- Shared components and helpers live under `admin-ui/src/shared`.
|
|
56
|
+
- Desktop routes are registered through `admin-ui/src/routes/routes.tsx`.
|
|
57
|
+
- The app renders cleanly at desktop sizes around `1180px x 760px` and above.
|
|
58
|
+
|
|
35
59
|
## Signing
|
|
36
60
|
|
|
37
61
|
Unsigned builds are suitable for internal testing only. Public commercial distribution should use platform signing:
|
|
@@ -51,6 +75,22 @@ release/
|
|
|
51
75
|
|
|
52
76
|
The folder is intentionally ignored by git.
|
|
53
77
|
|
|
78
|
+
### Publish Flow
|
|
79
|
+
|
|
80
|
+
1. Build the desktop package:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm run dist:dir
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
2. Upload the generated files from `release/` to the matching GitHub Release tag.
|
|
87
|
+
|
|
88
|
+
3. Publish the npm package after confirming `package.json` and `package-lock.json` both point at the new version:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm publish
|
|
92
|
+
```
|
|
93
|
+
|
|
54
94
|
## App Resources
|
|
55
95
|
|
|
56
96
|
App icon files live in:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-zero-token",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
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",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"azt": "dist/cli.js",
|
|
18
18
|
"ai-zero-token": "dist/cli.js"
|
|
19
19
|
},
|
|
20
|
+
"main": "dist/desktop/main.js",
|
|
20
21
|
"keywords": [
|
|
21
22
|
"ai",
|
|
22
23
|
"cli",
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
"build": "npm run build:ui && npm run build:server",
|
|
46
47
|
"build:server": "tsup",
|
|
47
48
|
"build:ui": "vite build --config admin-ui/vite.config.ts",
|
|
48
|
-
"desktop": "npm run build && electron
|
|
49
|
+
"desktop": "npm run build && electron .",
|
|
49
50
|
"desktop:dev": "node scripts/dev.mjs desktop",
|
|
50
51
|
"dist": "npm run build && electron-builder",
|
|
51
52
|
"dist:dir": "npm run build && electron-builder --dir",
|