ai-zero-token 1.0.9 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ :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:320px;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:260px minmax(0,1fr);align-items:start;gap:20px;width:calc(100vw - 24px);max-width:none;margin:12px auto 24px;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:24px;gap:24px;min-width:0;max-height:calc(100vh - 40px);padding:22px 18px;display:grid;position:sticky;top:20px;overflow:auto}.brand{align-items:flex-start;gap:12px;display:flex}.brand-mark{width:36px;height:36px;color:var(--brand);background:linear-gradient(#635bff24,#635bff0a);border:1px solid #635bff33;border-radius:12px;flex:none;place-items:center;display:grid}.brand strong{font-size:18px;line-height:1.25;display:block}.brand span{color:var(--text-muted);margin-top:5px;font-size:12px;line-height:1.5;display:block}.nav{gap:6px;display:grid}.nav-item{width:100%;min-height:44px;color:var(--text-soft);text-align:left;background:0 0;border:1px solid #0000;border-radius:12px;align-items:center;gap:10px;padding:0 12px;display:flex}.nav-item.is-active,.nav-item:hover{color:var(--brand);background:#635bff14;border-color:#635bff1a;font-weight:650}.main{gap:20px;min-width:0;display:grid}.topbar{grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:16px;display:grid}.page-title h1{margin:0;font-size:30px;line-height:1.15}.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}.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(auto-fit,minmax(190px,1fr));gap:14px;display:grid}.summary-card{border-radius:16px;align-content:start;gap:12px;min-height:130px;padding:18px;display:grid}.summary-card>div:last-child{gap:8px;min-width:0;display:grid}.summary-card span{color:var(--text-muted);font-size:13px;font-weight:650}.summary-card strong{overflow-wrap:break-word;min-width:0;font-size:23px;line-height:1.2;display:block}.summary-card.compact-value strong{overflow-wrap:anywhere;font-size:20px;line-height:1.25}.usage-summary{gap:7px;min-width:0;display:grid}.usage-summary-row{grid-template-columns:46px minmax(0,1fr);align-items:center;gap:8px;min-width:0;display:grid}.usage-summary-row span{min-width:0;height:22px;color:var(--brand-700);background:#4053ff17;border-radius:999px;justify-content:center;align-items:center;font-size:11px;font-weight:850;display:inline-flex}.summary-card.compact-value .usage-summary-row strong{white-space:nowrap;text-overflow:ellipsis;overflow-wrap:normal;min-width:0;font-size:14px;line-height:1.25;overflow:hidden}.summary-card p{color:var(--text-muted);margin:0;font-size:13px;line-height:1.55}.summary-icon{width:26px;height:26px;color:var(--brand);background:var(--brand-soft);border-radius:999px;place-items:center;display:grid}.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}.left-column,.right-column{gap:18px;min-width:0;display:grid}.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}.account-grid{justify-content:stretch;align-items:start;gap:16px;width:100%;display:grid}.account-grid.profile-count-1{grid-template-columns:minmax(340px,520px)}.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))}.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:14px;display:grid}.tester-tabs,.tester-result-tabs{border-radius:14px;gap:4px;padding:6px;display:flex;overflow-x:auto}.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-textarea{min-height:184px}.pre{color:#e2e8f0;white-space:pre-wrap;overflow-wrap:anywhere;background:#0f172a;border-radius:12px;width:100%;min-height:170px;max-height:360px;margin:0;padding:16px;font-size:12px;line-height:1.6;overflow:auto}.preview-panel{min-height:170px}.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}.service-card{border-radius:16px;gap:14px;padding:16px;display:grid}.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);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}.table-scroller{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:12px 10px;font-size:13px}th{color:var(--text-muted);font-size:12px;font-weight:800}td code{color:var(--text)}.table-footer{color:var(--text-muted);margin-top:10px;font-size:12px}.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}.settings-drawer{width:min(520px,100%);height:100%;box-shadow:var(--shadow);background:#fff;border-radius:18px 0 0 18px;grid-template-rows:auto minmax(0,1fr) auto;justify-self:end;display:grid}.settings-drawer-head,.settings-drawer-footer{border-bottom:1px solid var(--line);justify-content:space-between;align-items:flex-start;gap:12px;padding:18px 20px;display:flex}.settings-drawer-footer{border-top:1px solid var(--line);border-bottom:0;align-items:center}.settings-drawer-head h3{margin:0}.settings-drawer-head p,.settings-drawer-footer p{color:var(--text-muted);margin:6px 0 0;font-size:13px;line-height:1.6}.settings-drawer-body{align-content:start;padding:20px;display:grid;overflow:auto}.settings-section{border-bottom:1px solid var(--line);gap:13px;margin-bottom:20px;padding:0 0 20px;display:grid}.settings-section:last-child{border-bottom:0;margin-bottom:0}.settings-section h4{margin:0}.switch-line{color:var(--text-soft);align-items:flex-start;gap:10px;font-size:13px;font-weight:700;line-height:1.5;display:flex}.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)}}@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;padding:10px}.sidebar{max-height:none;position:relative;top:0}.nav{grid-template-columns:repeat(5,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(2,minmax(0,1fr));gap:10px;padding:0 12px}.summary-card{min-height:122px;padding:14px}.summary-card strong{font-size:20px}.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:1fr}.btn-primary,.btn-secondary,.btn-danger{min-height:40px;padding:0 12px}.card,.trend-card,.log-table-wrap{padding:16px 12px}}
@@ -0,0 +1,13 @@
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
+ <title>AI Zero Token</title>
7
+ <script type="module" crossorigin src="/assets/index-BBXWfa-w.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-n7rmcV5d.css">
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ </body>
13
+ </html>
Binary file
package/build/icon.ico ADDED
Binary file
package/build/icon.png ADDED
Binary file
@@ -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
- throw new Error(`\u5237\u65B0 token \u5931\u8D25: HTTP ${response.status} via ${response.transport} ${response.body}`);
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
- const refreshed = await refreshOpenAICodexToken(profile);
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 refreshOpenAICodexToken(profile);
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.updateProfileQuota(profile.profileId, result.quota, provider, {
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.updateProfileQuota(profile.profileId, quota, provider, {
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 active profile quota failed", {
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
- const updated = await updateProfile(profileId, (profile) => {
309
- if (profile.provider !== provider) {
310
- return profile;
311
- }
312
- return {
464
+ await this.applyProfileRuntimeUpdate(
465
+ profileId,
466
+ provider,
467
+ (profile) => ({
313
468
  ...profile,
314
469
  quota
315
- };
316
- });
317
- if (options?.skipAutoSwitch || !updated || updated.provider !== provider || !this.isQuotaExhausted(updated)) {
318
- return;
319
- }
320
- const activeProfile = await this.getActiveProfile(provider);
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.updateProfileQuota(profile.profileId, result.quota, provider);
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.updateProfileQuota(profile.profileId, quota, provider);
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.updateProfileQuota(profile.profileId, result.quota, "openai-codex");
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.updateProfileQuota(profile.profileId, quota, "openai-codex");
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 : {};
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ import { app as electronApp, BrowserWindow, dialog, shell } from "electron";
3
+ import { startServer } from "../server/index.js";
4
+ let gatewayServer = null;
5
+ let mainWindow = null;
6
+ let isQuitting = false;
7
+ function createBrowserUrl(host, port) {
8
+ if (host === "0.0.0.0" || host === "::") {
9
+ return `http://127.0.0.1:${port}`;
10
+ }
11
+ return `http://${host}:${port}`;
12
+ }
13
+ function isGatewayUrl(targetUrl, gatewayUrl) {
14
+ try {
15
+ const target = new URL(targetUrl);
16
+ const gateway = new URL(gatewayUrl);
17
+ return target.origin === gateway.origin;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+ function resolveAdminUrl(gatewayUrl) {
23
+ const devUrl = process.env.AZT_ADMIN_UI_DEV_URL?.trim();
24
+ return devUrl || gatewayUrl;
25
+ }
26
+ async function ensureGatewayServer() {
27
+ if (gatewayServer) {
28
+ return gatewayServer;
29
+ }
30
+ gatewayServer = await startServer();
31
+ const adminUrl = createBrowserUrl(gatewayServer.host, gatewayServer.port);
32
+ console.log("AI Zero Token desktop gateway started.");
33
+ console.log(`admin: ${adminUrl}`);
34
+ console.log(`apiBase: ${adminUrl}/v1`);
35
+ console.log(`listen: http://${gatewayServer.host}:${gatewayServer.port}`);
36
+ return gatewayServer;
37
+ }
38
+ async function createMainWindow() {
39
+ const server = await ensureGatewayServer();
40
+ const gatewayUrl = createBrowserUrl(server.host, server.port);
41
+ const adminUrl = resolveAdminUrl(gatewayUrl);
42
+ mainWindow = new BrowserWindow({
43
+ width: 1440,
44
+ height: 960,
45
+ minWidth: 1100,
46
+ minHeight: 720,
47
+ title: "AI Zero Token",
48
+ backgroundColor: "#f8fafc",
49
+ webPreferences: {
50
+ contextIsolation: true,
51
+ nodeIntegration: false,
52
+ sandbox: true
53
+ }
54
+ });
55
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
56
+ void shell.openExternal(url);
57
+ return { action: "deny" };
58
+ });
59
+ mainWindow.webContents.on("will-navigate", (event, url) => {
60
+ if (isGatewayUrl(url, adminUrl) || isGatewayUrl(url, gatewayUrl)) {
61
+ return;
62
+ }
63
+ event.preventDefault();
64
+ void shell.openExternal(url);
65
+ });
66
+ mainWindow.on("closed", () => {
67
+ mainWindow = null;
68
+ });
69
+ await mainWindow.loadURL(adminUrl);
70
+ }
71
+ function focusMainWindow() {
72
+ if (!mainWindow) {
73
+ void createMainWindow().catch(handleStartupError);
74
+ return;
75
+ }
76
+ if (mainWindow.isMinimized()) {
77
+ mainWindow.restore();
78
+ }
79
+ mainWindow.focus();
80
+ }
81
+ async function closeGatewayServer() {
82
+ if (!gatewayServer) {
83
+ return;
84
+ }
85
+ const server = gatewayServer;
86
+ gatewayServer = null;
87
+ await server.app.close();
88
+ }
89
+ function handleStartupError(error) {
90
+ const message = error instanceof Error ? error.message : String(error);
91
+ console.error("[desktop:error]", error);
92
+ if (electronApp.isReady()) {
93
+ dialog.showErrorBox("AI Zero Token \u542F\u52A8\u5931\u8D25", message);
94
+ }
95
+ electronApp.quit();
96
+ }
97
+ const hasSingleInstanceLock = electronApp.requestSingleInstanceLock();
98
+ if (!hasSingleInstanceLock) {
99
+ electronApp.quit();
100
+ } else {
101
+ electronApp.on("second-instance", () => {
102
+ focusMainWindow();
103
+ });
104
+ electronApp.whenReady().then(createMainWindow).catch(handleStartupError);
105
+ electronApp.on("activate", () => {
106
+ if (BrowserWindow.getAllWindows().length === 0) {
107
+ focusMainWindow();
108
+ }
109
+ });
110
+ electronApp.on("window-all-closed", () => {
111
+ if (process.platform !== "darwin") {
112
+ electronApp.quit();
113
+ }
114
+ });
115
+ electronApp.on("before-quit", (event) => {
116
+ if (!gatewayServer || isQuitting) {
117
+ return;
118
+ }
119
+ event.preventDefault();
120
+ isQuitting = true;
121
+ void closeGatewayServer().catch((error) => {
122
+ console.error("[desktop:gateway:close]", error);
123
+ }).finally(() => {
124
+ electronApp.quit();
125
+ });
126
+ });
127
+ }