creditdaddy 1.2.0 → 1.3.2

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/src/panel.html CHANGED
@@ -7,6 +7,7 @@
7
7
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9IjEwMjQiPjxwYXRoIGQ9Ik0zNzIuNDggMTM4LjY2NjY2N0ExMjggMTI4IDAgMCAwIDI5OC42NjY2NjcgMTAxLjU0NjY2N2ExMTcuNzYgMTE3Ljc2IDAgMCAwLTYyLjA4IDAgMTAuODggMTAuODggMCAwIDAtNS4xMiAxNi44NTMzMzNsNzEuMjUzMzMzIDkxLjUyLTgxLjI4IDI4LjM3MzMzM2ExMC42NjY2NjcgMTAuNjY2NjY3IDAgMCAwLTIuMzQ2NjY3IDE4Ljk4NjY2NyAyNDIuNTYgMjQyLjU2IDAgMCAwIDY5Ljc2IDMyLjg1MzMzMyAyNzMuNzA2NjY3IDI3My43MDY2NjcgMCAwIDAgNTYuOTYgOC4zMnoiIGZpbGw9IiNGRjhBMTQiPjwvcGF0aD48cGF0aCBkPSJNMjQ1Ljc2IDEzOC4wMjY2NjdhMTMwLjk4NjY2NyAxMzAuOTg2NjY3IDAgMCAxIDUyLjA1MzMzMyAyLjEzMzMzM0ExNDguMDUzMzMzIDE0OC4wNTMzMzMgMCAwIDEgMzY2LjcyIDE3MC42NjY2NjdsNS43Ni0zMi44NTMzMzRBMTI4IDEyOCAwIDAgMCAyOTguNjY2NjY3IDEwMS41NDY2NjdhMTE3Ljk3MzMzMyAxMTcuOTczMzMzIDAgMCAwLTYyLjA4IDAgMTAuODggMTAuODggMCAwIDAtNS4xMiAxNi44NTMzMzN6IiBmaWxsPSIjRkZBQTU0Ij48L3BhdGg+PHBhdGggZD0iTTIzNC42NjY2NjcgOTcwLjY2NjY2N2EyNzcuMzMzMzMzIDMyIDAgMSAwIDU1NC42NjY2NjYgMCAyNzcuMzMzMzMzIDMyIDAgMSAwLTU1NC42NjY2NjYgMFoiIGZpbGw9IiM0NTQxM0MiIG9wYWNpdHk9Ii4xNSI+PC9wYXRoPjxwYXRoIGQ9Ik02MjAuMzczMzMzIDQ2MC41ODY2NjdzMTUxLjA0LTkwLjY2NjY2NyAxODEuMzMzMzM0LTc0LjY2NjY2Ny0xMi41ODY2NjcgMTc0LjI5MzMzMy0xMDYuNjY2NjY3IDIxMy4zMzMzMzMtNzQuNjY2NjY3LTEzOC42NjY2NjctNzQuNjY2NjY3LTEzOC42NjY2NjZ6IiBmaWxsPSIjRkZFNTAwIj48L3BhdGg+PHBhdGggZD0iTTYyMC4zNzMzMzMgNTEzLjkyczE1MS4wNC05MC42NjY2NjcgMTgxLjMzMzMzNC03NC42NjY2NjdhMTQuNzIgMTQuNzIgMCAwIDEgNS4zMzMzMzMgNS45NzMzMzRjNi44MjY2NjctMjkuODY2NjY3IDUuMzMzMzMzLTUzLjU0NjY2Ny01LjMzMzMzMy01OS4zMDY2NjctMzAuMjkzMzMzLTE2LTE4MS4zMzMzMzMgNzQuNjY2NjY3LTE4MS4zMzMzMzQgNzQuNjY2NjY3YTM4Ni45ODY2NjcgMzg2Ljk4NjY2NyAwIDAgMCAwIDYxLjg2NjY2NmMtMC40MjY2NjctNS4zMzMzMzMgMC04LjUzMzMzMyAwLTguNTMzMzMzeiIgZmlsbD0iI0ZGRjQ4QyI+PC9wYXRoPjxwYXRoIGQ9Ik00MDMuNjI2NjY3IDQ2MC41ODY2NjdzLTE1MS4wNC05MC42NjY2NjctMTgxLjMzMzMzNC03NC42NjY2NjcgMTIuMzczMzMzIDE3NC4yOTMzMzMgMTA2LjY2NjY2NyAyMTMuMzMzMzMzIDc0LjY2NjY2Ny0xMzguNjY2NjY3IDc0LjY2NjY2Ny0xMzguNjY2NjY2eiIgZmlsbD0iI0ZGRTUwMCI+PC9wYXRoPjxwYXRoIGQ9Ik0yMjIuMjkzMzMzIDQzOS4yNTMzMzNjMzAuMjkzMzMzLTE2IDE4MS4zMzMzMzMgNzQuNjY2NjY3IDE4MS4zMzMzMzQgNzQuNjY2NjY3djguNTMzMzMzYTM3My41NDY2NjcgMzczLjU0NjY2NyAwIDAgMCAwLTYxLjg2NjY2NnMtMTUxLjA0LTkwLjY2NjY2Ny0xODEuMzMzMzM0LTc0LjY2NjY2N2MtMTAuNjY2NjY3IDUuNzYtMTIuMTYgMjkuNDQtNS4zMzMzMzMgNTkuMzA2NjY3YTEzLjQ0IDEzLjQ0IDAgMCAxIDUuMzMzMzMzLTUuOTczMzM0eiIgZmlsbD0iI0ZGRjQ4QyI+PC9wYXRoPjxwYXRoIGQ9Ik0yODggNjUwLjY2NjY2N2EyNjYuNjY2NjY3IDIyNCA5MCAxIDAgNDQ4IDAgMjY2LjY2NjY2NyAyMjQgOTAgMSAwLTQ0OCAwWiIgZmlsbD0iI0ZGRTUwMCI+PC9wYXRoPjxwYXRoIGQ9Ik02ODAuMTA2NjY3IDQ3NC4yNGEyMjQgMjI0IDAgMCAxLTMzNi4yMTMzMzQgMEEyMTAuNTYgMjEwLjU2IDAgMCAxIDUxMiAzODRhMjEwLjU2IDIxMC41NiAwIDAgMSAxNjguMTA2NjY3IDkwLjI0eiIgZmlsbD0iI0VCQ0IwMCI+PC9wYXRoPjxwYXRoIGQ9Ik01MTIgMjY2LjY2NjY2N20tMjI0IDBhMjI0IDIyNCAwIDEgMCA0NDggMCAyMjQgMjI0IDAgMSAwLTQ0OCAwWiIgZmlsbD0iI0ZGRTUwMCI+PC9wYXRoPjxwYXRoIGQ9Ik01MTIgMTAxLjk3MzMzM2EyMjQuMjEzMzMzIDIyNC4yMTMzMzMgMCAwIDEgMjIxLjg2NjY2NyAxOTQuMzQ2NjY3IDIyNCAyMjQgMCAxIDAtNDQzLjczMzMzNCAwQTIyMy43ODY2NjcgMjIzLjc4NjY2NyAwIDAgMSA1MTIgMTAxLjk3MzMzM3oiIGZpbGw9IiNGRkY0OEMiPjwvcGF0aD48cGF0aCBkPSJNODMzLjQ5MzMzMyA1OTguODI2NjY3bC03MC40IDcwLjRhMTguMzQ2NjY3IDE4LjM0NjY2NyAwIDAgMS0yNS44MTMzMzMgMEw2NDQuMDUzMzMzIDU3NkExOC4zNDY2NjcgMTguMzQ2NjY3IDAgMCAwIDYxOC42NjY2NjcgNTc2bC05My4wMTMzMzQgOTMuMjI2NjY3YTE4Ljc3MzMzMyAxOC43NzMzMzMgMCAwIDEtMjYuMDI2NjY2IDBMNDA1LjMzMzMzMyA1NzZhMTguMTMzMzMzIDE4LjEzMzMzMyAwIDAgMC0yNS44MTMzMzMgMGwtOTMuMjI2NjY3IDkzLjIyNjY2N2ExOC4zNDY2NjcgMTguMzQ2NjY3IDAgMCAxLTI1LjgxMzMzMyAwbC03MC40LTcwLjRhOS4xNzMzMzMgOS4xNzMzMzMgMCAwIDAtMTUuNzg2NjY3IDYuODI2NjY2QzE4OS44NjY2NjcgODQxLjgxMzMzMyAzMzQuOTMzMzMzIDk2MCA1MTIgOTYwczMyMi4xMzMzMzMtMTE4LjE4NjY2NyAzMzcuMjgtMzU0LjM0NjY2N2E5LjE3MzMzMyA5LjE3MzMzMyAwIDAgMC0xNS43ODY2NjctNi44MjY2NjZ6IiBmaWxsPSIjRjBGMEYwIj48L3BhdGg+PHBhdGggZD0iTTE5MC41MDY2NjcgNjY1LjgxMzMzM2w3MC40IDYxLjIyNjY2N2EyMS4zMzMzMzMgMjEuMzMzMzMzIDAgMCAwIDI1LjgxMzMzMyAwbDkzLjIyNjY2Ny04MS4wNjY2NjdhMjEuMzMzMzMzIDIxLjMzMzMzMyAwIDAgMSAyNS44MTMzMzMgMGw5My4yMjY2NjcgODEuMDY2NjY3YTIxLjMzMzMzMyAyMS4zMzMzMzMgMCAwIDAgMjYuMDI2NjY2IDBsOTMuNjUzMzM0LTgxLjA2NjY2N2EyMS4zMzMzMzMgMjEuMzMzMzMzIDAgMCAxIDI2LjAyNjY2NiAwbDkzLjIyNjY2NyA4MS4wNjY2NjdhMjEuMzMzMzMzIDIxLjMzMzMzMyAwIDAgMCAyNS44MTMzMzMgMGw3MC40LTYxLjIyNjY2N2E5LjgxMzMzMyA5LjgxMzMzMyAwIDAgMSA4LjUzMzMzNC0xLjkyIDUzNi4xMDY2NjcgNTM2LjEwNjY2NyAwIDAgMCA3LjI1MzMzMy01OC4yNCA5LjE3MzMzMyA5LjE3MzMzMyAwIDAgMC0xNS43ODY2NjctNi44MjY2NjZsLTcwLjQgNzAuNGExOC4zNDY2NjcgMTguMzQ2NjY3IDAgMCAxLTI1LjgxMzMzMyAwTDY0NC4wNTMzMzMgNTc2QTE4LjM0NjY2NyAxOC4zNDY2NjcgMCAwIDAgNjE4LjY2NjY2NyA1NzZsLTkzLjAxMzMzNCA5My4yMjY2NjdhMTguNzczMzMzIDE4Ljc3MzMzMyAwIDAgMS0yNi4wMjY2NjYgMEw0MDUuMzMzMzMzIDU3NmExOC4xMzMzMzMgMTguMTMzMzMzIDAgMCAwLTI1LjgxMzMzMyAwbC05My4yMjY2NjcgOTMuMjI2NjY3YTE4LjM0NjY2NyAxOC4zNDY2NjcgMCAwIDEtMjUuODEzMzMzIDBsLTcwLjQtNzAuNGE5LjE3MzMzMyA5LjE3MzMzMyAwIDAgMC0xNS43ODY2NjcgNi44MjY2NjYgNTM3LjE3MzMzMyA1MzcuMTczMzMzIDAgMCAwIDcuMDQgNTguMjQgMTAuMDI2NjY3IDEwLjAyNjY2NyAwIDAgMSA5LjE3MzMzNCAxLjkyeiIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPjxwYXRoIGQ9Ik00OTguMTMzMzMzIDE4NC41MzMzMzNtLTI5LjAxMzMzMyAwYTI5LjAxMzMzMyAyOS4wMTMzMzMgMCAxIDAgNTguMDI2NjY3IDAgMjkuMDEzMzMzIDI5LjAxMzMzMyAwIDEgMC01OC4wMjY2NjcgMFoiIGZpbGw9IiM0NTQxM0MiPjwvcGF0aD48cGF0aCBkPSJNNTc2IDI5MC45ODY2NjdjMCAxMS43MzMzMzMtMTUuNzg2NjY3IDIxLjMzMzMzMy0zNC45ODY2NjcgMjEuMzMzMzMzcy0zNS4yLTkuNi0zNS4yLTIxLjMzMzMzMyAxNS43ODY2NjctMjEuMzMzMzMzIDM1LjItMjEuMzMzMzM0IDM0Ljk4NjY2NyA5LjYgMzQuOTg2NjY3IDIxLjMzMzMzNHoiIGZpbGw9IiNGRkY0OEMiPjwvcGF0aD48L3N2Zz4=">
8
8
  <style>
9
9
  :root {
10
+ color-scheme: light;
10
11
  --bg: #f5f6f8; --surface: #ffffff; --surface-2: #fafafa; --tertiary: #f0f0f0; --hover: #f5f6f8; --active: #eef0f3;
11
12
  --border-subtle: #eeeeee; --border: #e5e5e5; --border-strong: #cccccc;
12
13
  --text: #1f1f1f; --text-2: #5f6368; --text-3: #999999;
@@ -17,6 +18,7 @@
17
18
  --mask: rgba(0, 0, 0, .4);
18
19
  }
19
20
  html[data-theme="dark"] {
21
+ color-scheme: dark;
20
22
  --bg: #0a0a0c; --surface: #131316; --surface-2: #17171b; --tertiary: #1b1b20; --hover: rgba(255,255,255,.06); --active: rgba(255,255,255,.1);
21
23
  --border-subtle: #202025; --border: rgba(255,255,255,.13); --border-strong: #3a3a42;
22
24
  --text: #e6e6e9; --text-2: rgba(230,230,233,.72); --text-3: rgba(230,230,233,.5);
@@ -32,7 +34,7 @@
32
34
  font-size: 13px; color: var(--text); background: var(--bg); min-height: 100vh;
33
35
  -webkit-font-smoothing: antialiased; transition: background .2s;
34
36
  }
35
- button, input, select { font-family: inherit; font-size: inherit; color: inherit; }
37
+ button, input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; }
36
38
  .wrap { max-width: 1120px; margin: 0 auto; padding: 18px 22px 40px; }
37
39
  .num { font-variant-numeric: tabular-nums; }
38
40
 
@@ -116,6 +118,8 @@
116
118
  .badge.intl { color: #3b82f6; background: rgba(59,130,246,.1); }
117
119
  .badge.cn { color: #d97706; background: rgba(245,158,11,.12); }
118
120
  .badge.prod { font-weight: 600; }
121
+ .badge.web { font-weight: 600; cursor: help; }
122
+ .badge.web-warn { color: var(--warn); background: var(--warn-soft); }
119
123
  .badge.prod.qoder { color: #6d28d9; background: rgba(124,58,237,.1); }
120
124
  .badge.prod.workbuddy { color: #0f766e; background: rgba(20,184,166,.12); }
121
125
  html[data-theme="dark"] .badge.prod.qoder { color: #b8a4ff; }
@@ -126,6 +130,8 @@
126
130
  html[data-theme="dark"] .badge.prod.catpaw { color: #fdba74; }
127
131
  .badge.prod.trae { color: #047857; background: rgba(16,185,129,.14); }
128
132
  html[data-theme="dark"] .badge.prod.trae { color: #6ee7b7; }
133
+ .badge.prod.minimax { color: #e11d48; background: rgba(244,63,94,.14); }
134
+ html[data-theme="dark"] .badge.prod.minimax { color: #fda4af; }
129
135
  .seg-ctl { display: inline-flex; gap: 2px; padding: 2px; border-radius: 9px; background: var(--hover); }
130
136
  .seg-ctl button { border: none; background: transparent; color: var(--text-2); font-size: 12px; font-weight: 600; padding: 4px 10px; border-radius: 7px; cursor: pointer; }
131
137
  .seg-ctl button.active { background: var(--surface); color: var(--text); box-shadow: 0 1px 3px rgba(0,0,0,.08); }
@@ -180,6 +186,13 @@
180
186
  .pcard.open .fold-body { display: block; }
181
187
  .logs { margin-top: 8px; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 11px; line-height: 1.65; max-height: 240px; overflow-y: auto; background: var(--tertiary); border-radius: 9px; padding: 8px 10px; }
182
188
  .logs .info { color: var(--text-2); } .logs .warn { color: var(--warn); } .logs .error { color: var(--err); } .logs .debug { color: var(--text-3); }
189
+ .logs .lvl-ico { margin-right: 5px; font-weight: 700; }
190
+ .logs .warn .lvl-ico { color: var(--warn); }
191
+ .logs .error .lvl-ico { color: var(--err); }
192
+ .log-filter { display: inline-flex; gap: 2px; padding: 2px; border-radius: 9px; background: var(--hover); }
193
+ .log-filter button { border: none; background: transparent; color: var(--text-2); font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 7px; cursor: pointer; }
194
+ .log-filter button.active { background: var(--surface); color: var(--text); box-shadow: 0 1px 3px rgba(0,0,0,.08); }
195
+ html[data-theme="dark"] .log-filter button.active { background: rgba(255,255,255,.1); }
183
196
  .collapsed .logs { display: none; }
184
197
 
185
198
  /* modal */
@@ -198,8 +211,10 @@
198
211
  .pane { display: none; } .pane.active { display: block; }
199
212
  .field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 10px; }
200
213
  .field label { font-size: 12px; color: var(--text-2); font-weight: 600; }
201
- .input { width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: 9px; background: var(--surface-2); outline: none; transition: border-color .15s; }
214
+ .input { width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: 9px; background: var(--surface-2); color: var(--text); outline: none; transition: border-color .15s; }
215
+ .input::placeholder { color: var(--text-3); opacity: .7; }
202
216
  .input:focus { border-color: #4f86ff; }
217
+ .input:disabled { opacity: .6; cursor: not-allowed; }
203
218
  .input.mono { font-family: ui-monospace, Consolas, monospace; }
204
219
  .row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
205
220
  .hint { font-size: 12px; color: var(--text-3); line-height: 1.7; }
@@ -247,7 +262,7 @@
247
262
  .prod-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
248
263
  .prod-name { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 700; }
249
264
  .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
250
- .dot.qoder { background: #7c3aed; } .dot.workbuddy { background: #14b8a6; } .dot.zcode { background: #2563eb; } .dot.mirasim { background: #64748b; } .dot.catpaw { background: #f97316; } .dot.trae { background: #10b981; }
265
+ .dot.qoder { background: #7c3aed; } .dot.workbuddy { background: #14b8a6; } .dot.zcode { background: #2563eb; } .dot.mirasim { background: #64748b; } .dot.catpaw { background: #f97316; } .dot.trae { background: #10b981; } .dot.minimax { background: #f43f5e; }
251
266
  .prod-go { color: var(--text-3); font-size: 12px; white-space: nowrap; display: inline-flex; align-items: center; gap: 2px; }
252
267
  .prod-go svg { width: 12px; height: 12px; transition: transform .15s; }
253
268
  .prodcard:hover .prod-go { color: var(--text); }
@@ -363,6 +378,7 @@
363
378
  <button data-view="mirasim">mirasim<span class="cnt" id="cnt-mirasim">0</span></button>
364
379
  <button data-view="catpaw">妙手<span class="cnt" id="cnt-catpaw">0</span></button>
365
380
  <button data-view="trae">Trae<span class="cnt" id="cnt-trae">0</span></button>
381
+ <button data-view="minimax">MiniMax<span class="cnt" id="cnt-minimax">0</span></button>
366
382
  <button data-view="tenrouter">10Router<span class="cnt" id="cnt-tenrouter">–</span></button>
367
383
  </nav>
368
384
 
@@ -413,8 +429,13 @@
413
429
  <button class="btn" id="zt-open" style="display:none"></button>
414
430
  </span>
415
431
  <span id="t-actions" style="display:none">
432
+ <button class="btn" id="tt-gw"></button>
416
433
  <button class="btn" id="tt-open" style="display:none"></button>
417
434
  </span>
435
+ <span id="m-actions" style="display:none">
436
+ <button class="btn" id="mt-gw"></button>
437
+ <button class="btn" id="mt-open" style="display:none"></button>
438
+ </span>
418
439
  <button class="btn" id="btn-add"></button>
419
440
  <button class="btn" id="btn-import"></button>
420
441
  <button class="btn" id="btn-export"></button>
@@ -430,9 +451,19 @@
430
451
  <div class="panel" id="tr-panel"><div class="empty">加载中…</div></div>
431
452
  </section>
432
453
 
454
+ <!-- 日志卡:运行日志 + 各网关调用日志,卡内标签切换(默认跟随当前页,可手动切) -->
433
455
  <section class="pcard collapsed" id="log-card">
434
456
  <div class="pcard-title">
435
- <span>运行日志</span>
457
+ <div class="seg-ctl" id="log-tabs">
458
+ <button data-log-tab="run" class="active">运行日志</button>
459
+ <button data-log-tab="zcode">ZCode 网关</button>
460
+ <button data-log-tab="minimax">MiniMax 网关</button>
461
+ <button data-log-tab="trae">Trae 网关</button>
462
+ </div>
463
+ <div class="log-filter" id="log-filter">
464
+ <button data-log-level="all" class="active">全部</button>
465
+ <button data-log-level="warn">只看提醒</button>
466
+ </div>
436
467
  <button class="btn" id="btn-logs">展开</button>
437
468
  </div>
438
469
  <div class="logs" id="logs">(暂无)</div>
@@ -463,6 +494,7 @@
463
494
  <option value="qoder">Qoder 国际版</option><option value="qoder-cn">Qoder 国内版</option>
464
495
  <option value="workbuddy">WorkBuddy / CodeBuddy 国内版</option><option value="workbuddy-intl">WorkBuddy / CodeBuddy 国际版</option>
465
496
  <option value="zcode-bigmodel">ZCode · BigModel(智谱)</option><option value="zcode-zai">ZCode · Z.ai(国际)</option>
497
+ <option value="minimax">MiniMax Code</option>
466
498
  </select>
467
499
  </div>
468
500
  <div class="row"><button class="btn primary" id="btn-device">生成授权链接</button><button class="btn" id="dev-cancel" style="display:none">取消</button></div>
@@ -475,7 +507,7 @@
475
507
  <div class="pane" data-pane="token">
476
508
  <div class="field">
477
509
  <label>版本</label>
478
- <select class="input" id="f-provider"><option value="qoder">Qoder 国际版</option><option value="qoder-cn">Qoder 国内版</option><option value="workbuddy">WorkBuddy 国内版</option><option value="workbuddy-intl">WorkBuddy 国际版</option></select>
510
+ <select class="input" id="f-provider"><option value="qoder">Qoder 国际版</option><option value="qoder-cn">Qoder 国内版</option><option value="workbuddy">WorkBuddy 国内版</option><option value="workbuddy-intl">WorkBuddy 国际版</option><option value="minimax">MiniMax</option></select>
479
511
  </div>
480
512
  <div class="field"><label>备注名(可选)</label><input class="input" id="f-name" placeholder="留空则自动读取昵称"></div>
481
513
  <div class="field"><label>Token</label><input class="input mono" id="f-token" placeholder="dt-… / jt-… 或 PAT(pt-…,自动兑换)"></div>
@@ -490,13 +522,14 @@
490
522
  <div class="modal-title"><span>导出账号</span><button class="close" data-close>✕</button></div>
491
523
  <div class="field">
492
524
  <label>范围</label>
493
- <select class="input" id="ex-provider"><option value="">全部账号</option><option value="p:qoder">Qoder(全部)</option><option value="qoder">仅 Qoder 国际版</option><option value="qoder-cn">仅 Qoder 国内版</option><option value="p:workbuddy">WorkBuddy(全部)</option><option value="workbuddy">仅 WorkBuddy 国内版</option><option value="workbuddy-intl">仅 WorkBuddy 国际版</option><option value="zcode">ZCode</option><option value="p:mirasim">mirasim</option><option value="p:catpaw">妙手</option><option value="p:trae">Trae</option></select>
525
+ <select class="input" id="ex-provider"><option value="">全部账号</option><option value="p:qoder">Qoder(全部)</option><option value="qoder">仅 Qoder 国际版</option><option value="qoder-cn">仅 Qoder 国内版</option><option value="p:workbuddy">WorkBuddy(全部)</option><option value="workbuddy">仅 WorkBuddy 国内版</option><option value="workbuddy-intl">仅 WorkBuddy 国际版</option><option value="zcode">ZCode</option><option value="p:mirasim">mirasim</option><option value="p:catpaw">妙手</option><option value="p:trae">Trae</option><option value="p:minimax">MiniMax</option></select>
494
526
  </div>
495
527
  <div class="field">
496
528
  <label>加密口令(必填)</label>
497
529
  <input class="input" id="ex-password" type="password" placeholder="至少 4 位" autocomplete="new-password">
498
530
  </div>
499
531
  <div class="hint">导出文件一律加密(10router 迁移格式,scrypt + AES-256-GCM),可直接在 10router 对应提供商页导入(WorkBuddy 对应 10router 的 CodeBuddy)。10router 按提供商导入,建议按单个版本导出。<b>口令不会被保存</b>,请牢记——遗失后文件无法解开、无法导入。</div>
532
+ <div class="notice" id="ex-session-note" style="display:none"></div>
500
533
  <div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="btn-do-export">导出</button></div>
501
534
  </div>
502
535
  </div>
@@ -520,6 +553,12 @@
520
553
  <label>版本与更新</label>
521
554
  <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap"><span class="dim" id="upd-ver"></span><button class="btn" id="btn-update">检查更新</button></div>
522
555
  </div>
556
+ <div class="field" style="margin-top:12px;padding-top:10px;border-top:1px dashed var(--border-subtle)">
557
+ <label>服务维护</label>
558
+ <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
559
+ <button class="btn" id="btn-restart-app">重启 CreditDaddy</button>
560
+ </div>
561
+ </div>
523
562
  <div class="modal-actions"><button class="btn" data-close>关闭</button></div>
524
563
  </div>
525
564
  </div>
@@ -628,18 +667,54 @@
628
667
  <div class="modal-title"><span>ZCode 体验包接口</span><button class="close" data-close>✕</button></div>
629
668
  <div class="hint" id="zgw-url" style="margin-bottom:10px"></div>
630
669
  <div class="field">
631
- <label class="check"><input type="checkbox" id="zgw-on"> 启用体验包接口(/gateway/v1/messages)</label>
670
+ <label class="check"><input type="checkbox" id="zgw-on"> 启用体验包接口(/gateway/zcode/v1/messages)</label>
632
671
  </div>
633
672
  <div class="field" id="zgw-lan-fields">
634
673
  <label class="check"><input type="checkbox" id="zgw-lan"> 对局域网开放(重启后绑定 0.0.0.0)</label>
635
674
  <label>IP 白名单(每行一个,支持 192.168.31.* 通配)</label>
636
675
  <textarea class="input" id="zgw-allow" rows="4" placeholder="192.168.31.101"></textarea>
637
676
  </div>
638
- <div class="hint">开启后,领到的体验包额度变成本机的一个补全接口——10Router 建一个 anthropic-compatible 节点指向本机 /gateway 即可调用(需要桌面版,隐藏窗口过验证码)。白名单内的机器<b>免密直连</b>:10Router 的 zcode-free 本就是免授权供应商,把 10Router 服务地址的 IP 加白即可;白名单外的请求一律拒绝。本机回环(127.0.0.1)始终放行。</div>
677
+ <div class="hint">开启后,领到的体验包额度变成本机的一个补全接口——10Router 建一个 anthropic-compatible 节点指向本机 <code>/gateway/zcode/v1/messages</code> 即可调用(需要桌面版,隐藏窗口过验证码)。白名单内的机器<b>免密直连</b>:10Router 的 zcode-free 本就是免授权供应商,把 10Router 服务地址的 IP 加白即可;白名单外的请求一律拒绝。本机回环(127.0.0.1)始终放行。</div>
639
678
  <div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="zgw-ok">保存</button></div>
640
679
  </div>
641
680
  </div>
642
681
 
682
+ <!-- MiniMax 本地接口 -->
683
+ <div class="mask" id="m-mgw">
684
+ <div class="modal sm">
685
+ <div class="modal-title"><span>MiniMax 本地接口</span><button class="close" data-close>✕</button></div>
686
+ <div class="hint" id="mgw-url" style="margin-bottom:10px"></div>
687
+ <div class="field">
688
+ <label class="check"><input type="checkbox" id="mgw-on"> 启用本地接口(/gateway/minimax/v1/messages)</label>
689
+ </div>
690
+ <div class="field" id="mgw-lan-fields">
691
+ <label class="check"><input type="checkbox" id="mgw-lan"> 对局域网开放(重启后绑定 0.0.0.0)</label>
692
+ <label>IP 白名单(每行一个,支持 192.168.31.* 通配)</label>
693
+ <textarea class="input" id="mgw-allow" rows="4" placeholder="192.168.31.101"></textarea>
694
+ </div>
695
+ <div class="hint">开启后,MiniMax 积分与模型能力变成本机的一个原生 Anthropic Messages 补全接口——在 10Router 或 Claude Code 等工具中建一个 anthropic-compatible 节点指向本机 <code>/gateway/minimax/v1/messages</code> 即可调用。支持多账号自动轮转、429 限流自动冷却 5 分钟、凭据失效自动静默刷新。白名单内的机器<b>免密直连</b>;本机回环(127.0.0.1)始终放行。</div>
696
+ <div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="mgw-ok">保存</button></div>
697
+ </div>
698
+ </div>
699
+
700
+ <!-- Trae 本地接口 -->
701
+ <div class="mask" id="m-tgw">
702
+ <div class="modal sm">
703
+ <div class="modal-title"><span>Trae 本地接口</span><button class="close" data-close>✕</button></div>
704
+ <div class="hint" id="tgw-url" style="margin-bottom:10px"></div>
705
+ <div class="field">
706
+ <label class="check"><input type="checkbox" id="tgw-on"> 启用本地接口(/gateway/trae/v1/messages)</label>
707
+ </div>
708
+ <div class="field" id="tgw-lan-fields">
709
+ <label class="check"><input type="checkbox" id="tgw-lan"> 对局域网开放(重启后绑定 0.0.0.0)</label>
710
+ <label>IP 白名单(每行一个,支持 192.168.31.* 通配)</label>
711
+ <textarea class="input" id="tgw-allow" rows="4" placeholder="192.168.31.101"></textarea>
712
+ </div>
713
+ <div class="hint">开启后,Trae SOLO 积分与模型能力(Doubao-Seed / GLM-5.1 / Kimi-K2.6 / DeepSeek-V4 等 12 款模型)变成本机的一个原生 Anthropic Messages 补全接口——在 10Router 或 Claude Code 等工具中建一个 anthropic-compatible 节点指向本机 <code>/gateway/trae/v1/messages</code> 即可调用。支持多账号自动加权轮转、429 限流自动冷却 5 分钟、流式增量与完整思考过程(thinking_delta)。白名单内的机器<b>免密直连</b>;本机回环(127.0.0.1)始终放行。</div>
714
+ <div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="tgw-ok">保存</button></div>
715
+ </div>
716
+ </div>
717
+
643
718
  <input type="file" id="file-input" accept=".json,application/json" style="display:none">
644
719
  <div id="toast"></div>
645
720
 
@@ -669,8 +744,8 @@ const I = {
669
744
  };
670
745
 
671
746
  const $ = (id) => document.getElementById(id);
672
- const state = { accounts: [], quotas: {}, status: null, busy: {}, view: 'dash', region: '', addProduct: '' };
673
- const VIEWS = ['dash', 'qoder', 'workbuddy', 'zcode', 'mirasim', 'catpaw', 'trae', 'tenrouter'];
747
+ const state = { accounts: [], quotas: {}, status: null, busy: {}, view: 'dash', region: '', addProduct: '', logTab: 'run', logLevel: 'all' };
748
+ const VIEWS = ['dash', 'qoder', 'workbuddy', 'zcode', 'mirasim', 'catpaw', 'trae', 'minimax', 'tenrouter'];
674
749
  // 10Router 集成:config 来自 /api/tenrouter,quotas 来自 /api/tenrouter/quotas
675
750
  const tr = { config: null, quotas: null, error: null, loading: false, syncing: false };
676
751
  const PRODUCTS = [
@@ -680,6 +755,7 @@ const PRODUCTS = [
680
755
  { id: 'mirasim', label: 'mirasim' },
681
756
  { id: 'catpaw', label: '妙手' },
682
757
  { id: 'trae', label: 'Trae' },
758
+ { id: 'minimax', label: 'MiniMax' },
683
759
  ];
684
760
  const prodLabel = (id) => (PRODUCTS.find((x) => x.id === id) || { label: id }).label;
685
761
 
@@ -691,6 +767,7 @@ const COMPONENTS = [
691
767
  { id: 'mirasim', label: 'mirasim' },
692
768
  { id: 'catpaw', label: '妙手' },
693
769
  { id: 'trae', label: 'Trae' },
770
+ { id: 'minimax', label: 'MiniMax' },
694
771
  { id: 'tenrouter', label: '10Router' },
695
772
  ];
696
773
  const HIDDEN_COMP_KEY = 'qd_hidden_components';
@@ -812,12 +889,15 @@ const PROV = {
812
889
  mirasim: { prod: 'mirasim', prodLabel: 'mirasim', region: '', cls: '' },
813
890
  catpaw: { prod: 'catpaw', prodLabel: '妙手', region: '', cls: '' },
814
891
  trae: { prod: 'trae', prodLabel: 'Trae', region: '', cls: '' },
892
+ minimax: { prod: 'minimax', prodLabel: 'MiniMax', region: '', cls: '' },
815
893
  };
816
894
  // 支持每日签到的产品(ZCode / mirasim / 妙手 额度靠套餐或滚动窗口发放,不做签到)
817
- const CHECKIN_PRODUCTS = new Set(['qoder', 'workbuddy', 'trae']);
818
- const CLIENT_LABEL = { qoder: 'Qoder', workbuddy: 'WorkBuddy', zcode: 'ZCode', mirasim: 'mirasim', catpaw: '妙手', trae: 'Trae' };
895
+ const CHECKIN_PRODUCTS = new Set(['qoder', 'workbuddy', 'trae', 'minimax']);
896
+ const CLIENT_LABEL = { qoder: 'Qoder', workbuddy: 'WorkBuddy', zcode: 'ZCode', mirasim: 'mirasim', catpaw: '妙手', trae: 'Trae', minimax: 'MiniMax' };
819
897
  // 额度单位为 Credits/时长而非「积分」口径的产品(不计入剩余积分合计)
820
898
  const CREDIT_VIEWS = new Set(['zcode', 'mirasim', 'catpaw']);
899
+ // 网关调用日志:tag → 所属产品页(无网关的产品不展示日志卡)
900
+ const GW_LOG_TAG = { zcode: 'ZCODE-GW', minimax: 'MINIMAX-GW', trae: 'TRAE-GW' };
821
901
  const provOf = (p) => PROV[p] || { prod: 'qoder', prodLabel: p, region: '', cls: '' };
822
902
  const provBadges = (p, regionOnly, flavor) => {
823
903
  const v = provOf(p);
@@ -928,8 +1008,20 @@ function applyTheme(t) {
928
1008
  function initTheme() {
929
1009
  let t = null;
930
1010
  try { t = localStorage.getItem('qd_theme'); } catch (e) {}
931
- if (!t) t = window.matchMedia && matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
1011
+ const mq = window.matchMedia ? matchMedia('(prefers-color-scheme: dark)') : null;
1012
+ if (!t) t = mq && mq.matches ? 'dark' : 'light';
932
1013
  applyTheme(t);
1014
+ // 自动跟随系统:用户没手动切过(localStorage 无值)时,系统换深色/浅色实时响应,
1015
+ // 不用刷新页面;手动切过则尊重用户选择,不再跟系统跑。
1016
+ const onSysTheme = (e) => {
1017
+ let saved = null;
1018
+ try { saved = localStorage.getItem('qd_theme'); } catch (err) {}
1019
+ if (!saved) applyTheme(e.matches ? 'dark' : 'light');
1020
+ };
1021
+ if (mq) {
1022
+ if (mq.addEventListener) mq.addEventListener('change', onSysTheme);
1023
+ else if (mq.addListener) mq.addListener(onSysTheme); // 旧 Safari
1024
+ }
933
1025
  $('btn-theme').onclick = () => {
934
1026
  const n = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
935
1027
  applyTheme(n);
@@ -942,7 +1034,8 @@ function initTheme() {
942
1034
  function statusOf(a) {
943
1035
  if (!CHECKIN_PRODUCTS.has(a.product)) return { cls: '', text: '', msg: '' }; // ZCode 无每日领取,不显示徽标
944
1036
  const intl = a.provider === 'workbuddy-intl';
945
- const today = state.status && state.status.today;
1037
+ const byProd = (state.status && state.status.todayByProduct) || {};
1038
+ const today = byProd[a.product] || (state.status && state.status.today);
946
1039
  const done = state.status && state.status.todayDone && state.status.todayDone[a.id] === today;
947
1040
  const r = a.lastResult;
948
1041
  const fresh = r && r.at && state.status && (Date.now() - new Date(r.at).getTime() < 26 * 3600e3);
@@ -1104,7 +1197,10 @@ function cardHtml(a) {
1104
1197
  + provBadges(a.provider, true, a.flavor)
1105
1198
  + (isCurrent ? '<span class="pill cur" title="' + esc(CLIENT_LABEL[a.product] || '') + ' 客户端当前登录的账号">客户端当前</span>' : '')
1106
1199
  + (a.product === 'zcode' && isGatewayCurrent(a) ? '<span class="pill gw" title="体验包接口最近一次补全用的账号(多账号时按轮换自动选择,与客户端登录无关)">接口使用中</span>' : '')
1200
+ + (a.product === 'minimax' && isMiniMaxGatewayCurrent(a) ? '<span class="pill gw" title="本地接口最近一次补全用的账号(多账号时按轮换自动选择,与客户端登录无关)">接口使用中</span>' : '')
1201
+ + (a.product === 'trae' && isTraeGatewayCurrent(a) ? '<span class="pill gw" title="本地接口最近一次补全用的账号(多账号时按轮换自动选择,与客户端登录无关)">接口使用中</span>' : '')
1107
1202
  + (a.isPat ? '<span class="badge">PAT</span>' : '')
1203
+ + qoderWebBadge(a)
1108
1204
  + (a.verified === false ? '<span class="pill fail">token 无效</span>' : '')
1109
1205
  + (st.text ? '<span class="pill ' + st.cls + '" title="' + esc(a.lastResult ? fmtTime(a.lastResult.at) + ' ' + (a.lastResult.message || a.lastResult.status) : '') + '">' + esc(st.text) + '</span>' : '')
1110
1206
  + '</div><div class="ops">'
@@ -1125,7 +1221,14 @@ function cardHtml(a) {
1125
1221
  + '</div>';
1126
1222
  }
1127
1223
 
1128
- const isDone = (a) => Boolean(state.status && state.status.todayDone && state.status.todayDone[a.id] === state.status.today);
1224
+ const isDone = (a) => {
1225
+ if (!state.status || !state.status.todayDone) return false;
1226
+ const doneDay = state.status.todayDone[a.id];
1227
+ if (!doneDay) return false;
1228
+ const byProd = state.status.todayByProduct || {};
1229
+ const expectedDay = byProd[a.product] || state.status.today;
1230
+ return doneDay === expectedDay;
1231
+ };
1129
1232
  const ofProduct = (prod) => state.accounts.filter((a) => a.product === prod);
1130
1233
  const currentUidOf = (prod) => state.status && (prod === 'workbuddy' ? state.status.workbuddyCurrentUid : prod === 'zcode' ? state.status.zcodeCurrentUid : prod === 'catpaw' ? state.status.catpawCurrentUid : prod === 'trae' ? state.status.traeCurrentUid : null);
1131
1234
  /** 接口使用中判定:体验包接口最近一次补全用的账号(lastAccount 记的是 name || uid || id) */
@@ -1133,8 +1236,18 @@ function isGatewayCurrent(a) {
1133
1236
  const last = state.zGatewayStats && state.zGatewayStats.lastAccount;
1134
1237
  return Boolean(last) && (a.name === last || a.uid === last || a.id === last);
1135
1238
  }
1239
+ /** MiniMax 本地接口使用中判定 */
1240
+ function isMiniMaxGatewayCurrent(a) {
1241
+ const last = state.mGatewayStats && state.mGatewayStats.lastAccount;
1242
+ return Boolean(last) && (a.name === last || a.uid === last || a.id === last);
1243
+ }
1244
+ /** Trae 本地接口使用中判定 */
1245
+ function isTraeGatewayCurrent(a) {
1246
+ const last = state.tGatewayStats && state.tGatewayStats.lastAccount;
1247
+ return Boolean(last) && (a.name === last || a.uid === last || a.id === last);
1248
+ }
1136
1249
 
1137
- // 客户端当前登录判定:WorkBuddy / mirasim / 妙手 按 uid;ZCode 按 uid 或 email(早期导入的账号可能只存了其中一个)
1250
+ // 客户端当前登录判定:WorkBuddy / mirasim / 妙手 / MiniMax 按 uid;ZCode 按 uid 或 email(早期导入的账号可能只存了其中一个)
1138
1251
  function isClientCurrent(a) {
1139
1252
  const s = state.status;
1140
1253
  if (!s) return false;
@@ -1146,6 +1259,7 @@ function isClientCurrent(a) {
1146
1259
  if (a.product === 'mirasim') return Boolean(a.uid && s.mirasimCurrentUid && s.mirasimCurrentUid === a.uid);
1147
1260
  if (a.product === 'catpaw') return Boolean(a.uid && s.catpawCurrentUid && s.catpawCurrentUid === a.uid);
1148
1261
  if (a.product === 'trae') return Boolean(a.uid && s.traeCurrentUid && s.traeCurrentUid === a.uid);
1262
+ if (a.product === 'minimax') return Boolean(a.uid && s.minimaxCurrentUid && s.minimaxCurrentUid === a.uid);
1149
1263
  if (a.product === 'zcode') {
1150
1264
  const id = s.zcodeCurrentIdentity;
1151
1265
  return Boolean(id && ((a.uid && id.uid != null && String(id.uid) === String(a.uid)) || (a.email && id.email && id.email === a.email)));
@@ -1169,6 +1283,7 @@ function creditSum(list) {
1169
1283
  function render() {
1170
1284
  $('z-actions').style.display = 'none'; // 兜底:切到仪表盘 / 10Router 时 renderProd 不跑,先藏起来
1171
1285
  $('t-actions').style.display = 'none';
1286
+ $('m-actions').style.display = 'none';
1172
1287
  for (const p of PRODUCTS) $('cnt-' + p.id).textContent = ofProduct(p.id).length;
1173
1288
  $('cnt-tenrouter').textContent = tr.quotas ? tr.quotas.connections.length : tr.config && tr.config.configured ? '…' : '–';
1174
1289
  if (state.view === 'dash') renderDash();
@@ -1183,6 +1298,7 @@ const EMPTY_TIP = {
1183
1298
  mirasim: '还没有 mirasim 账号。<br>点「添加账号」可读取本机 mirasim 客户端当前登录的账号(本地解密,token 不出本机)。',
1184
1299
  catpaw: '还没有妙手账号。<br>点「添加账号」可读取本机妙手(美团 CatPaw)客户端当前登录的账号(本地解密凭据,token 不出本机)。',
1185
1300
  trae: '还没有 Trae 账号。<br>点「添加账号 → 本机导入」可读取本机 Trae 客户端当前登录的账号(本地解密 storage.json 凭据,token 不出本机)。多个账号需先在 Trae 里分别登录并逐个导入。',
1301
+ minimax: '还没有 MiniMax 账号。<br>点「添加账号 → 本机导入」可读取本机 MiniMax Code 客户端当前登录的账号,或粘贴 Bearer Token 添加。',
1186
1302
  };
1187
1303
 
1188
1304
  function renderProd() {
@@ -1211,7 +1327,7 @@ function renderProd() {
1211
1327
  $('s-credits').textContent = CREDIT_VIEWS.has(prod)
1212
1328
  ? acc.filter((a) => { const q = state.quotas[a.id]; return q && !q.error && !q.empty; }).length + ' / ' + acc.length
1213
1329
  : c.loading ? '…' : (c.known ? fmtNum(c.sum) : '-');
1214
- const hasRegions = !CREDIT_VIEWS.has(prod) && prod !== 'trae'; // Trae 不分国际 / 国内版
1330
+ const hasRegions = !CREDIT_VIEWS.has(prod) && prod !== 'trae' && prod !== 'minimax'; // Trae / MiniMax 不分国际 / 国内版
1215
1331
  $('region').style.display = hasRegions ? '' : 'none';
1216
1332
  $('region-div').style.display = hasRegions ? '' : 'none';
1217
1333
  document.querySelectorAll('#region button').forEach((x) => x.classList.toggle('active', x.dataset.r === state.region));
@@ -1242,6 +1358,9 @@ function renderProd() {
1242
1358
  }
1243
1359
  $('t-actions').style.display = prod === 'trae' ? 'contents' : 'none';
1244
1360
  if (prod === 'trae') {
1361
+ const gwOn = state.tGateway === true;
1362
+ $('tt-gw').innerHTML = dot(gwOn) + '接口' + (gwOn && state.tGatewayLan === true ? '·局域网' : '');
1363
+ $('tt-gw').title = 'Trae 本地接口:开关、局域网开放与 IP 白名单';
1245
1364
  const tc = (state.status && state.status.traeClient) || {};
1246
1365
  const canOpen = tc.installed && window.creditdaddy && typeof window.creditdaddy.openTraeClient === 'function';
1247
1366
  $('tt-open').style.display = canOpen ? '' : 'none';
@@ -1250,6 +1369,19 @@ function renderProd() {
1250
1369
  $('tt-open').title = tc.running ? 'Trae 客户端运行中,点我唤到前台' : 'Trae 客户端未运行,点我启动';
1251
1370
  }
1252
1371
  }
1372
+ $('m-actions').style.display = prod === 'minimax' ? 'contents' : 'none';
1373
+ if (prod === 'minimax') {
1374
+ const gwOn = state.mGateway === true;
1375
+ $('mt-gw').innerHTML = dot(gwOn) + '接口' + (gwOn && state.mGatewayLan === true ? '·局域网' : '');
1376
+ $('mt-gw').title = 'MiniMax 本地接口:开关、局域网开放与 IP 白名单';
1377
+ const mc = (state.status && state.status.minimaxClient) || {};
1378
+ const canOpen = mc.installed && window.creditdaddy && typeof window.creditdaddy.openMiniMaxClient === 'function';
1379
+ $('mt-open').style.display = canOpen ? '' : 'none';
1380
+ if (canOpen) {
1381
+ $('mt-open').innerHTML = dot(mc.running === true, true) + '打开客户端';
1382
+ $('mt-open').title = mc.running ? 'MiniMax Code 客户端运行中,点我唤到前台' : 'MiniMax Code 客户端未运行,点我启动';
1383
+ }
1384
+ }
1253
1385
  }
1254
1386
 
1255
1387
  function kpi(label, value, sub, labelTitle) {
@@ -1266,7 +1398,7 @@ function renderDash() {
1266
1398
  const failed = signable.filter((a) => statusOf(a).cls === 'fail').length;
1267
1399
  const credit = creditSum(state.accounts.filter((a) => a.product !== 'zcode'));
1268
1400
  const byProd = orderedVisibleComps().filter((id) => id !== 'tenrouter').map((id) => prodLabel(id) + ' ' + ofProduct(id).length).join(' · ');
1269
- const creditByProd = ['qoder', 'workbuddy'].filter(compVisible).map((id) => { const c = creditSum(ofProduct(id)); return prodLabel(id) + ' ' + (c.known ? fmtNum(c.sum) : '-'); }).join(' · ');
1401
+ const creditByProd = ['qoder', 'workbuddy', 'minimax'].filter(compVisible).map((id) => { const c = creditSum(ofProduct(id)); return prodLabel(id) + ' ' + (c.known ? fmtNum(c.sum) : '-'); }).join(' · ');
1270
1402
  // 今日进度拆「国内领取 / 国际活跃」:国际版 WorkBuddy 是保持活跃(活跃不等于必有奖励)
1271
1403
  const intlAcc = signable.filter((a) => a.provider === 'workbuddy-intl');
1272
1404
  const domAcc = signable.filter((a) => a.provider !== 'workbuddy-intl');
@@ -1306,11 +1438,18 @@ function renderDash() {
1306
1438
  + (cnList.length && intlList.length ? ' · ' : '')
1307
1439
  + (intlList.length ? intlList.filter(isDone).length + ' / ' + intlList.length + ' 国际' : '')
1308
1440
  + '</span>';
1441
+ } else if (p.id === 'minimax') {
1442
+ const planned = list.filter((a) => { const q = state.quotas[a.id]; return q && !q.error && !q.empty; }).length;
1443
+ rows = '<span class="lbl">今日领取</span><span class="val">' + d + ' / ' + list.length + '</span>'
1444
+ + '<span style="grid-column:1 / -1">' + meter(d, list.length) + '</span>'
1445
+ + '<span class="lbl">剩余积分</span><span class="val">' + (c.known ? fmtNum(c.sum) : '-') + '</span>';
1309
1446
  } else {
1310
1447
  rows = '<span class="lbl">今日领取</span><span class="val">' + d + ' / ' + list.length + '</span>';
1311
1448
  }
1312
- rows += '<span style="grid-column:1 / -1">' + meter(d, list.length) + '</span>'
1313
- + '<span class="lbl">剩余积分</span><span class="val">' + (c.known ? fmtNum(c.sum) : '-') + '</span>';
1449
+ if (p.id !== 'minimax') {
1450
+ rows += '<span style="grid-column:1 / -1">' + meter(d, list.length) + '</span>'
1451
+ + '<span class="lbl">剩余积分</span><span class="val">' + (c.known ? fmtNum(c.sum) : '-') + '</span>';
1452
+ }
1314
1453
  } else if (p.id === 'mirasim') {
1315
1454
  const planned = list.filter((a) => { const q = state.quotas[a.id]; return q && !q.error && !q.empty; }).length;
1316
1455
  rows = '<span class="lbl">额度体系</span><span class="val dim">5h / 7d 滚动窗口</span>'
@@ -1337,6 +1476,7 @@ function renderDash() {
1337
1476
  : p.id === 'mirasim' && s.mirasimCurrentUid ? '未导入的账号'
1338
1477
  : p.id === 'catpaw' && s.catpawClient && s.catpawClient.signedIn && !list.some((a) => isClientCurrent(a)) ? '未导入的账号'
1339
1478
  : p.id === 'trae' && s.traeClient && s.traeClient.signedIn && !list.some((a) => isClientCurrent(a)) ? '未导入的账号'
1479
+ : p.id === 'minimax' && s.minimaxClient && s.minimaxClient.signedIn && !list.some((a) => isClientCurrent(a)) ? '未导入的账号'
1340
1480
  : (p.id === 'workbuddy' && s.workbuddyCurrentUid ? '未导入的账号' : '—');
1341
1481
  rows += '<span class="lbl">客户端当前</span><span class="val">' + live + '</span>';
1342
1482
  }
@@ -1409,8 +1549,11 @@ function renderStatus() {
1409
1549
  // 产品相关提示只在对应标签页出现
1410
1550
  const hasIntl = state.view === 'qoder' && state.region !== 'cn' && state.accounts.some((a) => a.provider === 'qoder');
1411
1551
  const n = $('notice');
1412
- n.classList.toggle('info', state.view === 'zcode' || state.view === 'mirasim' || state.view === 'catpaw' || state.view === 'trae' || (hasIntl && Boolean(s.riskIdentity)));
1413
- if (state.view === 'trae') {
1552
+ n.classList.toggle('info', state.view === 'zcode' || state.view === 'mirasim' || state.view === 'catpaw' || state.view === 'trae' || state.view === 'minimax' || (hasIntl && Boolean(s.riskIdentity)));
1553
+ if (state.view === 'minimax') {
1554
+ n.style.display = '';
1555
+ n.innerHTML = '<div class="z-row"><span class="z-label">每日签到</span><span class="z-text">每日签到领 800~2000 积分 + 400 连签加成。支持自动检测本机 MiniMax Code 凭据与 Bearer Token 导入,自带标准 Anthropic 本地网关。</span></div>';
1556
+ } else if (state.view === 'trae') {
1414
1557
  n.style.display = '';
1415
1558
  n.innerHTML = '<div class="z-row"><span class="z-label">每日签到</span><span class="z-text">每日签到领约 150 通用积分(7 天有效,多资源包自动归并)。凭据由本机 Trae 客户端解密导入,支持多账号快照一键切换。</span></div>';
1416
1559
  } else if (state.view === 'catpaw') {
@@ -1515,6 +1658,44 @@ document.addEventListener('click', async (e) => {
1515
1658
  } catch (err) { toast(String(err && err.message || err), false); }
1516
1659
  return;
1517
1660
  }
1661
+ if (e.target.closest('#mt-open')) {
1662
+ try {
1663
+ const r = await window.creditdaddy.openMiniMaxClient();
1664
+ if (r && r.ok) toast('已启动 MiniMax Code 客户端');
1665
+ else toast((r && r.error) || '无法打开 MiniMax Code 客户端', false);
1666
+ } catch (err) { toast(String(err && err.message || err), false); }
1667
+ return;
1668
+ }
1669
+ if (e.target.closest('#tt-gw')) {
1670
+ // Trae 本地接口弹窗:开关 + 局域网 + 白名单设置
1671
+ $('tgw-on').checked = state.tGateway === true;
1672
+ $('tgw-lan').checked = state.tGatewayLan === true;
1673
+ const allow = Array.isArray(state.tGatewayAllow) ? state.tGatewayAllow : [];
1674
+ $('tgw-allow').value = (allow.length ? allow : (state.tGatewaySuggest ? [state.tGatewaySuggest] : [])).join('\n');
1675
+ const st = state.tGatewayStats;
1676
+ $('tgw-url').innerHTML = '接口地址 <code>POST http://127.0.0.1:' + location.port + '/gateway/trae/v1/messages</code>(局域网开放时把 127.0.0.1 换成本机 IP)'
1677
+ + (st && st.lastCallAt
1678
+ ? '<br>最近调用 ' + esc(String(st.lastCallAt).slice(11, 19)) + ' · 共 ' + st.calls + ' 次' + (st.lastAccount ? ' · 最后用号 ' + esc(st.lastAccount) : '')
1679
+ : '<br>尚未有调用');
1680
+ syncTgwLanFields();
1681
+ openModal('m-tgw');
1682
+ return;
1683
+ }
1684
+ if (e.target.closest('#mt-gw')) {
1685
+ // MiniMax 本地接口弹窗:开关 + 局域网 + 白名单设置
1686
+ $('mgw-on').checked = state.mGateway === true;
1687
+ $('mgw-lan').checked = state.mGatewayLan === true;
1688
+ const allow = Array.isArray(state.mGatewayAllow) ? state.mGatewayAllow : [];
1689
+ $('mgw-allow').value = (allow.length ? allow : (state.mGatewaySuggest ? [state.mGatewaySuggest] : [])).join('\n');
1690
+ const st = state.mGatewayStats;
1691
+ $('mgw-url').innerHTML = '接口地址 <code>POST http://127.0.0.1:' + location.port + '/gateway/minimax/v1/messages</code>(局域网开放时把 127.0.0.1 换成本机 IP)'
1692
+ + (st && st.lastCallAt
1693
+ ? '<br>最近调用 ' + esc(String(st.lastCallAt).slice(11, 19)) + ' · 共 ' + st.calls + ' 次' + (st.lastAccount ? ' · 最后用号 ' + esc(st.lastAccount) : '')
1694
+ : '<br>尚未有调用');
1695
+ syncMgwLanFields();
1696
+ openModal('m-mgw');
1697
+ return;
1698
+ }
1518
1699
  if (e.target.closest('#zt-ac')) {
1519
1700
  // 自动领取计划弹窗:周期 / 运行时长由用户决定
1520
1701
  $('zac-interval').value = state.zClaimIntervalMin || 2;
@@ -1531,7 +1712,7 @@ document.addEventListener('click', async (e) => {
1531
1712
  const allow = Array.isArray(state.zGatewayAllow) ? state.zGatewayAllow : [];
1532
1713
  $('zgw-allow').value = (allow.length ? allow : (state.zGatewaySuggest ? [state.zGatewaySuggest] : [])).join('\n');
1533
1714
  const st = state.zGatewayStats;
1534
- $('zgw-url').innerHTML = '接口地址 <code>POST http://127.0.0.1:' + location.port + '/gateway/v1/messages</code>(局域网开放时把 127.0.0.1 换成本机 IP)'
1715
+ $('zgw-url').innerHTML = '接口地址 <code>POST http://127.0.0.1:' + location.port + '/gateway/zcode/v1/messages</code>(局域网开放时把 127.0.0.1 换成本机 IP)'
1535
1716
  + (st && st.lastCallAt
1536
1717
  ? '<br>最近调用 ' + esc(String(st.lastCallAt).slice(11, 19)) + ' · 共 ' + st.calls + ' 次' + (st.lastAccount ? ' · 最后用号 ' + esc(st.lastAccount) : '')
1537
1718
  : '<br>尚未有调用');
@@ -1570,6 +1751,31 @@ async function loadZGateway() {
1570
1751
  state.zGatewayHasCaptcha = d.hasCaptcha === true;
1571
1752
  state.zGatewayStats = d.stats || null;
1572
1753
  if (state.view === 'zcode') { renderStatus(); render(); }
1754
+ applyLogTabs(); // 网关开关变化影响日志标签显隐
1755
+ } catch (e) {}
1756
+ }
1757
+ async function loadMGateway() {
1758
+ try {
1759
+ const d = await api('/api/minimax-gateway');
1760
+ state.mGateway = d.enabled === true;
1761
+ state.mGatewayLan = d.lan === true;
1762
+ state.mGatewayAllow = d.allow || [];
1763
+ state.mGatewaySuggest = d.suggestAllow || '';
1764
+ state.mGatewayStats = d.stats || null;
1765
+ if (state.view === 'minimax') { renderStatus(); render(); }
1766
+ applyLogTabs(); // 网关开关变化影响日志标签显隐
1767
+ } catch (e) {}
1768
+ }
1769
+ async function loadTGateway() {
1770
+ try {
1771
+ const d = await api('/api/trae-gateway');
1772
+ state.tGateway = d.enabled === true;
1773
+ state.tGatewayLan = d.lan === true;
1774
+ state.tGatewayAllow = d.allow || [];
1775
+ state.tGatewaySuggest = d.suggestAllow || '';
1776
+ state.tGatewayStats = d.stats || null;
1777
+ if (state.view === 'trae') { renderStatus(); render(); }
1778
+ applyLogTabs(); // 网关开关变化影响日志标签显隐
1573
1779
  } catch (e) {}
1574
1780
  }
1575
1781
 
@@ -2080,12 +2286,54 @@ async function loadQuotas(ids) {
2080
2286
  };
2081
2287
  await Promise.all([worker(), worker(), worker()]);
2082
2288
  }
2289
+ let logTabView = null; // 当前日志标签所属的视图:切到别的视图时才重置跟随,同一视图内保留手动切换
2290
+ function followLogTab() {
2291
+ if (logTabView !== state.view) {
2292
+ state.logTab = (state.view !== 'dash' && GW_LOG_TAG[state.view]) ? state.view : 'run';
2293
+ logTabView = state.view;
2294
+ }
2295
+ }
2083
2296
  async function loadLogs() {
2297
+ followLogTab();
2298
+ applyLogTabs();
2299
+ await loadCurrentLogs();
2300
+ }
2301
+ function applyLogTabs() {
2302
+ // 网关开关关闭时,隐藏对应日志标签(无该网关时也看不到)
2303
+ document.querySelectorAll('#log-tabs button').forEach((b) => {
2304
+ const t = b.dataset.logTab;
2305
+ let show = true;
2306
+ if (t === 'zcode') show = Boolean(state.zGateway);
2307
+ if (t === 'minimax') show = Boolean(state.mGateway);
2308
+ if (t === 'trae') show = Boolean(state.tGateway);
2309
+ b.style.display = show ? '' : 'none';
2310
+ b.classList.toggle('active', t === state.logTab);
2311
+ });
2312
+ document.querySelectorAll('#log-filter button').forEach((b) => {
2313
+ b.classList.toggle('active', b.dataset.logLevel === state.logLevel);
2314
+ });
2315
+ }
2316
+ async function loadCurrentLogs() {
2317
+ const el = $('logs');
2318
+ if (!el) return;
2319
+ let logs = [];
2084
2320
  try {
2085
- const data = await api('/api/logs');
2086
- const el = $('logs');
2321
+ if (state.logTab === 'run') {
2322
+ const data = await api('/api/logs');
2323
+ logs = data.logs || [];
2324
+ } else {
2325
+ const data = await api('/api/logs?tag=' + encodeURIComponent(GW_LOG_TAG[state.logTab]));
2326
+ logs = data.logs || [];
2327
+ }
2328
+ if (state.logLevel !== 'all') {
2329
+ logs = logs.filter((l) => l.level === 'warn' || l.level === 'error');
2330
+ }
2087
2331
  const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 8;
2088
- el.innerHTML = data.logs.length ? data.logs.map((l) => '<div class="' + esc(l.level) + '">[' + esc(l.at) + '] ' + esc(l.tag) + ' · ' + esc(l.msg) + '</div>').join('') : '(暂无)';
2332
+ const withTag = state.logTab === 'run';
2333
+ const ico = (lvl) => lvl === 'error' ? '✗' : lvl === 'warn' ? '⚠' : lvl === 'info' ? '·' : '';
2334
+ el.innerHTML = logs.length
2335
+ ? logs.map((l) => '<div class="' + esc(l.level) + '">[' + esc(l.at) + '] <span class="lvl-ico">' + ico(l.level) + '</span>' + (withTag ? esc(l.tag) + ' · ' : '') + esc(l.msg) + '</div>').join('')
2336
+ : (state.logLevel === 'all' ? '(暂无)' : '(暂无警告或错误)');
2089
2337
  if (atBottom) el.scrollTop = el.scrollHeight;
2090
2338
  } catch (e) {}
2091
2339
  }
@@ -2381,6 +2629,57 @@ function okDialog() { const v = $('dlg-field').style.display === 'none' ? true :
2381
2629
  $('dlg-ok').addEventListener('click', okDialog);
2382
2630
  $('dlg-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') okDialog(); });
2383
2631
 
2632
+ // ── 重启服务(桌面客户端或 CLI) ──
2633
+ async function restartCreditDaddy() {
2634
+ const mask = document.createElement('div');
2635
+ mask.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:99999;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff;font-size:15px;gap:12px;backdrop-filter:blur(4px);user-select:none;';
2636
+ mask.innerHTML = '<div style="font-size:28px">⏳</div><div style="font-weight:600">正在重启 CreditDaddy,请稍候…</div><div style="font-size:12px;color:rgba(255,255,255,.6)">服务恢复后将自动刷新页面</div>';
2637
+ document.body.appendChild(mask);
2638
+
2639
+ try {
2640
+ if (window.creditdaddy && typeof window.creditdaddy.restartApp === 'function') {
2641
+ await window.creditdaddy.restartApp();
2642
+ } else {
2643
+ await api('/api/system/restart', { method: 'POST' });
2644
+ }
2645
+ } catch {}
2646
+
2647
+ let attempts = 0;
2648
+ const poll = setInterval(async () => {
2649
+ attempts++;
2650
+ try {
2651
+ const res = await fetch('/api/status', { cache: 'no-store' });
2652
+ if (res.ok) {
2653
+ clearInterval(poll);
2654
+ location.reload();
2655
+ }
2656
+ } catch {}
2657
+ if (attempts > 30) {
2658
+ clearInterval(poll);
2659
+ mask.innerHTML = '<div>重启超时或端口已改变</div><button class="btn primary" onclick="location.reload()" style="margin-top:10px">手动刷新</button>';
2660
+ }
2661
+ }, 1000);
2662
+ }
2663
+
2664
+ async function maybePromptRestartForLan(oldLan, newLan) {
2665
+ if (oldLan !== newLan) {
2666
+ const ok = await uiConfirm('局域网开放绑定改动需重启服务生效,是否立即重启 CreditDaddy?', '配置已保存', '立即重启');
2667
+ if (ok) {
2668
+ await restartCreditDaddy();
2669
+ return true;
2670
+ }
2671
+ }
2672
+ return false;
2673
+ }
2674
+
2675
+ $('btn-restart-app').addEventListener('click', async () => {
2676
+ const ok = await uiConfirm('确定要重启 CreditDaddy 服务吗?正在进行的任务可能会中断。', '重启服务', '重启');
2677
+ if (ok) {
2678
+ closeModal('m-settings');
2679
+ await restartCreditDaddy();
2680
+ }
2681
+ });
2682
+
2384
2683
  // ── 检查更新(桌面版):主进程推状态(update:state),面板只管渲染 ──
2385
2684
  const updBridge = window.creditdaddy && window.creditdaddy.update ? window.creditdaddy.update : null;
2386
2685
  let updState = null;
@@ -2486,6 +2785,7 @@ function syncZgwLanFields() {
2486
2785
  $('zgw-on').addEventListener('change', syncZgwLanFields);
2487
2786
  $('zgw-ok').addEventListener('click', async () => {
2488
2787
  const allow = $('zgw-allow').value.split('\n').map((x) => x.trim()).filter(Boolean);
2788
+ const oldLan = state.zGatewayLan;
2489
2789
  try {
2490
2790
  const r = await api('/api/zcode-gateway', { method: 'PUT', json: { enabled: $('zgw-on').checked, lan: $('zgw-lan').checked, allow } });
2491
2791
  state.zGateway = r.enabled === true;
@@ -2493,11 +2793,58 @@ $('zgw-ok').addEventListener('click', async () => {
2493
2793
  state.zGatewayAllow = r.allow || [];
2494
2794
  renderStatus(); render();
2495
2795
  closeModal('m-zgw');
2796
+ if (await maybePromptRestartForLan(oldLan, state.zGatewayLan)) return;
2496
2797
  toast('体验包接口:' + (r.enabled ? '已开启' : '已关闭')
2497
2798
  + (r.enabled && r.lan ? ',局域网开放' + (r.allow && r.allow.length ? ',白名单 ' + r.allow.join('、') : ',白名单为空——白名单外需虚拟 key') + ';绑定改动重启后生效' : ''));
2498
2799
  } catch (err) { toast(err.message, false); }
2499
2800
  });
2500
2801
 
2802
+ // MiniMax 本地接口:开关 + 局域网开放 + 免密 IP 白名单一次保存
2803
+ function syncMgwLanFields() {
2804
+ const off = !$('mgw-on').checked;
2805
+ $('mgw-lan-fields').querySelectorAll('input, textarea').forEach((el) => { el.disabled = off; });
2806
+ $('mgw-lan-fields').style.opacity = off ? '.45' : '';
2807
+ }
2808
+ $('mgw-on').addEventListener('change', syncMgwLanFields);
2809
+ $('mgw-ok').addEventListener('click', async () => {
2810
+ const allow = $('mgw-allow').value.split('\n').map((x) => x.trim()).filter(Boolean);
2811
+ const oldLan = state.mGatewayLan;
2812
+ try {
2813
+ const r = await api('/api/minimax-gateway', { method: 'PUT', json: { enabled: $('mgw-on').checked, lan: $('mgw-lan').checked, allow } });
2814
+ state.mGateway = r.enabled === true;
2815
+ state.mGatewayLan = r.lan === true;
2816
+ state.mGatewayAllow = r.allow || [];
2817
+ renderStatus(); render();
2818
+ closeModal('m-mgw');
2819
+ if (await maybePromptRestartForLan(oldLan, state.mGatewayLan)) return;
2820
+ toast('MiniMax 接口:' + (r.enabled ? '已开启' : '已关闭')
2821
+ + (r.enabled && r.lan ? ',局域网开放' + (r.allow && r.allow.length ? ',白名单 ' + r.allow.join('、') : '') + ';绑定改动重启后生效' : ''));
2822
+ } catch (err) { toast(err.message, false); }
2823
+ });
2824
+
2825
+ // Trae 本地接口:开关 + 局域网开放 + 免密 IP 白名单一次保存
2826
+ function syncTgwLanFields() {
2827
+ const off = !$('tgw-on').checked;
2828
+ $('tgw-lan-fields').querySelectorAll('input, textarea').forEach((el) => { el.disabled = off; });
2829
+ $('tgw-lan-fields').style.opacity = off ? '.45' : '';
2830
+ }
2831
+ $('tgw-on').addEventListener('change', syncTgwLanFields);
2832
+ $('tgw-ok').addEventListener('click', async () => {
2833
+ const allow = $('tgw-allow').value.split('\n').map((x) => x.trim()).filter(Boolean);
2834
+ const oldLan = state.tGatewayLan;
2835
+ try {
2836
+ const r = await api('/api/trae-gateway', { method: 'PUT', json: { enabled: $('tgw-on').checked, lan: $('tgw-lan').checked, allow } });
2837
+ state.tGateway = r.enabled === true;
2838
+ state.tGatewayLan = r.lan === true;
2839
+ state.tGatewayAllow = r.allow || [];
2840
+ renderStatus(); render();
2841
+ closeModal('m-tgw');
2842
+ if (await maybePromptRestartForLan(oldLan, state.tGatewayLan)) return;
2843
+ toast('Trae 接口:' + (r.enabled ? '已开启' : '已关闭')
2844
+ + (r.enabled && r.lan ? ',局域网开放' + (r.allow && r.allow.length ? ',白名单 ' + r.allow.join('、') : '') + ';绑定改动重启后生效' : ''));
2845
+ } catch (err) { toast(err.message, false); }
2846
+ });
2847
+
2501
2848
  // ZCode 网络出口:顺序 + 代理地址保存
2502
2849
  $('znet-ok').addEventListener('click', async () => {
2503
2850
  try {
@@ -2529,10 +2876,11 @@ const LOCAL_HINT = {
2529
2876
  mirasim: '读取本机 mirasim 客户端当前登录的账号(本地解密 ~/.mirasim/setting.json),token 不出本机。',
2530
2877
  catpaw: '读取本机妙手(美团 CatPaw)客户端当前登录的账号(本地解密 %APPDATA%\\catpaw-moon\\catx-credential.json),token 不出本机。',
2531
2878
  trae: '读取本机 Trae 客户端当前登录的账号(本地解密 <客户端数据目录>\\User\\globalStorage\\storage.json 的 tc 信封),token 不出本机。',
2879
+ minimax: '读取本机 MiniMax Code 客户端当前登录的账号(~/.minimax/auth/.../auth.json),token 不出本机。',
2532
2880
  };
2533
2881
  function openAdd(product) {
2534
2882
  state.addProduct = product || '';
2535
- // Trae 凭据只能从本机客户端 storage.json 解密:不支持浏览器登录 / 粘贴 Token
2883
+ // 各产品支持的添加方式:MiniMax 三种都支持(浏览器登录 = 官方设备码授权,独立凭据链不与本机客户端互斥)
2536
2884
  const tabShown = { local: true, device: product !== 'mirasim' && product !== 'catpaw' && product !== 'trae', token: product !== 'zcode' && product !== 'mirasim' && product !== 'catpaw' && product !== 'trae' };
2537
2885
  document.querySelectorAll('#m-add .tab').forEach((t) => { t.style.display = tabShown[t.dataset.tab] ? '' : 'none'; });
2538
2886
  $('local-hint').textContent = LOCAL_HINT[state.addProduct];
@@ -2562,7 +2910,7 @@ function openAdd(product) {
2562
2910
  async function localScan() {
2563
2911
  const box = $('scan-result');
2564
2912
  $('scan-status').textContent = '扫描中(最多约 10 秒)…';
2565
- box.innerHTML = '<div class="hint">正在读取本机客户端凭据(Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae)…</div>';
2913
+ box.innerHTML = '<div class="hint">正在读取本机客户端凭据(Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae / MiniMax)…</div>';
2566
2914
  try {
2567
2915
  const r = await api('/api/local/scan', { method: 'POST' });
2568
2916
  $('scan-status').textContent = '';
@@ -2583,9 +2931,9 @@ async function localScan() {
2583
2931
  html += '<div class="hint" style="color:var(--err)">读取失败:' + esc(e.file) + ':' + esc(e.error) + '</div>';
2584
2932
  }
2585
2933
  if (!cands.length) {
2586
- const where = state.addProduct ? prodLabel(state.addProduct) : 'Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae';
2587
- const alt = state.addProduct === 'zcode' || state.addProduct === 'mirasim' || state.addProduct === 'catpaw' ? '' : ',或改用「浏览器登录」「粘贴 Token」';
2588
- html += '<div class="hint">未发现已登录的 ' + where + ' 账号。请先在客户端登录' + alt + '。<br>已检查:Qoder / Qoder CN 客户端数据目录、WorkBuddy 凭据目录、ZCode 数据目录、mirasim 数据目录、妙手凭据目录、Trae 数据目录'
2934
+ const where = state.addProduct ? prodLabel(state.addProduct) : 'Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae / MiniMax';
2935
+ const alt = state.addProduct === 'zcode' || state.addProduct === 'mirasim' || state.addProduct === 'catpaw' || state.addProduct === 'trae' ? '' : ',或改用「浏览器登录」「粘贴 Token」';
2936
+ html += '<div class="hint">未发现已登录的 ' + where + ' 账号。请先在客户端登录' + alt + '。<br>已检查:Qoder / Qoder CN 客户端数据目录、WorkBuddy 凭据目录、ZCode 数据目录、mirasim 数据目录、妙手凭据目录、Trae 数据目录、MiniMax 数据目录'
2589
2937
  + (r.scannedDirs && r.scannedDirs.length ? '、' + r.scannedDirs.map(esc).join('、') : '') + '</div>';
2590
2938
  }
2591
2939
  box.innerHTML = html;
@@ -2675,6 +3023,53 @@ async function addToken() {
2675
3023
  b.disabled = false;
2676
3024
  }
2677
3025
 
3026
+ // Qoder 卡片徽章:网页会话(Cookie)缺失或超 7 天时点名提醒——
3027
+ // 10Router 的「套餐内 Credits」逐资源包明细依赖它(同 qoderWebSessionNote 口径)。
3028
+ // 正常状态不出徽章,只在需要「浏览器登录」补 cookie 时出橙色警示,避免噪音。
3029
+ function qoderWebBadge(a) {
3030
+ if (!String(a.provider || '').startsWith('qoder')) return '';
3031
+ const at = a.qoderWebCapturedAt;
3032
+ if (at && Date.now() - new Date(at).getTime() <= 7 * 86400000) return '';
3033
+ const stale = Boolean(at);
3034
+ const tip = stale
3035
+ ? '网页会话已超过 7 天(' + fmtTime(at).slice(0, 10) + ' 抓取),可能已过期:导出到 10Router 后「套餐内 Credits」可能缺失,用「浏览器登录」重新登录一次 Qoder 即可补上'
3036
+ : '缺少 Qoder 网页会话(Cookie):导出到 10Router 后「套餐内 Credits」可能缺失,用「浏览器登录」登录一次 Qoder 即可自动抓取';
3037
+ return '<span class="badge web web-warn" title="' + esc(tip) + '">' + (stale ? '网页会话过期' : '缺网页会话') + '</span>';
3038
+ }
3039
+
3040
+ // 导出弹窗里的网页会话提示(10Router issue #44):
3041
+ // 10Router 的「套餐内 Credits」逐资源包明细依赖随账号导出的 Qoder 网页会话
3042
+ // (openapi 聚合值在部分账号上会归零)。会话缺失或明显过期 → 导出前点名提醒,
3043
+ // 否则用户在 10Router 端只会看到套餐积分凭空消失。
3044
+ function qoderWebSessionNote() {
3045
+ const sel = $('ex-provider').value;
3046
+ const list = state.accounts.filter((a) => {
3047
+ if (!String(a.provider || '').startsWith('qoder')) return false;
3048
+ if (!sel || sel === 'p:qoder') return true;
3049
+ return a.provider === sel;
3050
+ });
3051
+ if (!list.length) return '';
3052
+ const missing = [], stale = [];
3053
+ for (const a of list) {
3054
+ const at = a.qoderWebCapturedAt;
3055
+ if (!at) missing.push(a);
3056
+ else if (Date.now() - new Date(at).getTime() > 7 * 86400000) stale.push(a);
3057
+ }
3058
+ if (!missing.length && !stale.length) return '';
3059
+ const names = (arr) => arr.map((x) => x.name || x.email || x.uid || x.id).join('、');
3060
+ const bits = [];
3061
+ if (missing.length) bits.push('没有网页会话:' + names(missing));
3062
+ if (stale.length) bits.push('会话已超过 7 天:' + names(stale));
3063
+ return '10router 的「套餐内 Credits」逐资源包明细依赖 Qoder 网页会话(' + bits.join(';')
3064
+ + ')。这些账号过去后套餐积分可能显示缺失——在「浏览器登录」重新登录一次 Qoder 再导出即可补上。';
3065
+ }
3066
+ function refreshExportNote() {
3067
+ const msg = qoderWebSessionNote();
3068
+ const el = $('ex-session-note');
3069
+ el.textContent = msg;
3070
+ el.style.display = msg ? '' : 'none';
3071
+ }
3072
+
2678
3073
  // 导出
2679
3074
  async function doExport() {
2680
3075
  const password = $('ex-password').value;
@@ -3018,13 +3413,15 @@ $('btn-checkin-all').onclick = () => checkinAll($('btn-checkin-all'));
3018
3413
  $('btn-checkin').onclick = () => checkinAll($('btn-checkin'), state.view);
3019
3414
  $('btn-add-dash').onclick = () => openAdd('');
3020
3415
  $('btn-add').onclick = () => openAdd(state.view);
3021
- $('btn-export-dash').onclick = () => { $('ex-provider').value = ''; openModal('m-export'); };
3416
+ $('btn-export-dash').onclick = () => { $('ex-provider').value = ''; refreshExportNote(); openModal('m-export'); };
3022
3417
  $('btn-export').onclick = () => {
3023
3418
  const v = state.view;
3024
3419
  // ZCode 是单一 provider,直接按 provider 选;其余产品页按产品线(p:);仪表盘 / 10Router 导出全部
3025
- $('ex-provider').value = v === 'zcode' ? 'zcode' : (v === 'qoder' || v === 'workbuddy' || v === 'mirasim' || v === 'catpaw' || v === 'trae') ? 'p:' + v : '';
3420
+ $('ex-provider').value = v === 'zcode' ? 'zcode' : (v === 'qoder' || v === 'workbuddy' || v === 'mirasim' || v === 'catpaw' || v === 'trae' || v === 'minimax') ? 'p:' + v : '';
3421
+ refreshExportNote();
3026
3422
  openModal('m-export');
3027
3423
  };
3424
+ $('ex-provider').addEventListener('change', refreshExportNote);
3028
3425
  $('btn-do-export').onclick = doExport;
3029
3426
  $('btn-pass-ok').onclick = () => submitImport($('pass-input').value);
3030
3427
  $('pass-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') submitImport($('pass-input').value); });
@@ -3050,7 +3447,12 @@ function applyView() {
3050
3447
  $('view-prod').classList.toggle('active', state.view !== 'dash' && state.view !== 'tenrouter');
3051
3448
  $('view-10r').classList.toggle('active', state.view === 'tenrouter');
3052
3449
  if (state.view === 'tenrouter') loadZGateway();
3450
+ if (state.view === 'minimax') loadMGateway();
3451
+ if (state.view === 'trae') loadTGateway();
3053
3452
  if (state.view === 'tenrouter' && tr.config && tr.config.configured && !tr.quotas && !tr.loading) loadTrQuotas();
3453
+ followLogTab();
3454
+ applyLogTabs();
3455
+ loadCurrentLogs();
3054
3456
  render();
3055
3457
  renderStatus();
3056
3458
  }
@@ -3074,15 +3476,34 @@ $('region').addEventListener('click', (e) => {
3074
3476
  renderStatus();
3075
3477
  });
3076
3478
  $('f-provider').addEventListener('change', () => {
3077
- $('f-token').placeholder = $('f-provider').value.startsWith('workbuddy')
3479
+ $('f-token').placeholder = $('f-provider').value === 'minimax'
3480
+ ? 'MiniMax OAuth Bearer Token(mmoat_…)'
3481
+ : $('f-provider').value.startsWith('workbuddy')
3078
3482
  ? 'WorkBuddy / CodeBuddy 登录 token(eyJ…,可在 CodeBuddyExtension 的 auth/*.info 中找到)'
3079
3483
  : 'dt-… / jt-… 或 PAT(pt-…,自动兑换)';
3080
3484
  });
3081
3485
  $('btn-logs').onclick = () => {
3082
3486
  const c = $('log-card').classList.toggle('collapsed');
3083
3487
  $('btn-logs').textContent = c ? '展开' : '收起';
3084
- if (!c) { loadLogs(); $('logs').scrollTop = $('logs').scrollHeight; }
3488
+ if (!c) { loadCurrentLogs(); $('logs').scrollTop = $('logs').scrollHeight; }
3085
3489
  };
3490
+ $('log-tabs').addEventListener('click', (e) => {
3491
+ const b = e.target.closest('button[data-log-tab]');
3492
+ if (!b) return;
3493
+ state.logTab = b.dataset.logTab;
3494
+ logTabView = state.view; // 手动切换后,本视图内不再被 10s 轮询重置
3495
+ applyLogTabs();
3496
+ loadCurrentLogs();
3497
+ $('logs').scrollTop = $('logs').scrollHeight;
3498
+ });
3499
+ $('log-filter').addEventListener('click', (e) => {
3500
+ const b = e.target.closest('button[data-log-level]');
3501
+ if (!b) return;
3502
+ state.logLevel = b.dataset.logLevel;
3503
+ applyLogTabs();
3504
+ loadCurrentLogs();
3505
+ $('logs').scrollTop = $('logs').scrollHeight;
3506
+ });
3086
3507
 
3087
3508
  // ── 仪表盘底部两卡:默认收起,点标题展开,展开状态存本地 ──
3088
3509
  const DASH_FOLD_KEY = 'qd_dash_fold';
@@ -3115,6 +3536,8 @@ applyView();
3115
3536
  loadSettings().then(renderCompMenu);
3116
3537
  loadZNet();
3117
3538
  loadZGateway();
3539
+ loadMGateway();
3540
+ loadTGateway();
3118
3541
  loadTrConfig().then(() => loadTrQuotas());
3119
3542
  setInterval(() => loadTrQuotas(), 5 * 60 * 1000);
3120
3543
  // 账号余额(ZCode 剩余 token 等)自动刷新:可见时每 2 分钟一轮;从托盘/后台切回时若超过 1 分钟没刷过立即补一轮
@@ -3124,7 +3547,7 @@ applyView();
3124
3547
  });
3125
3548
  loadLogs();
3126
3549
  setInterval(loadLogs, 10000);
3127
- setInterval(async () => { await loadStatus(); await loadZNet(); await loadZGateway(); render(); }, 30000);
3550
+ setInterval(async () => { await loadStatus(); await loadZNet(); await loadZGateway(); await loadMGateway(); await loadTGateway(); render(); }, 30000);
3128
3551
  }
3129
3552
  // 先直接请求(本机模式无需密码);需要密码时 api() 会弹框并重试
3130
3553
  api('/api/status').then(start).catch((e) => toast(e.code === 'PANEL_KEY'