creditdaddy 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +306 -289
- package/bin/creditdaddy.js +8 -4
- package/package.json +29 -28
- package/src/accounts.js +161 -161
- package/src/authDevice.js +205 -205
- package/src/catpawClient.js +103 -103
- package/src/catpawLocal.js +229 -229
- package/src/checkin.js +357 -340
- package/src/constants.js +97 -95
- package/src/daemon.js +130 -14
- package/src/localDetect.js +147 -147
- package/src/mirasimClient.js +189 -189
- package/src/panel.html +465 -142
- package/src/providers.js +190 -181
- package/src/qoderApp.js +93 -7
- package/src/qoderClient.js +237 -237
- package/src/qoderUmid.js +198 -198
- package/src/store.js +238 -238
- package/src/tenrouter.js +18 -8
- package/src/tenrouterAccounts.js +163 -163
- package/src/traeClient.js +258 -0
- package/src/traeLocal.js +337 -0
- package/src/transfer.js +160 -160
- package/src/usageSync.js +505 -505
- package/src/workbuddyLocal.js +133 -133
- package/src/zcodeAuth.js +131 -131
- package/src/zcodeAutoClaim.js +97 -97
- package/src/zcodeClient.js +33 -2
- package/src/zcodeGateway.js +26 -30
- package/src/zcodeLocal.js +334 -319
- package/src/zcrypto.js +160 -160
package/src/panel.html
CHANGED
|
@@ -48,6 +48,16 @@
|
|
|
48
48
|
.comp-menu-title { font-size: 11px; color: var(--text-3); margin: 10px 0 4px; padding-top: 8px; border-top: 1px dashed var(--border-subtle); }
|
|
49
49
|
.comp-menu-title:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
|
|
50
50
|
.comp-menu .check { margin-right: 0; font-size: 12.5px; color: var(--text); }
|
|
51
|
+
/* 产品页 notice 的功能分区行 */
|
|
52
|
+
.z-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
|
53
|
+
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-3); opacity: .45; flex: none; }
|
|
54
|
+
.dot.on { background: var(--ok); opacity: 1; box-shadow: 0 0 5px var(--ok); }
|
|
55
|
+
.dot.pulse { animation: dot-pulse 2.2s ease-out infinite; }
|
|
56
|
+
@keyframes dot-pulse { 0% { box-shadow: 0 0 0 0 rgba(46, 229, 157, .45); } 70% { box-shadow: 0 0 0 5px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }
|
|
57
|
+
.z-row + .z-row { margin-top: 7px; padding-top: 7px; border-top: 1px dashed var(--border-subtle); }
|
|
58
|
+
.z-label { font-size: 11px; color: var(--text-3); flex: none; width: 68px; }
|
|
59
|
+
.z-text { flex: 1 1 320px; min-width: 0; }
|
|
60
|
+
/* 10Router 状态条分两行 */
|
|
51
61
|
.sec-status { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border); color: var(--text-3); }
|
|
52
62
|
.sec-status.on { color: var(--ok); border-color: rgba(var(--accent-rgb), .45); }
|
|
53
63
|
.comp-row { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
|
|
@@ -95,9 +105,9 @@
|
|
|
95
105
|
.notice { margin: 12px 16px 0; padding: 9px 12px; border-radius: 10px; background: var(--warn-soft); color: var(--warn); font-size: 12px; line-height: 1.6; }
|
|
96
106
|
|
|
97
107
|
/* cards */
|
|
98
|
-
.list { display: grid; grid-template-columns: repeat(auto-fill, minmax(440px, 1fr)); gap:
|
|
99
|
-
.card { position: relative; padding: 11px 12px 12px; border-radius: 12px; transition: background .12s; min-width: 0; }
|
|
100
|
-
.card:hover { background: var(--hover); }
|
|
108
|
+
.list { display: grid; grid-template-columns: repeat(auto-fill, minmax(440px, 1fr)); gap: 10px; padding: 10px; }
|
|
109
|
+
.card { position: relative; padding: 11px 12px 12px; border-radius: 12px; background: var(--surface-2); border: 1px solid var(--border-subtle); transition: background .12s, border-color .12s; min-width: 0; }
|
|
110
|
+
.card:hover { background: var(--hover); border-color: var(--border-strong); }
|
|
101
111
|
.row1 { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 28px; margin-bottom: 7px; }
|
|
102
112
|
.name-group { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; min-width: 0; }
|
|
103
113
|
.name { font-size: 14px; font-weight: 600; cursor: text; border-radius: 5px; padding: 0 2px; margin: 0 -2px; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -114,6 +124,8 @@
|
|
|
114
124
|
html[data-theme="dark"] .badge.prod.zcode { color: #93c5fd; }
|
|
115
125
|
.badge.prod.catpaw { color: #c2410c; background: rgba(249,115,22,.14); }
|
|
116
126
|
html[data-theme="dark"] .badge.prod.catpaw { color: #fdba74; }
|
|
127
|
+
.badge.prod.trae { color: #047857; background: rgba(16,185,129,.14); }
|
|
128
|
+
html[data-theme="dark"] .badge.prod.trae { color: #6ee7b7; }
|
|
117
129
|
.seg-ctl { display: inline-flex; gap: 2px; padding: 2px; border-radius: 9px; background: var(--hover); }
|
|
118
130
|
.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; }
|
|
119
131
|
.seg-ctl button.active { background: var(--surface); color: var(--text); box-shadow: 0 1px 3px rgba(0,0,0,.08); }
|
|
@@ -160,6 +172,12 @@
|
|
|
160
172
|
/* section card */
|
|
161
173
|
.pcard { margin-top: 14px; background: var(--surface); border: 1px solid var(--border-subtle); border-radius: 14px; padding: 12px 14px; }
|
|
162
174
|
.pcard-title { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 13px; font-weight: 700; }
|
|
175
|
+
.fold-head { cursor: pointer; user-select: none; }
|
|
176
|
+
.fold-side { display: inline-flex; align-items: center; gap: 6px; color: var(--text-3); }
|
|
177
|
+
.fold-ico { display: inline-flex; }
|
|
178
|
+
.fold-ico svg { width: 14px; height: 14px; }
|
|
179
|
+
.pcard .fold-body { display: none; margin-top: 4px; }
|
|
180
|
+
.pcard.open .fold-body { display: block; }
|
|
163
181
|
.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; }
|
|
164
182
|
.logs .info { color: var(--text-2); } .logs .warn { color: var(--warn); } .logs .error { color: var(--err); } .logs .debug { color: var(--text-3); }
|
|
165
183
|
.collapsed .logs { display: none; }
|
|
@@ -187,8 +205,9 @@
|
|
|
187
205
|
.hint { font-size: 12px; color: var(--text-3); line-height: 1.7; }
|
|
188
206
|
.hint a { color: #4f86ff; word-break: break-all; }
|
|
189
207
|
.box { border: 1px solid var(--border-subtle); border-radius: 10px; padding: 10px 12px; margin-top: 10px; background: var(--surface-2); }
|
|
190
|
-
.cand { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding:
|
|
191
|
-
.cand
|
|
208
|
+
.cand { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; border: 1px solid var(--border-subtle); border-radius: 10px; background: var(--surface); }
|
|
209
|
+
.cand + .cand { margin-top: 8px; }
|
|
210
|
+
.cand .row { flex: none; flex-wrap: nowrap; gap: 6px; }
|
|
192
211
|
.cand-main { min-width: 0; }
|
|
193
212
|
.cand-name { font-weight: 600; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
|
194
213
|
.cand-sub { font-size: 11px; color: var(--text-3); margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -206,8 +225,15 @@
|
|
|
206
225
|
.view { display: none; } .view.active { display: block; }
|
|
207
226
|
|
|
208
227
|
/* 仪表盘 */
|
|
209
|
-
.dash-
|
|
210
|
-
.dash-title { font-size:
|
|
228
|
+
.dash-panel { margin-bottom: 12px; }
|
|
229
|
+
.dash-title { font-size: 13px; font-weight: 700; }
|
|
230
|
+
.dash-panel .toolbar { padding: 8px 16px; }
|
|
231
|
+
.dash-panel .kpis { display: flex; gap: 0; margin: 0; padding: 0 10px 8px; }
|
|
232
|
+
.dash-panel .kpi { background: none; border: none; border-radius: 0; padding: 6px 14px; flex: 1 1 0; min-width: 0; }
|
|
233
|
+
.dash-panel .kpi-value { font-size: 22px; margin-top: 2px; }
|
|
234
|
+
.dash-panel .kpi-sub { margin-top: 2px; }
|
|
235
|
+
.dash-panel .kpi + .kpi { border-left: 1px dashed var(--border-subtle); }
|
|
236
|
+
@media (max-width: 900px) { .dash-panel .kpis { flex-wrap: wrap; } .dash-panel .kpi { flex: 1 1 40%; } .dash-panel .kpi + .kpi { border-left: none; } }
|
|
211
237
|
.kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; margin-bottom: 10px; }
|
|
212
238
|
.kpi { background: var(--surface); border: 1px solid var(--border-subtle); border-radius: 14px; padding: 14px 16px; min-width: 0; }
|
|
213
239
|
.kpi-label { color: var(--text-3); font-size: 12px; font-weight: 500; }
|
|
@@ -221,7 +247,7 @@
|
|
|
221
247
|
.prod-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
222
248
|
.prod-name { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 700; }
|
|
223
249
|
.dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
224
|
-
.dot.qoder { background: #7c3aed; } .dot.workbuddy { background: #14b8a6; } .dot.zcode { background: #2563eb; } .dot.mirasim { background: #64748b; } .dot.catpaw { background: #f97316; }
|
|
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; }
|
|
225
251
|
.prod-go { color: var(--text-3); font-size: 12px; white-space: nowrap; display: inline-flex; align-items: center; gap: 2px; }
|
|
226
252
|
.prod-go svg { width: 12px; height: 12px; transition: transform .15s; }
|
|
227
253
|
.prodcard:hover .prod-go { color: var(--text); }
|
|
@@ -253,7 +279,7 @@
|
|
|
253
279
|
.tr-vrow { }
|
|
254
280
|
.tr-vhead { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 13px; }
|
|
255
281
|
.tr-vhead .dim { font-size: 12px; }
|
|
256
|
-
.tr-tabs { display: flex; gap: 6px; flex-wrap: wrap; margin:
|
|
282
|
+
.tr-tabs { display: flex; gap: 6px; flex-wrap: wrap; margin: 14px 12px 4px; }
|
|
257
283
|
.tr-vcards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; align-items: stretch; }
|
|
258
284
|
@media (max-width: 1180px) { .tr-vcards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
259
285
|
@media (max-width: 820px) { .tr-vcards { grid-template-columns: 1fr; } }
|
|
@@ -297,7 +323,6 @@
|
|
|
297
323
|
.tr-packs-head { font-size: 11px; color: var(--text-3); margin-bottom: 2px; }
|
|
298
324
|
.tr-packs-head b { color: var(--text-2); }
|
|
299
325
|
.tr-more { margin-top: 7px; border: none; background: none; color: #4f86ff; font-size: 12px; cursor: pointer; padding: 0; }
|
|
300
|
-
.tr-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 16px; border-bottom: 1px solid var(--border-subtle); font-size: 12px; color: var(--text-2); }
|
|
301
326
|
.chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 999px; font-size: 11px; background: var(--tertiary); color: var(--text-3); }
|
|
302
327
|
.chip.on { background: rgba(var(--accent-rgb), .14); color: var(--text); }
|
|
303
328
|
.setup { padding: 22px; max-width: 620px; }
|
|
@@ -337,25 +362,28 @@
|
|
|
337
362
|
<button data-view="zcode">ZCode<span class="cnt" id="cnt-zcode">0</span></button>
|
|
338
363
|
<button data-view="mirasim">mirasim<span class="cnt" id="cnt-mirasim">0</span></button>
|
|
339
364
|
<button data-view="catpaw">妙手<span class="cnt" id="cnt-catpaw">0</span></button>
|
|
365
|
+
<button data-view="trae">Trae<span class="cnt" id="cnt-trae">0</span></button>
|
|
340
366
|
<button data-view="tenrouter">10Router<span class="cnt" id="cnt-tenrouter">–</span></button>
|
|
341
367
|
</nav>
|
|
342
368
|
|
|
343
369
|
<!-- 仪表盘 -->
|
|
344
370
|
<section class="view active" id="view-dash">
|
|
345
|
-
<div class="dash-
|
|
346
|
-
<div class="
|
|
347
|
-
|
|
348
|
-
<
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
371
|
+
<div class="panel dash-panel">
|
|
372
|
+
<div class="toolbar">
|
|
373
|
+
<div class="summary"><span class="dash-title">总览</span></div>
|
|
374
|
+
<div class="actions">
|
|
375
|
+
<button class="btn primary" id="btn-checkin-all"></button>
|
|
376
|
+
<button class="btn" id="btn-add-dash"></button>
|
|
377
|
+
<button class="btn" id="btn-import-dash"></button>
|
|
378
|
+
<button class="btn" id="btn-export-dash"></button>
|
|
379
|
+
</div>
|
|
352
380
|
</div>
|
|
381
|
+
<div class="kpis" id="kpis"></div>
|
|
353
382
|
</div>
|
|
354
|
-
<div class="kpis" id="kpis"></div>
|
|
355
383
|
<div class="prods" id="prods"></div>
|
|
356
384
|
<div class="dash-cols">
|
|
357
|
-
<section class="pcard"><div class="pcard-title"><span>最近记录</span><span class="hint" id="last-tick"></span></div><div id="recent"></div></section>
|
|
358
|
-
<section class="pcard"><div class="pcard-title"><span>需要处理</span><span class="hint" id="att-count"></span></div><div id="attention"></div></section>
|
|
385
|
+
<section class="pcard" data-fold="recent"><div class="pcard-title fold-head"><span>最近记录</span><span class="fold-side"><span class="hint" id="last-tick"></span><span class="fold-ico"></span></span></div><div class="fold-body"><div id="recent"></div></div></section>
|
|
386
|
+
<section class="pcard" data-fold="attention"><div class="pcard-title fold-head"><span>需要处理</span><span class="fold-side"><span class="hint" id="att-count"></span><span class="fold-ico"></span></span></div><div class="fold-body"><div id="attention"></div></div></section>
|
|
359
387
|
</div>
|
|
360
388
|
</section>
|
|
361
389
|
|
|
@@ -378,6 +406,15 @@
|
|
|
378
406
|
</div>
|
|
379
407
|
<div class="actions">
|
|
380
408
|
<button class="btn primary" id="btn-checkin"></button>
|
|
409
|
+
<span id="z-actions" style="display:none">
|
|
410
|
+
<button class="btn" id="zt-ac"></button>
|
|
411
|
+
<button class="btn" id="zt-net"></button>
|
|
412
|
+
<button class="btn" id="zt-gw"></button>
|
|
413
|
+
<button class="btn" id="zt-open" style="display:none"></button>
|
|
414
|
+
</span>
|
|
415
|
+
<span id="t-actions" style="display:none">
|
|
416
|
+
<button class="btn" id="tt-open" style="display:none"></button>
|
|
417
|
+
</span>
|
|
381
418
|
<button class="btn" id="btn-add"></button>
|
|
382
419
|
<button class="btn" id="btn-import"></button>
|
|
383
420
|
<button class="btn" id="btn-export"></button>
|
|
@@ -453,7 +490,7 @@
|
|
|
453
490
|
<div class="modal-title"><span>导出账号</span><button class="close" data-close>✕</button></div>
|
|
454
491
|
<div class="field">
|
|
455
492
|
<label>范围</label>
|
|
456
|
-
<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></select>
|
|
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>
|
|
457
494
|
</div>
|
|
458
495
|
<div class="field">
|
|
459
496
|
<label>加密口令(必填)</label>
|
|
@@ -479,26 +516,34 @@
|
|
|
479
516
|
<div class="modal">
|
|
480
517
|
<div class="modal-title"><span>面板设置</span><button class="close" data-close>✕</button></div>
|
|
481
518
|
<div class="comp-menu" id="comp-menu"></div>
|
|
519
|
+
<div class="field" id="upd-entry" style="display:none;margin-top:12px">
|
|
520
|
+
<label>版本与更新</label>
|
|
521
|
+
<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
|
+
</div>
|
|
482
523
|
<div class="modal-actions"><button class="btn" data-close>关闭</button></div>
|
|
483
524
|
</div>
|
|
484
525
|
</div>
|
|
485
526
|
|
|
527
|
+
<!-- 检查更新(桌面版):状态由主进程 update:state 推送 -->
|
|
528
|
+
<div class="mask" id="m-update">
|
|
529
|
+
<div class="modal sm">
|
|
530
|
+
<div class="modal-title"><span>检查更新</span><button class="close" data-close>✕</button></div>
|
|
531
|
+
<div id="upd-body"></div>
|
|
532
|
+
<div class="modal-actions" id="upd-actions"></div>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
|
|
486
536
|
<!-- 10Router 设置 -->
|
|
487
537
|
<div class="mask" id="m-10r">
|
|
488
538
|
<div class="modal">
|
|
489
539
|
<div class="modal-title"><span>10Router 连接设置</span><button class="close" data-close>✕</button></div>
|
|
490
540
|
<div class="hint">填 10Router 的访问地址和仪表盘里创建的<b>虚拟 key(sk-…)</b>。key 只保存在 CreditDaddy 守护进程的数据目录(0600),面板里只显示脱敏值。</div>
|
|
491
|
-
<div class="field" style="margin-top:12px"><label>10Router 地址</label><input class="input mono" id="tr-endpoint" placeholder="http://127.0.0.1:20128
|
|
541
|
+
<div class="field" style="margin-top:12px"><label>10Router 地址</label><input class="input mono" id="tr-endpoint" placeholder="http://127.0.0.1:20128(站点根地址,不要带 /v1)"></div>
|
|
492
542
|
<div class="field"><label>虚拟 key</label><input class="input mono" id="tr-key" placeholder="sk-…" autocomplete="off"></div>
|
|
493
543
|
<div class="field"><label>10Router 面板密码(可选)</label><input class="input mono" id="tr-admin-pw" type="password" placeholder="配置后「同步账号」走 OAuth 通道(带 refreshToken 自动续期);留空保持不变" autocomplete="new-password"></div>
|
|
494
544
|
<div class="field"><label>账号同步</label>
|
|
495
545
|
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap"><button class="btn" id="tr-sync-accounts">同步账号到 10R</button><span class="hint">把本机 Qoder / WorkBuddy 账号推为 10Router 上游连接;zcode 待反代就绪后支持</span></div>
|
|
496
546
|
</div>
|
|
497
|
-
<div class="field"><label>用量同步(同 10router-sync 插件)</label>
|
|
498
|
-
<div><label class="check"><input type="checkbox" id="tr-auto"> 每小时自动同步本机用量到 10Router</label></div>
|
|
499
|
-
<div id="tr-sources" style="margin-top:6px"></div>
|
|
500
|
-
<div class="hint" style="margin-top:4px">ZCode 只同步官方渠道;经 10Router 中转的 mirasim 调用会跳过,避免重复计数。「10Router 本机」只把实例原生产生的行标记为网关同步。10Router 按行签名去重,重复同步不会产生重复数据。</div>
|
|
501
|
-
</div>
|
|
502
547
|
<div class="field"><label>显示</label>
|
|
503
548
|
<div><label class="check"><input type="checkbox" id="tr-hide-empty"> 「全部」视图隐藏没有额度 / 余额为 0 的供应商汇总卡</label></div>
|
|
504
549
|
</div>
|
|
@@ -511,6 +556,18 @@
|
|
|
511
556
|
</div>
|
|
512
557
|
</div>
|
|
513
558
|
|
|
559
|
+
<!-- 10Router 用量同步 -->
|
|
560
|
+
<div class="mask" id="m-trsync">
|
|
561
|
+
<div class="modal sm">
|
|
562
|
+
<div class="modal-title"><span>用量同步(同 10router-sync 插件)</span><button class="close" data-close>✕</button></div>
|
|
563
|
+
<div class="field"><label class="check"><input type="checkbox" id="tr-auto"> 每小时自动同步本机用量到 10Router</label></div>
|
|
564
|
+
<div class="field"><label>同步来源</label><div id="tr-sources"></div></div>
|
|
565
|
+
<div class="hint">ZCode 只同步官方渠道;经 10Router 中转的 mirasim 调用会跳过,避免重复计数。「10Router 本机」只把实例原生产生的行标记为网关同步。10Router 按行签名去重,重复同步不会产生重复数据。</div>
|
|
566
|
+
<div class="hint" id="tr-sync-status" style="margin-top:8px"></div>
|
|
567
|
+
<div class="modal-actions"><button class="btn" style="margin-right:auto" id="tr-sync-now">立即同步一次</button><button class="btn" data-close>取消</button><button class="btn primary" id="tr-sync-save">保存</button></div>
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
|
|
514
571
|
<!-- ZCode 活动领取 -->
|
|
515
572
|
<div class="mask" id="m-claim">
|
|
516
573
|
<div class="modal">
|
|
@@ -530,6 +587,59 @@
|
|
|
530
587
|
</div>
|
|
531
588
|
</div>
|
|
532
589
|
|
|
590
|
+
<!-- ZCode 自动领取计划 -->
|
|
591
|
+
<div class="mask" id="m-zac">
|
|
592
|
+
<div class="modal sm">
|
|
593
|
+
<div class="modal-title"><span>ZCode 自动领取计划</span><button class="close" data-close>✕</button></div>
|
|
594
|
+
<div class="field">
|
|
595
|
+
<label>轮询周期(分钟,1–720)</label>
|
|
596
|
+
<input class="input" id="zac-interval" type="number" min="1" max="720" step="1">
|
|
597
|
+
</div>
|
|
598
|
+
<div class="field">
|
|
599
|
+
<label>单次运行时长(分钟,0 = 一直运行直到手动关闭)</label>
|
|
600
|
+
<input class="input" id="zac-window" type="number" min="0" max="1440" step="5">
|
|
601
|
+
</div>
|
|
602
|
+
<div class="hint">开启后按此周期检查活动资格并自动领取(首次检查在开启 2 分钟后开始)。活动包由服务端在每日 0 点左右刷新放出——若运行时段没有覆盖 0 点,会错过当次领取,建议把运行时长设为 0,或让时段跨过 0 点。需要验证码时无法自动通过,卡片上会提示手动领取。</div>
|
|
603
|
+
<div class="modal-actions"><button class="btn" id="zac-off" style="display:none">停用</button><button class="btn" data-close>取消</button><button class="btn primary" id="zac-ok">保存并启用</button></div>
|
|
604
|
+
</div>
|
|
605
|
+
</div>
|
|
606
|
+
|
|
607
|
+
<!-- ZCode 网络出口 -->
|
|
608
|
+
<div class="mask" id="m-znet">
|
|
609
|
+
<div class="modal sm">
|
|
610
|
+
<div class="modal-title"><span>ZCode 网络出口</span><button class="close" data-close>✕</button></div>
|
|
611
|
+
<div class="field">
|
|
612
|
+
<label>上游接口访问顺序(失败 / 被拦时自动回退另一条)</label>
|
|
613
|
+
<label class="check"><input type="radio" name="znet-order" id="znet-direct"> 直连优先(代理兜底)</label>
|
|
614
|
+
<label class="check"><input type="radio" name="znet-order" id="znet-proxy"> 代理优先(直连兜底)</label>
|
|
615
|
+
</div>
|
|
616
|
+
<div class="field">
|
|
617
|
+
<label>出口代理(http://host:port,支持口令 http://user:pass@host:port)</label>
|
|
618
|
+
<input class="input" id="znet-url" placeholder="http://192.168.31.101:7890">
|
|
619
|
+
</div>
|
|
620
|
+
<div class="hint">留空 = 清除已保存的代理,退回 HTTPS_PROXY 环境变量;取消 = 不改动。当前代理只显示脱敏后的地址。</div>
|
|
621
|
+
<div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="znet-ok">保存</button></div>
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
|
|
625
|
+
<!-- ZCode 体验包接口 -->
|
|
626
|
+
<div class="mask" id="m-zgw">
|
|
627
|
+
<div class="modal sm">
|
|
628
|
+
<div class="modal-title"><span>ZCode 体验包接口</span><button class="close" data-close>✕</button></div>
|
|
629
|
+
<div class="hint" id="zgw-url" style="margin-bottom:10px"></div>
|
|
630
|
+
<div class="field">
|
|
631
|
+
<label class="check"><input type="checkbox" id="zgw-on"> 启用体验包接口(/gateway/v1/messages)</label>
|
|
632
|
+
</div>
|
|
633
|
+
<div class="field" id="zgw-lan-fields">
|
|
634
|
+
<label class="check"><input type="checkbox" id="zgw-lan"> 对局域网开放(重启后绑定 0.0.0.0)</label>
|
|
635
|
+
<label>IP 白名单(每行一个,支持 192.168.31.* 通配)</label>
|
|
636
|
+
<textarea class="input" id="zgw-allow" rows="4" placeholder="192.168.31.101"></textarea>
|
|
637
|
+
</div>
|
|
638
|
+
<div class="hint">开启后,领到的体验包额度变成本机的一个补全接口——10Router 建一个 anthropic-compatible 节点指向本机 /gateway 即可调用(需要桌面版,隐藏窗口过验证码)。白名单内的机器<b>免密直连</b>:10Router 的 zcode-free 本就是免授权供应商,把 10Router 服务地址的 IP 加白即可;白名单外的请求一律拒绝。本机回环(127.0.0.1)始终放行。</div>
|
|
639
|
+
<div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="zgw-ok">保存</button></div>
|
|
640
|
+
</div>
|
|
641
|
+
</div>
|
|
642
|
+
|
|
533
643
|
<input type="file" id="file-input" accept=".json,application/json" style="display:none">
|
|
534
644
|
<div id="toast"></div>
|
|
535
645
|
|
|
@@ -560,7 +670,7 @@ const I = {
|
|
|
560
670
|
|
|
561
671
|
const $ = (id) => document.getElementById(id);
|
|
562
672
|
const state = { accounts: [], quotas: {}, status: null, busy: {}, view: 'dash', region: '', addProduct: '' };
|
|
563
|
-
const VIEWS = ['dash', 'qoder', 'workbuddy', 'zcode', 'mirasim', 'catpaw', 'tenrouter'];
|
|
673
|
+
const VIEWS = ['dash', 'qoder', 'workbuddy', 'zcode', 'mirasim', 'catpaw', 'trae', 'tenrouter'];
|
|
564
674
|
// 10Router 集成:config 来自 /api/tenrouter,quotas 来自 /api/tenrouter/quotas
|
|
565
675
|
const tr = { config: null, quotas: null, error: null, loading: false, syncing: false };
|
|
566
676
|
const PRODUCTS = [
|
|
@@ -569,6 +679,7 @@ const PRODUCTS = [
|
|
|
569
679
|
{ id: 'zcode', label: 'ZCode' },
|
|
570
680
|
{ id: 'mirasim', label: 'mirasim' },
|
|
571
681
|
{ id: 'catpaw', label: '妙手' },
|
|
682
|
+
{ id: 'trae', label: 'Trae' },
|
|
572
683
|
];
|
|
573
684
|
const prodLabel = (id) => (PRODUCTS.find((x) => x.id === id) || { label: id }).label;
|
|
574
685
|
|
|
@@ -579,6 +690,7 @@ const COMPONENTS = [
|
|
|
579
690
|
{ id: 'zcode', label: 'ZCode' },
|
|
580
691
|
{ id: 'mirasim', label: 'mirasim' },
|
|
581
692
|
{ id: 'catpaw', label: '妙手' },
|
|
693
|
+
{ id: 'trae', label: 'Trae' },
|
|
582
694
|
{ id: 'tenrouter', label: '10Router' },
|
|
583
695
|
];
|
|
584
696
|
const HIDDEN_COMP_KEY = 'qd_hidden_components';
|
|
@@ -699,10 +811,11 @@ const PROV = {
|
|
|
699
811
|
zcode: { prod: 'zcode', prodLabel: 'ZCode', region: '', cls: '' },
|
|
700
812
|
mirasim: { prod: 'mirasim', prodLabel: 'mirasim', region: '', cls: '' },
|
|
701
813
|
catpaw: { prod: 'catpaw', prodLabel: '妙手', region: '', cls: '' },
|
|
814
|
+
trae: { prod: 'trae', prodLabel: 'Trae', region: '', cls: '' },
|
|
702
815
|
};
|
|
703
816
|
// 支持每日签到的产品(ZCode / mirasim / 妙手 额度靠套餐或滚动窗口发放,不做签到)
|
|
704
|
-
const CHECKIN_PRODUCTS = new Set(['qoder', 'workbuddy']);
|
|
705
|
-
const CLIENT_LABEL = { workbuddy: 'WorkBuddy', zcode: 'ZCode', mirasim: 'mirasim', catpaw: '妙手' };
|
|
817
|
+
const CHECKIN_PRODUCTS = new Set(['qoder', 'workbuddy', 'trae']);
|
|
818
|
+
const CLIENT_LABEL = { qoder: 'Qoder', workbuddy: 'WorkBuddy', zcode: 'ZCode', mirasim: 'mirasim', catpaw: '妙手', trae: 'Trae' };
|
|
706
819
|
// 额度单位为 Credits/时长而非「积分」口径的产品(不计入剩余积分合计)
|
|
707
820
|
const CREDIT_VIEWS = new Set(['zcode', 'mirasim', 'catpaw']);
|
|
708
821
|
const provOf = (p) => PROV[p] || { prod: 'qoder', prodLabel: p, region: '', cls: '' };
|
|
@@ -813,17 +926,10 @@ function applyTheme(t) {
|
|
|
813
926
|
$('btn-theme').innerHTML = t === 'dark' ? I.sun : I.moon;
|
|
814
927
|
}
|
|
815
928
|
function initTheme() {
|
|
816
|
-
const mq = window.matchMedia ? matchMedia('(prefers-color-scheme: dark)') : null;
|
|
817
929
|
let t = null;
|
|
818
930
|
try { t = localStorage.getItem('qd_theme'); } catch (e) {}
|
|
819
|
-
if (!t) t =
|
|
931
|
+
if (!t) t = window.matchMedia && matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
820
932
|
applyTheme(t);
|
|
821
|
-
// 未手动锁定主题(localStorage 无记录)时,实时跟随系统日夜切换
|
|
822
|
-
if (mq && mq.addEventListener) mq.addEventListener('change', (e) => {
|
|
823
|
-
let saved = null;
|
|
824
|
-
try { saved = localStorage.getItem('qd_theme'); } catch (err) {}
|
|
825
|
-
if (!saved) applyTheme(e.matches ? 'dark' : 'light');
|
|
826
|
-
});
|
|
827
933
|
$('btn-theme').onclick = () => {
|
|
828
934
|
const n = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
|
|
829
935
|
applyTheme(n);
|
|
@@ -1004,7 +1110,7 @@ function cardHtml(a) {
|
|
|
1004
1110
|
+ '</div><div class="ops">'
|
|
1005
1111
|
+ (a.product === 'zcode' ? '<button class="icon-btn" data-act="claim" title="活动领取(billing/preview → claim,可能需过一次验证码)">' + I.gift + '</button>' : '')
|
|
1006
1112
|
+ (canCheckin ? '<button class="icon-btn' + (state.busy[a.id] ? ' busy' : '') + '" data-act="checkin" title="' + (a.provider === 'workbuddy-intl' ? '发送活跃请求' : '立即领取') + '">' + (state.busy[a.id] ? I.refresh : I.play) + '</button>' : '')
|
|
1007
|
-
+ (
|
|
1113
|
+
+ (a.canSwitch && !isCurrent && st.cls !== 'fail' ? '<button class="icon-btn" data-act="switch" title="切换 ' + (CLIENT_LABEL[a.product] || prodLabel(a.product)) + ' 客户端到此账号">' + I.swap + '</button>' : '')
|
|
1008
1114
|
+ '<button class="icon-btn" data-act="quota" title="刷新积分">' + I.refresh + '</button>'
|
|
1009
1115
|
+ '<button class="icon-btn del" data-act="del" title="删除(点两次确认)">' + I.trash + '</button>'
|
|
1010
1116
|
+ '</div></div>'
|
|
@@ -1021,7 +1127,7 @@ function cardHtml(a) {
|
|
|
1021
1127
|
|
|
1022
1128
|
const isDone = (a) => Boolean(state.status && state.status.todayDone && state.status.todayDone[a.id] === state.status.today);
|
|
1023
1129
|
const ofProduct = (prod) => state.accounts.filter((a) => a.product === prod);
|
|
1024
|
-
const currentUidOf = (prod) => state.status && (prod === 'workbuddy' ? state.status.workbuddyCurrentUid : prod === 'zcode' ? state.status.zcodeCurrentUid : prod === 'catpaw' ? state.status.catpawCurrentUid : null);
|
|
1130
|
+
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);
|
|
1025
1131
|
/** 接口使用中判定:体验包接口最近一次补全用的账号(lastAccount 记的是 name || uid || id) */
|
|
1026
1132
|
function isGatewayCurrent(a) {
|
|
1027
1133
|
const last = state.zGatewayStats && state.zGatewayStats.lastAccount;
|
|
@@ -1033,8 +1139,13 @@ function isClientCurrent(a) {
|
|
|
1033
1139
|
const s = state.status;
|
|
1034
1140
|
if (!s) return false;
|
|
1035
1141
|
if (a.product === 'workbuddy') return Boolean(a.uid && s.workbuddyCurrentUid && s.workbuddyCurrentUid === a.uid);
|
|
1142
|
+
if (a.product === 'qoder') {
|
|
1143
|
+
const cur = s.qoderCurrent || {};
|
|
1144
|
+
return Boolean(a.uid && cur[a.provider] && String(cur[a.provider]) === String(a.uid));
|
|
1145
|
+
}
|
|
1036
1146
|
if (a.product === 'mirasim') return Boolean(a.uid && s.mirasimCurrentUid && s.mirasimCurrentUid === a.uid);
|
|
1037
1147
|
if (a.product === 'catpaw') return Boolean(a.uid && s.catpawCurrentUid && s.catpawCurrentUid === a.uid);
|
|
1148
|
+
if (a.product === 'trae') return Boolean(a.uid && s.traeCurrentUid && s.traeCurrentUid === a.uid);
|
|
1038
1149
|
if (a.product === 'zcode') {
|
|
1039
1150
|
const id = s.zcodeCurrentIdentity;
|
|
1040
1151
|
return Boolean(id && ((a.uid && id.uid != null && String(id.uid) === String(a.uid)) || (a.email && id.email && id.email === a.email)));
|
|
@@ -1056,6 +1167,8 @@ function creditSum(list) {
|
|
|
1056
1167
|
}
|
|
1057
1168
|
|
|
1058
1169
|
function render() {
|
|
1170
|
+
$('z-actions').style.display = 'none'; // 兜底:切到仪表盘 / 10Router 时 renderProd 不跑,先藏起来
|
|
1171
|
+
$('t-actions').style.display = 'none';
|
|
1059
1172
|
for (const p of PRODUCTS) $('cnt-' + p.id).textContent = ofProduct(p.id).length;
|
|
1060
1173
|
$('cnt-tenrouter').textContent = tr.quotas ? tr.quotas.connections.length : tr.config && tr.config.configured ? '…' : '–';
|
|
1061
1174
|
if (state.view === 'dash') renderDash();
|
|
@@ -1069,6 +1182,7 @@ const EMPTY_TIP = {
|
|
|
1069
1182
|
zcode: '还没有 ZCode 账号。<br>点「添加账号」可读取本机 ZCode 客户端已登录的账号,或用浏览器登录(BigModel / Z.ai),多个账号可一键切换。',
|
|
1070
1183
|
mirasim: '还没有 mirasim 账号。<br>点「添加账号」可读取本机 mirasim 客户端当前登录的账号(本地解密,token 不出本机)。',
|
|
1071
1184
|
catpaw: '还没有妙手账号。<br>点「添加账号」可读取本机妙手(美团 CatPaw)客户端当前登录的账号(本地解密凭据,token 不出本机)。',
|
|
1185
|
+
trae: '还没有 Trae 账号。<br>点「添加账号 → 本机导入」可读取本机 Trae 客户端当前登录的账号(本地解密 storage.json 凭据,token 不出本机)。多个账号需先在 Trae 里分别登录并逐个导入。',
|
|
1072
1186
|
};
|
|
1073
1187
|
|
|
1074
1188
|
function renderProd() {
|
|
@@ -1097,7 +1211,7 @@ function renderProd() {
|
|
|
1097
1211
|
$('s-credits').textContent = CREDIT_VIEWS.has(prod)
|
|
1098
1212
|
? acc.filter((a) => { const q = state.quotas[a.id]; return q && !q.error && !q.empty; }).length + ' / ' + acc.length
|
|
1099
1213
|
: c.loading ? '…' : (c.known ? fmtNum(c.sum) : '-');
|
|
1100
|
-
const hasRegions = !CREDIT_VIEWS.has(prod);
|
|
1214
|
+
const hasRegions = !CREDIT_VIEWS.has(prod) && prod !== 'trae'; // Trae 不分国际 / 国内版
|
|
1101
1215
|
$('region').style.display = hasRegions ? '' : 'none';
|
|
1102
1216
|
$('region-div').style.display = hasRegions ? '' : 'none';
|
|
1103
1217
|
document.querySelectorAll('#region button').forEach((x) => x.classList.toggle('active', x.dataset.r === state.region));
|
|
@@ -1106,6 +1220,36 @@ function renderProd() {
|
|
|
1106
1220
|
const hasIntl = acc.some((a) => a.provider === 'workbuddy-intl');
|
|
1107
1221
|
$('btn-checkin').innerHTML = I.play + (prod === 'workbuddy' && hasIntl ? '领取 / 活跃全部' : '领取全部 ' + prodLabel(prod));
|
|
1108
1222
|
}
|
|
1223
|
+
// 顶栏产品专属入口:ZCode 设置组、Trae 打开客户端,带状态灯(运行中绿点呼吸)
|
|
1224
|
+
const dot = (on, pulse) => '<span class="dot' + (on ? ' on' : '') + (on && pulse ? ' pulse' : '') + '"></span>';
|
|
1225
|
+
$('z-actions').style.display = prod === 'zcode' ? 'contents' : 'none';
|
|
1226
|
+
if (prod === 'zcode') {
|
|
1227
|
+
const acOn = state.zAutoClaim === true;
|
|
1228
|
+
const gwOn = state.zGateway === true;
|
|
1229
|
+
$('zt-ac').innerHTML = dot(acOn) + '自动领取';
|
|
1230
|
+
$('zt-ac').title = acOn ? '自动领取运行中——点我调整轮询周期 / 运行时长' : '自动领取已关闭——点我设置轮询周期与运行时长并开启';
|
|
1231
|
+
$('zt-net').innerHTML = '出口:<b style="font-weight:700;color:var(--text)">' + (state.zProxyFirst === true ? '代理优先' : '直连优先') + '</b>';
|
|
1232
|
+
$('zt-net').title = '网络出口:直连 / 代理顺序与代理地址';
|
|
1233
|
+
$('zt-gw').innerHTML = dot(gwOn) + '接口' + (gwOn && state.zGatewayLan === true ? '·局域网' : '');
|
|
1234
|
+
$('zt-gw').title = '体验包接口:开关、局域网开放与 IP 白名单';
|
|
1235
|
+
const zc = (state.status && state.status.zcodeClient) || {};
|
|
1236
|
+
const canOpen = zc.installed && window.creditdaddy && typeof window.creditdaddy.openZcodeClient === 'function';
|
|
1237
|
+
$('zt-open').style.display = canOpen ? '' : 'none';
|
|
1238
|
+
if (canOpen) {
|
|
1239
|
+
$('zt-open').innerHTML = dot(zc.running === true, true) + '打开客户端';
|
|
1240
|
+
$('zt-open').title = zc.running ? 'ZCode 客户端运行中,点我唤到前台' : 'ZCode 客户端未运行,点我启动';
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
$('t-actions').style.display = prod === 'trae' ? 'contents' : 'none';
|
|
1244
|
+
if (prod === 'trae') {
|
|
1245
|
+
const tc = (state.status && state.status.traeClient) || {};
|
|
1246
|
+
const canOpen = tc.installed && window.creditdaddy && typeof window.creditdaddy.openTraeClient === 'function';
|
|
1247
|
+
$('tt-open').style.display = canOpen ? '' : 'none';
|
|
1248
|
+
if (canOpen) {
|
|
1249
|
+
$('tt-open').innerHTML = dot(tc.running === true, true) + '打开客户端';
|
|
1250
|
+
$('tt-open').title = tc.running ? 'Trae 客户端运行中,点我唤到前台' : 'Trae 客户端未运行,点我启动';
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1109
1253
|
}
|
|
1110
1254
|
|
|
1111
1255
|
function kpi(label, value, sub, labelTitle) {
|
|
@@ -1192,6 +1336,7 @@ function renderDash() {
|
|
|
1192
1336
|
: zid && (zid.email || zid.uid || zid.username) ? '未导入的账号(' + esc(zid.email || zid.username || zid.uid) + ')'
|
|
1193
1337
|
: p.id === 'mirasim' && s.mirasimCurrentUid ? '未导入的账号'
|
|
1194
1338
|
: p.id === 'catpaw' && s.catpawClient && s.catpawClient.signedIn && !list.some((a) => isClientCurrent(a)) ? '未导入的账号'
|
|
1339
|
+
: p.id === 'trae' && s.traeClient && s.traeClient.signedIn && !list.some((a) => isClientCurrent(a)) ? '未导入的账号'
|
|
1195
1340
|
: (p.id === 'workbuddy' && s.workbuddyCurrentUid ? '未导入的账号' : '—');
|
|
1196
1341
|
rows += '<span class="lbl">客户端当前</span><span class="val">' + live + '</span>';
|
|
1197
1342
|
}
|
|
@@ -1264,8 +1409,11 @@ function renderStatus() {
|
|
|
1264
1409
|
// 产品相关提示只在对应标签页出现
|
|
1265
1410
|
const hasIntl = state.view === 'qoder' && state.region !== 'cn' && state.accounts.some((a) => a.provider === 'qoder');
|
|
1266
1411
|
const n = $('notice');
|
|
1267
|
-
n.classList.toggle('info', state.view === 'zcode' || state.view === 'mirasim' || state.view === 'catpaw' || (hasIntl && Boolean(s.riskIdentity)));
|
|
1268
|
-
if (state.view === '
|
|
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') {
|
|
1414
|
+
n.style.display = '';
|
|
1415
|
+
n.innerHTML = '<div class="z-row"><span class="z-label">每日签到</span><span class="z-text">每日签到领约 150 通用积分(7 天有效,多资源包自动归并)。凭据由本机 Trae 客户端解密导入,支持多账号快照一键切换。</span></div>';
|
|
1416
|
+
} else if (state.view === 'catpaw') {
|
|
1269
1417
|
n.style.display = '';
|
|
1270
1418
|
n.innerHTML = '妙手(美团 CatPaw)按套餐发放 Credits(体验版 / 专业版…),没有每日签到。凭据由妙手客户端登录管理:读取额度用本机解密出的登录 token 直连桌面网关,token 失效时到妙手客户端重新登录后再「本机导入」即可。'
|
|
1271
1419
|
+ '多账号切换:写入本机凭据后重启妙手客户端即生效(切换会自动保存当前登录,不丢号)。'
|
|
@@ -1277,23 +1425,12 @@ function renderStatus() {
|
|
|
1277
1425
|
+ mirasimClientChip(s);
|
|
1278
1426
|
} else if (state.view === 'zcode') {
|
|
1279
1427
|
n.style.display = '';
|
|
1280
|
-
const pf = state.zProxyFirst === true;
|
|
1281
|
-
const pu = state.zProxyUrl;
|
|
1282
1428
|
const ac = state.zAutoClaim === true;
|
|
1283
|
-
const
|
|
1284
|
-
const
|
|
1285
|
-
n.innerHTML = '
|
|
1286
|
-
+ '
|
|
1287
|
-
+ '
|
|
1288
|
-
+ ' <button class="btn" id="btn-zproxy" style="margin-left:4px" title="设置 ZCode 出口用的 http 代理(留空则清除;不设置时使用 HTTPS_PROXY 环境变量)">代理:' + esc(pu || (state.zHasEnvProxy ? '环境变量' : '未设置')) + '</button>'
|
|
1289
|
-
+ ' <button class="btn" id="btn-zgw" style="margin-left:4px" title="开启后,领到的体验包额度会变成本机的一个补全接口(/gateway/v1/messages);10Router 建一个 anthropic-compatible 节点指向 http://127.0.0.1:' + location.port + '/gateway 即可调用。需要桌面版(隐藏窗口过验证码)">'
|
|
1290
|
-
+ (state.zGateway === true ? '体验包接口:开' : '体验包接口:关') + '</button>'
|
|
1291
|
-
+ ' <button class="btn" id="btn-zgwlan" style="margin-left:4px" title="允许局域网其他设备的 10Router 直连本机的体验包接口(daemon 重启后生效)。远程调用需要用「10Router 连接设置」里保存的虚拟 key 鉴权,因此需先配置 10Router 连接并设置面板访问密码">'
|
|
1292
|
-
+ (state.zGatewayLan === true ? '局域网:开' : '局域网:关') + '</button>'
|
|
1293
|
-
+ (state.zGateway === true && state.zGatewayStats && state.zGatewayStats.lastAccount ? ' <span class="chip on" style="margin-left:6px" title="体验包接口有调用记录,10Router 正在调用本机接口(最近用的账号见账号卡片上的「接口使用中」标记)">10Router 已接入</span>' : '')
|
|
1294
|
-
+ (state.zGateway === true ? ' <span class="ver" style="margin-left:6px">POST http://127.0.0.1:' + location.port + '/gateway/v1/messages</span>' : '')
|
|
1295
|
-
+ (state.zGateway === true && state.zGatewayStats && state.zGatewayStats.lastCallAt ? ' <span class="ver" style="margin-left:6px">最近调用 ' + esc(String(state.zGatewayStats.lastCallAt).slice(11, 19)) + ' · 共 ' + state.zGatewayStats.calls + ' 次</span>' : (state.zGateway === true ? ' <span class="ver" style="margin-left:6px">尚未有调用</span>' : ''))
|
|
1296
|
-
+ zcodeClientChip(s);
|
|
1429
|
+
const ziv = state.zClaimIntervalMin || 2;
|
|
1430
|
+
const zwin = state.zClaimWindowMin === 0 ? 0 : (state.zClaimWindowMin || 60);
|
|
1431
|
+
n.innerHTML = '<div class="z-row"><span class="z-label">活动领取</span><span class="z-text">活动包积分,每日 0 点左右刷新(无每日签到)。自动领取'
|
|
1432
|
+
+ (ac ? '每 ' + ziv + ' 分钟检查资格' + (zwin > 0 ? ',本次运行 ' + zwin + ' 分钟' : ',持续运行') : '未开启(右上角设置轮询计划)')
|
|
1433
|
+
+ ';礼物图标 = 手动临时领。</span></div>';
|
|
1297
1434
|
} else if (hasIntl && !s.riskIdentity && umidOffer(s)) {
|
|
1298
1435
|
n.style.display = '';
|
|
1299
1436
|
const busy = s.umid.installing || umidBusy;
|
|
@@ -1312,15 +1449,6 @@ function renderStatus() {
|
|
|
1312
1449
|
}
|
|
1313
1450
|
}
|
|
1314
1451
|
|
|
1315
|
-
// ZCode 客户端状态芯片 + 「打开客户端」按钮(仅桌面版:面板里有 Electron 桥才显示按钮)
|
|
1316
|
-
function zcodeClientChip(s) {
|
|
1317
|
-
const c = s.zcodeClient || {};
|
|
1318
|
-
if (!c.installed) return ' <span class="dim">客户端:未安装</span>';
|
|
1319
|
-
const canOpen = window.creditdaddy && typeof window.creditdaddy.openZcodeClient === 'function';
|
|
1320
|
-
return ' <span class="dim">客户端:' + (c.running ? '运行中' : '未运行') + '</span>'
|
|
1321
|
-
+ (canOpen ? ' <button class="btn" id="btn-zopen" style="margin-left:4px" title="打开本机 ZCode 客户端">打开客户端</button>' : '');
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
1452
|
// mirasim 客户端状态芯片 + 「打开客户端」按钮
|
|
1325
1453
|
function mirasimClientChip(s) {
|
|
1326
1454
|
const c = s.mirasimClient || {};
|
|
@@ -1330,6 +1458,7 @@ function mirasimClientChip(s) {
|
|
|
1330
1458
|
+ (canOpen ? ' <button class="btn" id="btn-miraopen" style="margin-left:4px" title="打开本机 Mirasim 客户端">打开客户端</button>' : '');
|
|
1331
1459
|
}
|
|
1332
1460
|
|
|
1461
|
+
|
|
1333
1462
|
// 妙手客户端状态芯片 + 「打开客户端」按钮
|
|
1334
1463
|
function catpawClientChip(s) {
|
|
1335
1464
|
const c = s.catpawClient || {};
|
|
@@ -1352,8 +1481,9 @@ async function installUmid() {
|
|
|
1352
1481
|
await loadStatus(); render(); loadLogs();
|
|
1353
1482
|
}
|
|
1354
1483
|
$('notice').addEventListener('click', (e) => { if (e.target.closest('#btn-umid')) installUmid(); });
|
|
1355
|
-
|
|
1356
|
-
|
|
1484
|
+
// 覆盖 #notice(各产品横幅)与 #z-actions(ZCode 工具栏按钮组)的点击
|
|
1485
|
+
document.addEventListener('click', async (e) => {
|
|
1486
|
+
if (e.target.closest('#zt-open')) {
|
|
1357
1487
|
try {
|
|
1358
1488
|
const r = await window.creditdaddy.openZcodeClient();
|
|
1359
1489
|
if (r && r.ok) toast('已启动 ZCode 客户端');
|
|
@@ -1377,51 +1507,44 @@ $('notice').addEventListener('click', async (e) => {
|
|
|
1377
1507
|
} catch (err) { toast(String(err && err.message || err), false); }
|
|
1378
1508
|
return;
|
|
1379
1509
|
}
|
|
1380
|
-
if (e.target.closest('#
|
|
1510
|
+
if (e.target.closest('#tt-open')) {
|
|
1381
1511
|
try {
|
|
1382
|
-
const r = await
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
} catch (err) { toast(err.message, false); }
|
|
1512
|
+
const r = await window.creditdaddy.openTraeClient();
|
|
1513
|
+
if (r && r.ok) toast('已启动 Trae 客户端');
|
|
1514
|
+
else toast((r && r.error) || '无法打开 Trae 客户端', false);
|
|
1515
|
+
} catch (err) { toast(String(err && err.message || err), false); }
|
|
1387
1516
|
return;
|
|
1388
1517
|
}
|
|
1389
|
-
if (e.target.closest('#
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1518
|
+
if (e.target.closest('#zt-ac')) {
|
|
1519
|
+
// 自动领取计划弹窗:周期 / 运行时长由用户决定
|
|
1520
|
+
$('zac-interval').value = state.zClaimIntervalMin || 2;
|
|
1521
|
+
$('zac-window').value = state.zClaimWindowMin === 0 ? 0 : (state.zClaimWindowMin || 60);
|
|
1522
|
+
$('zac-off').style.display = state.zAutoClaim === true ? '' : 'none';
|
|
1523
|
+
$('zac-ok').textContent = state.zAutoClaim === true ? '保存' : '保存并启用';
|
|
1524
|
+
openModal('m-zac');
|
|
1396
1525
|
return;
|
|
1397
1526
|
}
|
|
1398
|
-
if (e.target.closest('#
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1527
|
+
if (e.target.closest('#zt-gw')) {
|
|
1528
|
+
// 体验包接口弹窗:开关 + 局域网 + 白名单一次设置;白名单默认预填 10Router 服务地址的主机
|
|
1529
|
+
$('zgw-on').checked = state.zGateway === true;
|
|
1530
|
+
$('zgw-lan').checked = state.zGatewayLan === true;
|
|
1531
|
+
const allow = Array.isArray(state.zGatewayAllow) ? state.zGatewayAllow : [];
|
|
1532
|
+
$('zgw-allow').value = (allow.length ? allow : (state.zGatewaySuggest ? [state.zGatewaySuggest] : [])).join('\n');
|
|
1533
|
+
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)'
|
|
1535
|
+
+ (st && st.lastCallAt
|
|
1536
|
+
? '<br>最近调用 ' + esc(String(st.lastCallAt).slice(11, 19)) + ' · 共 ' + st.calls + ' 次' + (st.lastAccount ? ' · 最后用号 ' + esc(st.lastAccount) : '')
|
|
1537
|
+
: '<br>尚未有调用');
|
|
1538
|
+
syncZgwLanFields();
|
|
1539
|
+
openModal('m-zgw');
|
|
1405
1540
|
return;
|
|
1406
1541
|
}
|
|
1407
|
-
if (e.target.closest('#
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
} catch (err) { toast(err.message, false); }
|
|
1414
|
-
return;
|
|
1415
|
-
}
|
|
1416
|
-
if (e.target.closest('#btn-zproxy')) {
|
|
1417
|
-
const cur = state.zProxyUrl || '';
|
|
1418
|
-
const v = await uiPrompt('ZCode 出口代理(http://host:port,支持口令 http://user:pass@host:port)\n留空 = 清除,退回 HTTPS_PROXY 环境变量', cur, { okText: '保存' });
|
|
1419
|
-
if (v === null) return;
|
|
1420
|
-
try {
|
|
1421
|
-
await api('/api/zcode/net', { method: 'PUT', json: { proxyUrl: v.trim() } });
|
|
1422
|
-
await loadZNet(); renderStatus();
|
|
1423
|
-
toast(v.trim() ? '代理已保存' : '代理已清除');
|
|
1424
|
-
} catch (err) { toast(err.message, false); }
|
|
1542
|
+
if (e.target.closest('#zt-net')) {
|
|
1543
|
+
// 出口弹窗:顺序 + 代理地址一起保存;代理只回填到 placeholder(脱敏),留空即清除
|
|
1544
|
+
(state.zProxyFirst === true ? $('znet-proxy') : $('znet-direct')).checked = true;
|
|
1545
|
+
$('znet-url').value = '';
|
|
1546
|
+
$('znet-url').placeholder = state.zProxyUrl || (state.zHasEnvProxy ? '跟随环境变量 HTTPS_PROXY' : 'http://192.168.31.101:7890');
|
|
1547
|
+
openModal('m-znet');
|
|
1425
1548
|
return;
|
|
1426
1549
|
}
|
|
1427
1550
|
});
|
|
@@ -1431,8 +1554,10 @@ async function loadZNet() {
|
|
|
1431
1554
|
state.zProxyFirst = d.proxyFirst === true;
|
|
1432
1555
|
state.zProxyUrl = d.proxyUrlMasked || null;
|
|
1433
1556
|
state.zAutoClaim = d.autoClaim === true;
|
|
1557
|
+
state.zClaimIntervalMin = d.claimIntervalMin;
|
|
1558
|
+
state.zClaimWindowMin = d.claimWindowMin;
|
|
1434
1559
|
state.zHasEnvProxy = d.hasEnvProxy === true;
|
|
1435
|
-
if (state.view === 'zcode') renderStatus();
|
|
1560
|
+
if (state.view === 'zcode') { renderStatus(); render(); }
|
|
1436
1561
|
} catch (e) {}
|
|
1437
1562
|
}
|
|
1438
1563
|
async function loadZGateway() {
|
|
@@ -1440,9 +1565,11 @@ async function loadZGateway() {
|
|
|
1440
1565
|
const d = await api('/api/zcode-gateway');
|
|
1441
1566
|
state.zGateway = d.enabled === true;
|
|
1442
1567
|
state.zGatewayLan = d.lan === true;
|
|
1568
|
+
state.zGatewayAllow = d.allow || [];
|
|
1569
|
+
state.zGatewaySuggest = d.suggestAllow || '';
|
|
1443
1570
|
state.zGatewayHasCaptcha = d.hasCaptcha === true;
|
|
1444
1571
|
state.zGatewayStats = d.stats || null;
|
|
1445
|
-
if (state.view === 'zcode') renderStatus();
|
|
1572
|
+
if (state.view === 'zcode') { renderStatus(); render(); }
|
|
1446
1573
|
} catch (e) {}
|
|
1447
1574
|
}
|
|
1448
1575
|
|
|
@@ -1701,17 +1828,14 @@ function renderTenrouter() {
|
|
|
1701
1828
|
const c = tr.config;
|
|
1702
1829
|
if (!c) { box.innerHTML = '<div class="empty">加载中…</div>'; return; }
|
|
1703
1830
|
const gwLine = state.zGateway === true
|
|
1704
|
-
? '<div class="gw-line"
|
|
1831
|
+
? '<div class="gw-line"><span class="dot on pulse"></span> ZCode 体验包接口运行中'
|
|
1832
|
+
+ (state.zGatewayStats && state.zGatewayStats.lastCallAt ? ' · 最近调用 ' + esc(String(state.zGatewayStats.lastCallAt).slice(11, 19)) + ' · ' + state.zGatewayStats.calls + ' 次 · 最后用号 ' + esc(state.zGatewayStats.lastAccount || '-') : ' · 尚未有调用')
|
|
1833
|
+
+ ' · 模型 <code>zcode-free/glm-5.3-flash</code></div>'
|
|
1705
1834
|
: '';
|
|
1706
1835
|
if (!c.configured) { box.innerHTML = gwLine + trSetupHtml(); return; }
|
|
1707
1836
|
const list = (tr.quotas && tr.quotas.connections) || [];
|
|
1708
1837
|
const low = list.filter((x) => x.isActive && !x.error && quotaLow(x)).length;
|
|
1709
1838
|
const failed = list.filter((x) => x.error).length;
|
|
1710
|
-
const ls = c.lastSync;
|
|
1711
|
-
const srcChips = (c.sources || []).map((x) => {
|
|
1712
|
-
const on = x.found && c.sync.sources.includes(x.id);
|
|
1713
|
-
return '<span class="chip' + (on ? ' on' : '') + '" title="' + (x.found ? (x.supported ? '已检测到' : '需要 Node 22.5+') : '本机未检测到') + '">' + esc(x.label) + (x.found ? '' : ' ·无') + '</span>';
|
|
1714
|
-
}).join('');
|
|
1715
1839
|
let grid;
|
|
1716
1840
|
let tabStrip = '';
|
|
1717
1841
|
const groups = list.length ? trGroups(list) : [];
|
|
@@ -1759,15 +1883,11 @@ function renderTenrouter() {
|
|
|
1759
1883
|
+ '<div class="stat"><span>额度告急</span><strong>' + (tr.quotas ? low : '-') + '</strong></div><div class="divider"></div>'
|
|
1760
1884
|
+ '<div class="stat"><span>查询失败</span><strong>' + (tr.quotas ? failed : '-') + '</strong></div>'
|
|
1761
1885
|
+ '</div><div class="actions">'
|
|
1762
|
-
+ '<button class="btn" data-tr="refresh"' + (tr.loading ? ' disabled' : '') + '>' +
|
|
1763
|
-
+ '<button class="btn" data-tr="sync"' + (tr.syncing || c.syncing ? ' disabled' : '') + '>'
|
|
1886
|
+
+ '<button class="btn" data-tr="refresh"' + (tr.loading ? ' disabled' : '') + '>' + (tr.loading ? '刷新中…' : '刷新额度') + '</button>'
|
|
1887
|
+
+ '<button class="btn" data-tr="sync" title="用量同步:自动开关、同步来源与手动同步(点我打开)"' + (tr.syncing || c.syncing ? ' disabled' : '') + '>'
|
|
1888
|
+
+ '<span class="dot' + (c.sync.enabled ? ' on' : '') + '"></span>' + (tr.syncing || c.syncing ? '同步中…' : '用量同步') + '</button>'
|
|
1764
1889
|
+ '<button class="btn" data-tr="settings">设置</button>'
|
|
1765
1890
|
+ '</div></div>'
|
|
1766
|
-
+ '<div class="tr-bar"><span title="' + esc(c.endpoint) + '">' + esc(c.endpoint) + ' · ' + esc(c.keyMasked) + '</span>'
|
|
1767
|
-
+ '<span>用量同步:' + (c.sync.enabled ? '每小时自动' : '手动') + '</span>' + srcChips
|
|
1768
|
-
+ '<span style="margin-left:auto" title="' + esc(ls ? ls.summary : '') + '">' + (ls ? '上次同步 ' + esc(fmtTime(ls.at).slice(5)) + ' · ' + esc(ls.summary.slice(0, 40)) : '还没有同步过') + '</span>'
|
|
1769
|
-
+ (tr.quotas ? '<span>额度更新于 ' + esc(fmtTime(tr.quotas.generatedAt).slice(11)) + '</span>' : '')
|
|
1770
|
-
+ '</div>'
|
|
1771
1891
|
+ tabStrip
|
|
1772
1892
|
+ '<div class="tr-grid">' + grid + '</div>';
|
|
1773
1893
|
}
|
|
@@ -1817,23 +1937,36 @@ function openTrSettings() {
|
|
|
1817
1937
|
$('tr-key').placeholder = c.keyMasked ? '已保存 ' + c.keyMasked + '(留空保持不变)' : 'sk-…';
|
|
1818
1938
|
$('tr-admin-pw').value = '';
|
|
1819
1939
|
$('tr-admin-pw').placeholder = c.hasAdminPassword ? '已保存(留空保持不变)' : '可选:10Router 面板密码';
|
|
1820
|
-
$('tr-auto').checked = c.configured ? c.sync.enabled : true;
|
|
1821
1940
|
$('tr-hide-empty').checked = state.trHideEmpty === true;
|
|
1822
|
-
$('tr-sources').innerHTML = (c.sources || []).map((x) => '<label class="check"><input type="checkbox" data-src="' + x.id + '"'
|
|
1823
|
-
+ ((c.configured ? c.sync.sources.includes(x.id) : true) ? ' checked' : '') + (x.supported ? '' : ' disabled') + '> ' + esc(x.label)
|
|
1824
|
-
+ '<span class="dim">' + (x.found ? '' : '(本机未检测到)') + (x.supported ? '' : '(需要 Node 22.5+)') + '</span></label>').join('');
|
|
1825
1941
|
$('tr-disconnect').style.display = c.configured ? '' : 'none';
|
|
1826
1942
|
$('tr-test-msg').textContent = '';
|
|
1827
1943
|
openModal('m-10r');
|
|
1828
1944
|
setTimeout(() => $(c.configured ? 'tr-key' : 'tr-endpoint').focus(), 30);
|
|
1829
1945
|
}
|
|
1946
|
+
// 用量同步弹窗:自动开关 + 来源勾选 + 手动同步
|
|
1947
|
+
function openTrSync() {
|
|
1948
|
+
const c = tr.config || { sync: { enabled: true, sources: [] }, sources: [] };
|
|
1949
|
+
$('tr-auto').checked = c.configured ? c.sync.enabled : true;
|
|
1950
|
+
$('tr-sources').innerHTML = (c.sources || []).map((x) => '<label class="check"><input type="checkbox" data-src="' + x.id + '"'
|
|
1951
|
+
+ ((c.configured ? c.sync.sources.includes(x.id) : true) ? ' checked' : '') + (x.supported ? '' : ' disabled') + '> ' + esc(x.label)
|
|
1952
|
+
+ '<span class="dim">' + (x.found ? '' : '(本机未检测到)') + (x.supported ? '' : '(需要 Node 22.5+)') + '</span></label>').join('');
|
|
1953
|
+
const ls = c.lastSync;
|
|
1954
|
+
$('tr-sync-status').textContent = ls ? '上次同步 ' + fmtTime(ls.at).slice(5) + ':' + ls.summary : '还没有同步过';
|
|
1955
|
+
$('tr-sync-now').disabled = tr.syncing || c.syncing === true;
|
|
1956
|
+
openModal('m-trsync');
|
|
1957
|
+
}
|
|
1958
|
+
async function trSyncSave(close) {
|
|
1959
|
+
try {
|
|
1960
|
+
tr.config = await api('/api/tenrouter', { method: 'PUT', json: { syncEnabled: $('tr-auto').checked, sources: [...document.querySelectorAll('#tr-sources input[data-src]:checked')].map((x) => x.dataset.src) } });
|
|
1961
|
+
if (close) { closeModal('m-trsync'); toast('用量同步设置已保存'); render(); }
|
|
1962
|
+
} catch (e) { toast(e.message, false); throw e; }
|
|
1963
|
+
}
|
|
1830
1964
|
function trForm() {
|
|
1831
1965
|
return {
|
|
1832
|
-
|
|
1966
|
+
// 从 LLM 客户端复制的 base URL 常带 /v1 后缀——10Router 只认站点根地址,带 /v1 会让所有 /api/* 调用 404、误报「需要 1.2.1+」
|
|
1967
|
+
endpoint: $('tr-endpoint').value.trim().replace(/\/+$/, '').replace(/\/v1$/, ''),
|
|
1833
1968
|
key: $('tr-key').value.trim(),
|
|
1834
1969
|
adminPassword: $('tr-admin-pw').value, // 留空 = 服务端保持原值
|
|
1835
|
-
syncEnabled: $('tr-auto').checked,
|
|
1836
|
-
sources: [...document.querySelectorAll('#tr-sources input[data-src]:checked')].map((x) => x.dataset.src),
|
|
1837
1970
|
};
|
|
1838
1971
|
}
|
|
1839
1972
|
async function trTest() {
|
|
@@ -1886,7 +2019,7 @@ $('tr-panel').addEventListener('click', (e) => {
|
|
|
1886
2019
|
if (b.dataset.tr === 'toggle') { state.trOpen = Object.assign({}, state.trOpen, { [b.dataset.id]: !(state.trOpen && state.trOpen[b.dataset.id]) }); render(); return; }
|
|
1887
2020
|
if (b.dataset.tr === 'settings') openTrSettings();
|
|
1888
2021
|
else if (b.dataset.tr === 'refresh') loadTrQuotas(true);
|
|
1889
|
-
else if (b.dataset.tr === 'sync')
|
|
2022
|
+
else if (b.dataset.tr === 'sync') openTrSync();
|
|
1890
2023
|
else if (b.dataset.tr === 'vendor') {
|
|
1891
2024
|
const v = b.dataset.v || '';
|
|
1892
2025
|
state.trVendor = v;
|
|
@@ -1895,6 +2028,12 @@ $('tr-panel').addEventListener('click', (e) => {
|
|
|
1895
2028
|
}
|
|
1896
2029
|
});
|
|
1897
2030
|
$('tr-test').onclick = trTest;
|
|
2031
|
+
$('tr-sync-save').onclick = () => trSyncSave(true);
|
|
2032
|
+
$('tr-sync-now').onclick = async () => {
|
|
2033
|
+
try { await trSyncSave(false); } catch (e) { return; }
|
|
2034
|
+
closeModal('m-trsync');
|
|
2035
|
+
trSync();
|
|
2036
|
+
};
|
|
1898
2037
|
$('tr-sync-accounts').onclick = trSyncAccounts;
|
|
1899
2038
|
$('tr-save').onclick = trSave;
|
|
1900
2039
|
$('tr-disconnect').onclick = trDisconnect;
|
|
@@ -1957,7 +2096,9 @@ async function checkinOne(id) {
|
|
|
1957
2096
|
try {
|
|
1958
2097
|
const d = await api('/api/accounts/' + encodeURIComponent(id) + '/checkin', { method: 'POST' });
|
|
1959
2098
|
const r = d.results && d.results[0];
|
|
1960
|
-
|
|
2099
|
+
const accObj = state.accounts.find((x) => x.id === id);
|
|
2100
|
+
const accLabel = (r && r.account) || (accObj && (accObj.name || accObj.id)) || '该账号';
|
|
2101
|
+
toast(r ? (accLabel + ':' + (r.status === 'checked-in' ? '领取成功 +' + r.claimedAmount + ' Credits' : (r.error || r.message || r.status))) : d.summary, !(r && r.status === 'failed'));
|
|
1961
2102
|
} catch (e) { toast(e.message, false); }
|
|
1962
2103
|
delete state.busy[id];
|
|
1963
2104
|
await loadStatus(); await loadAccounts(); loadLogs();
|
|
@@ -1982,21 +2123,25 @@ async function onCardClick(e) {
|
|
|
1982
2123
|
if (act === 'claim') return openClaim(id);
|
|
1983
2124
|
if (act === 'switch') {
|
|
1984
2125
|
const a = state.accounts.find((x) => x.id === id);
|
|
1985
|
-
const client = CLIENT_LABEL[a && a.product] || '客户端';
|
|
2126
|
+
const client = CLIENT_LABEL[a && a.product] || (a && prodLabel(a.product)) || '客户端';
|
|
1986
2127
|
const tip = a && a.product === 'zcode'
|
|
1987
2128
|
? '\n需先退出 ZCode;切换后重新打开 ZCode 即为新账号。当前登录会先保存到 CreditDaddy,不会丢号。'
|
|
1988
2129
|
: a && a.product === 'mirasim'
|
|
1989
2130
|
? '\n需先退出 Mirasim;切换后重新打开即为新账号。当前登录会先保存到 CreditDaddy,不会丢号。'
|
|
1990
2131
|
: a && a.product === 'catpaw'
|
|
1991
2132
|
? '\n需先退出妙手;切换后重新打开妙手即为新账号。当前登录会先保存到 CreditDaddy,不会丢号。'
|
|
1992
|
-
|
|
2133
|
+
: a && a.product === 'trae'
|
|
2134
|
+
? '\n需先退出 Trae;切换后重新打开 Trae 即为新账号。当前登录会先自动快照保存到 CreditDaddy,不会丢号。'
|
|
2135
|
+
: a && a.product === 'qoder'
|
|
2136
|
+
? '\n需先退出 Qoder;切换后重新打开 Qoder 即为新账号。运行中的 Qoder 退出时会把旧登录写回去,可选「关闭并强制切换」代为关进程。'
|
|
2137
|
+
: '\n客户端会自动应用新会话;当前登录账号的最新 token 会先保存到 CreditDaddy。';
|
|
1993
2138
|
if (!(await uiConfirm('把 ' + client + ' 切换到「' + (a && a.name || id) + '」?' + tip, '切换账号', '切换'))) return;
|
|
1994
2139
|
const doSwitch = (force) => api('/api/accounts/' + encodeURIComponent(id) + '/switch', { json: { force } });
|
|
1995
2140
|
try {
|
|
1996
2141
|
let r;
|
|
1997
2142
|
try { r = await doSwitch(false); }
|
|
1998
2143
|
catch (err) {
|
|
1999
|
-
if (err.code !== 'ZCODE_RUNNING' && err.code !== 'MIRASIM_RUNNING' && err.code !== 'CATPAW_RUNNING') throw err;
|
|
2144
|
+
if (err.code !== 'ZCODE_RUNNING' && err.code !== 'MIRASIM_RUNNING' && err.code !== 'CATPAW_RUNNING' && err.code !== 'TRAE_RUNNING' && err.code !== 'QODER_RUNNING') throw err;
|
|
2000
2145
|
if (!(await uiConfirm(err.message + '\n\n仍要强制切换吗?强制切换会先关闭客户端再写入登录,重新打开客户端即为新账号。', client + ' 正在运行', '关闭并强制切换'))) return;
|
|
2001
2146
|
r = await doSwitch(true);
|
|
2002
2147
|
}
|
|
@@ -2029,6 +2174,15 @@ async function onCardClick(e) {
|
|
|
2029
2174
|
} catch (e) {}
|
|
2030
2175
|
}, 800);
|
|
2031
2176
|
}
|
|
2177
|
+
if (a && a.product === 'trae' && r && !r.alreadyActive && window.creditdaddy && typeof window.creditdaddy.openTraeClient === 'function') {
|
|
2178
|
+
setTimeout(async () => {
|
|
2179
|
+
try {
|
|
2180
|
+
const re = await window.creditdaddy.openTraeClient();
|
|
2181
|
+
if (re && re.ok) toast('已重新启动 Trae 客户端');
|
|
2182
|
+
else toast((re && re.error) || '无法打开 Trae 客户端(桌面版才能自动拉起)', false);
|
|
2183
|
+
} catch (e) {}
|
|
2184
|
+
}, 800);
|
|
2185
|
+
}
|
|
2032
2186
|
} catch (err) { toast(err.message, false); }
|
|
2033
2187
|
return;
|
|
2034
2188
|
}
|
|
@@ -2226,6 +2380,135 @@ const uiPrompt = (title, value, opts = {}) => uiDialog({ title, message: opts.me
|
|
|
2226
2380
|
function okDialog() { const v = $('dlg-field').style.display === 'none' ? true : $('dlg-input').value; $('m-dialog').classList.remove('show'); settleDialog(v); }
|
|
2227
2381
|
$('dlg-ok').addEventListener('click', okDialog);
|
|
2228
2382
|
$('dlg-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') okDialog(); });
|
|
2383
|
+
|
|
2384
|
+
// ── 检查更新(桌面版):主进程推状态(update:state),面板只管渲染 ──
|
|
2385
|
+
const updBridge = window.creditdaddy && window.creditdaddy.update ? window.creditdaddy.update : null;
|
|
2386
|
+
let updState = null;
|
|
2387
|
+
function updActBtn(id, text, cls, fn) {
|
|
2388
|
+
return '<button class="btn' + (cls ? ' ' + cls : '') + '" id="' + id + '">' + text + '</button>';
|
|
2389
|
+
}
|
|
2390
|
+
function renderUpdateModal(s) {
|
|
2391
|
+
updState = s;
|
|
2392
|
+
const body = $('upd-body');
|
|
2393
|
+
const acts = $('upd-actions');
|
|
2394
|
+
const cur = '当前 v' + (s.current || '?');
|
|
2395
|
+
const rel = '<button class="btn" id="upd-rel">打开 Releases</button>';
|
|
2396
|
+
const close = '<button class="btn" data-close-uu>稍后</button>';
|
|
2397
|
+
switch (s.phase) {
|
|
2398
|
+
case 'unsupported':
|
|
2399
|
+
body.innerHTML = '<div class="hint">当前环境不支持应用内更新(开发模式 / Portable / 未签名的 macOS 版),请从 GitHub Releases 手动下载。</div>';
|
|
2400
|
+
acts.innerHTML = rel + '<button class="btn" data-close-uu>取消</button>';
|
|
2401
|
+
break;
|
|
2402
|
+
case 'checking':
|
|
2403
|
+
body.innerHTML = '<div class="hint">正在检查更新…</div>';
|
|
2404
|
+
acts.innerHTML = close;
|
|
2405
|
+
break;
|
|
2406
|
+
case 'latest':
|
|
2407
|
+
body.innerHTML = '<div class="hint"><span class="dot on"></span> 已是最新版本(' + cur + ')</div>';
|
|
2408
|
+
acts.innerHTML = '<button class="btn primary" data-close-uu>好的</button>';
|
|
2409
|
+
break;
|
|
2410
|
+
case 'available':
|
|
2411
|
+
body.innerHTML = s.via === 'updater'
|
|
2412
|
+
? '<div class="hint">发现新版本 <b>v' + esc(s.version) + '</b>(' + cur + ')——正在后台下载,完成后退出 CreditDaddy 时自动安装。</div>'
|
|
2413
|
+
: '<div class="hint">发现新版本 <b>v' + esc(s.version) + '</b>(' + cur + (s.sizeMB ? ',约 ' + s.sizeMB + ' MB' : '') + '),可由本机直接下载安装。</div>';
|
|
2414
|
+
acts.innerHTML = (s.via === 'github' ? updActBtn('upd-go', '立即下载安装', 'primary') : '') + rel + close;
|
|
2415
|
+
break;
|
|
2416
|
+
case 'downloading':
|
|
2417
|
+
body.innerHTML = '<div class="hint">正在下载 v' + esc(s.version) + '</div>'
|
|
2418
|
+
+ '<div class="meter" style="margin:8px 0 4px"><span style="width:' + (s.percent || 0) + '%"></span></div>'
|
|
2419
|
+
+ '<div class="hint">' + (s.percent != null ? s.percent + '%' : '') + '(' + (s.mb || '…') + ' MB)' + (s.percent == null ? ' 获取大小中…' : '') + '</div>';
|
|
2420
|
+
acts.innerHTML = '<button class="btn" data-close-uu>后台继续</button>';
|
|
2421
|
+
break;
|
|
2422
|
+
case 'downloaded':
|
|
2423
|
+
body.innerHTML = '<div class="hint"><span class="dot on pulse"></span> <b>v' + esc(s.version) + '</b> '
|
|
2424
|
+
+ (s.via === 'updater' ? '已下载完成,重启即完成更新(退出时也会自动安装)。' : '安装包已下载完成,安装时 CreditDaddy 会先退出。') + '</div>';
|
|
2425
|
+
acts.innerHTML = updActBtn('upd-go', s.via === 'updater' ? '立即重启更新' : '立即安装', 'primary') + close;
|
|
2426
|
+
break;
|
|
2427
|
+
case 'error':
|
|
2428
|
+
default:
|
|
2429
|
+
body.innerHTML = '<div class="hint" style="color:var(--err)">检查更新失败:' + esc(s.error || '未知错误') + '</div>';
|
|
2430
|
+
acts.innerHTML = updActBtn('upd-retry', '重试', 'primary') + rel + '<button class="btn" data-close-uu>取消</button>';
|
|
2431
|
+
break;
|
|
2432
|
+
}
|
|
2433
|
+
const relBtn = $('upd-rel');
|
|
2434
|
+
if (relBtn) relBtn.onclick = () => updBridge.openReleases();
|
|
2435
|
+
const goBtn = $('upd-go');
|
|
2436
|
+
if (goBtn) { goBtn.disabled = true; goBtn.textContent = '处理中…'; updBridge.install().then(renderUpdateModal).catch((e) => { goBtn.disabled = false; goBtn.textContent = '重试'; }); }
|
|
2437
|
+
const retryBtn = $('upd-retry');
|
|
2438
|
+
if (retryBtn) retryBtn.onclick = () => updBridge.check().then(renderUpdateModal).catch(() => {});
|
|
2439
|
+
acts.querySelectorAll('[data-close-uu]').forEach((b) => { b.onclick = () => closeModal('m-update'); });
|
|
2440
|
+
}
|
|
2441
|
+
async function openUpdateModal(autoCheck) {
|
|
2442
|
+
if (!updBridge) return;
|
|
2443
|
+
openModal('m-update');
|
|
2444
|
+
try {
|
|
2445
|
+
updState = await updBridge.state();
|
|
2446
|
+
renderUpdateModal(updState);
|
|
2447
|
+
if (autoCheck) renderUpdateModal(await updBridge.check());
|
|
2448
|
+
} catch (e) {
|
|
2449
|
+
renderUpdateModal({ phase: 'error', error: String((e && e.message) || e), current: (updState && updState.current) || '' });
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
if (updBridge) {
|
|
2453
|
+
updBridge.onState((s) => { updState = s; $('upd-ver') && ($('upd-ver').textContent = '当前 v' + (s.current || '?')); if (document.getElementById('m-update').classList.contains('show')) renderUpdateModal(s); });
|
|
2454
|
+
updBridge.onOpen(() => openUpdateModal(true));
|
|
2455
|
+
}
|
|
2456
|
+
$('btn-update').onclick = () => openUpdateModal(true);
|
|
2457
|
+
|
|
2458
|
+
// ZCode 自动领取计划:保存(按新配置启用)/ 停用
|
|
2459
|
+
$('zac-ok').addEventListener('click', async () => {
|
|
2460
|
+
try {
|
|
2461
|
+
const r = await api('/api/zcode/net', { method: 'PUT', json: { autoClaim: true, claimIntervalMin: Number($('zac-interval').value), claimWindowMin: Number($('zac-window').value) } });
|
|
2462
|
+
state.zAutoClaim = r.autoClaim === true;
|
|
2463
|
+
state.zClaimIntervalMin = r.claimIntervalMin;
|
|
2464
|
+
state.zClaimWindowMin = r.claimWindowMin;
|
|
2465
|
+
renderStatus(); render();
|
|
2466
|
+
closeModal('m-zac');
|
|
2467
|
+
toast('自动领取已开启:每 ' + r.claimIntervalMin + ' 分钟检查' + (r.claimWindowMin > 0 ? ',运行 ' + r.claimWindowMin + ' 分钟后自动关闭' : ',一直运行直到手动关闭'));
|
|
2468
|
+
} catch (err) { toast(err.message, false); }
|
|
2469
|
+
});
|
|
2470
|
+
$('zac-off').addEventListener('click', async () => {
|
|
2471
|
+
try {
|
|
2472
|
+
const r = await api('/api/zcode/net', { method: 'PUT', json: { autoClaim: false } });
|
|
2473
|
+
state.zAutoClaim = r.autoClaim === true;
|
|
2474
|
+
renderStatus(); render();
|
|
2475
|
+
closeModal('m-zac');
|
|
2476
|
+
toast('ZCode 自动领取:已关闭');
|
|
2477
|
+
} catch (err) { toast(err.message, false); }
|
|
2478
|
+
});
|
|
2479
|
+
|
|
2480
|
+
// ZCode 体验包接口:开关 + 局域网开放 + 免密 IP 白名单一次保存
|
|
2481
|
+
function syncZgwLanFields() {
|
|
2482
|
+
const off = !$('zgw-on').checked;
|
|
2483
|
+
$('zgw-lan-fields').querySelectorAll('input, textarea').forEach((el) => { el.disabled = off; });
|
|
2484
|
+
$('zgw-lan-fields').style.opacity = off ? '.45' : '';
|
|
2485
|
+
}
|
|
2486
|
+
$('zgw-on').addEventListener('change', syncZgwLanFields);
|
|
2487
|
+
$('zgw-ok').addEventListener('click', async () => {
|
|
2488
|
+
const allow = $('zgw-allow').value.split('\n').map((x) => x.trim()).filter(Boolean);
|
|
2489
|
+
try {
|
|
2490
|
+
const r = await api('/api/zcode-gateway', { method: 'PUT', json: { enabled: $('zgw-on').checked, lan: $('zgw-lan').checked, allow } });
|
|
2491
|
+
state.zGateway = r.enabled === true;
|
|
2492
|
+
state.zGatewayLan = r.lan === true;
|
|
2493
|
+
state.zGatewayAllow = r.allow || [];
|
|
2494
|
+
renderStatus(); render();
|
|
2495
|
+
closeModal('m-zgw');
|
|
2496
|
+
toast('体验包接口:' + (r.enabled ? '已开启' : '已关闭')
|
|
2497
|
+
+ (r.enabled && r.lan ? ',局域网开放' + (r.allow && r.allow.length ? ',白名单 ' + r.allow.join('、') : ',白名单为空——白名单外需虚拟 key') + ';绑定改动重启后生效' : ''));
|
|
2498
|
+
} catch (err) { toast(err.message, false); }
|
|
2499
|
+
});
|
|
2500
|
+
|
|
2501
|
+
// ZCode 网络出口:顺序 + 代理地址保存
|
|
2502
|
+
$('znet-ok').addEventListener('click', async () => {
|
|
2503
|
+
try {
|
|
2504
|
+
const body = { proxyFirst: $('znet-proxy').checked, proxyUrl: $('znet-url').value.trim() };
|
|
2505
|
+
const r = await api('/api/zcode/net', { method: 'PUT', json: body });
|
|
2506
|
+
await loadZNet();
|
|
2507
|
+
renderStatus(); render();
|
|
2508
|
+
closeModal('m-znet');
|
|
2509
|
+
toast('ZCode 出口:' + (r.proxyFirst ? '代理优先(直连兜底)' : '直连优先(代理兜底)') + (body.proxyUrl === '' ? ',代理已清除' : ''));
|
|
2510
|
+
} catch (err) { toast(err.message, false); }
|
|
2511
|
+
});
|
|
2229
2512
|
document.querySelectorAll('.mask').forEach((m) => {
|
|
2230
2513
|
m.addEventListener('mousedown', (e) => { if (e.target === m) closeModal(m.id); });
|
|
2231
2514
|
m.querySelectorAll('[data-close]').forEach((b) => b.addEventListener('click', () => closeModal(m.id)));
|
|
@@ -2245,10 +2528,12 @@ const LOCAL_HINT = {
|
|
|
2245
2528
|
zcode: '读取本机 ZCode 客户端当前登录的账号(本地解密 ~/.zcode/v2/credentials.json),token 不出本机。',
|
|
2246
2529
|
mirasim: '读取本机 mirasim 客户端当前登录的账号(本地解密 ~/.mirasim/setting.json),token 不出本机。',
|
|
2247
2530
|
catpaw: '读取本机妙手(美团 CatPaw)客户端当前登录的账号(本地解密 %APPDATA%\\catpaw-moon\\catx-credential.json),token 不出本机。',
|
|
2531
|
+
trae: '读取本机 Trae 客户端当前登录的账号(本地解密 <客户端数据目录>\\User\\globalStorage\\storage.json 的 tc 信封),token 不出本机。',
|
|
2248
2532
|
};
|
|
2249
2533
|
function openAdd(product) {
|
|
2250
2534
|
state.addProduct = product || '';
|
|
2251
|
-
|
|
2535
|
+
// Trae 凭据只能从本机客户端 storage.json 解密:不支持浏览器登录 / 粘贴 Token
|
|
2536
|
+
const tabShown = { local: true, device: product !== 'mirasim' && product !== 'catpaw' && product !== 'trae', token: product !== 'zcode' && product !== 'mirasim' && product !== 'catpaw' && product !== 'trae' };
|
|
2252
2537
|
document.querySelectorAll('#m-add .tab').forEach((t) => { t.style.display = tabShown[t.dataset.tab] ? '' : 'none'; });
|
|
2253
2538
|
$('local-hint').textContent = LOCAL_HINT[state.addProduct];
|
|
2254
2539
|
let first = null;
|
|
@@ -2277,7 +2562,7 @@ function openAdd(product) {
|
|
|
2277
2562
|
async function localScan() {
|
|
2278
2563
|
const box = $('scan-result');
|
|
2279
2564
|
$('scan-status').textContent = '扫描中(最多约 10 秒)…';
|
|
2280
|
-
box.innerHTML = '<div class="hint">正在读取本机客户端凭据(Qoder / WorkBuddy / ZCode / mirasim /
|
|
2565
|
+
box.innerHTML = '<div class="hint">正在读取本机客户端凭据(Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae)…</div>';
|
|
2281
2566
|
try {
|
|
2282
2567
|
const r = await api('/api/local/scan', { method: 'POST' });
|
|
2283
2568
|
$('scan-status').textContent = '';
|
|
@@ -2298,9 +2583,9 @@ async function localScan() {
|
|
|
2298
2583
|
html += '<div class="hint" style="color:var(--err)">读取失败:' + esc(e.file) + ':' + esc(e.error) + '</div>';
|
|
2299
2584
|
}
|
|
2300
2585
|
if (!cands.length) {
|
|
2301
|
-
const where = state.addProduct ? prodLabel(state.addProduct) : 'Qoder / WorkBuddy / ZCode / mirasim / 妙手';
|
|
2586
|
+
const where = state.addProduct ? prodLabel(state.addProduct) : 'Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae';
|
|
2302
2587
|
const alt = state.addProduct === 'zcode' || state.addProduct === 'mirasim' || state.addProduct === 'catpaw' ? '' : ',或改用「浏览器登录」「粘贴 Token」';
|
|
2303
|
-
html += '<div class="hint">未发现已登录的 ' + where + ' 账号。请先在客户端登录' + alt + '。<br>已检查:Qoder / Qoder CN 客户端数据目录、WorkBuddy 凭据目录、ZCode 数据目录、mirasim
|
|
2588
|
+
html += '<div class="hint">未发现已登录的 ' + where + ' 账号。请先在客户端登录' + alt + '。<br>已检查:Qoder / Qoder CN 客户端数据目录、WorkBuddy 凭据目录、ZCode 数据目录、mirasim 数据目录、妙手凭据目录、Trae 数据目录'
|
|
2304
2589
|
+ (r.scannedDirs && r.scannedDirs.length ? '、' + r.scannedDirs.map(esc).join('、') : '') + '</div>';
|
|
2305
2590
|
}
|
|
2306
2591
|
box.innerHTML = html;
|
|
@@ -2639,11 +2924,24 @@ function renderCompMenu() {
|
|
|
2639
2924
|
+ '<div class="hint">开启后每次打开面板都需要输入密码;密码可随时在此修改或关闭。'
|
|
2640
2925
|
+ (state.sec.enabled ? '' : '账号导出不受影响(导出口令始终必填)。') + '</div>'
|
|
2641
2926
|
+ '<div class="row">' + renderSecActions() + '</div>'
|
|
2927
|
+
+ '<div class="comp-menu-title">自动签到</div>'
|
|
2928
|
+
+ '<div class="hint">' + autoCheckinHint() + '</div>'
|
|
2642
2929
|
+ '<div class="comp-menu-title">组件顺序(仪表盘与标签栏同步)</div>' + compRows
|
|
2643
2930
|
+ '<div class="comp-menu-title">账号卡片排序</div>'
|
|
2644
2931
|
+ (sortSections || '<div class="sort-hint">暂无账号</div>');
|
|
2645
2932
|
}
|
|
2646
2933
|
|
|
2934
|
+
/** 自动签到口径说明:调度周期 + 各产品业务日刷新时点 */
|
|
2935
|
+
function autoCheckinHint() {
|
|
2936
|
+
const sch = state.status && state.status.scheduler;
|
|
2937
|
+
const next = sch && sch.nextTickAt ? ',下次执行 ' + esc(String(sch.nextTickAt).slice(11, 16)) : '';
|
|
2938
|
+
const zc = sch && sch.zcode;
|
|
2939
|
+
const zcText = zc && zc.enabled
|
|
2940
|
+
? 'ZCode 自动领取运行中(每 ' + (zc.intervalMin || 2) + ' 分钟' + (zc.autoOffAt ? ',约 ' + Math.max(1, Math.ceil((new Date(zc.autoOffAt).getTime() - Date.now()) / 60000)) + ' 分钟后自动关闭' : ',一直运行') + '),周期可在 ZCode 页「自动领取计划」里改'
|
|
2941
|
+
: 'ZCode 活动领取默认关闭,可在 ZCode 页「自动领取计划」里开启并配置轮询周期';
|
|
2942
|
+
return '守护进程每 ~2 小时自动扫描执行一轮全部账号' + next + '。各产品「业务日」刷新时点(UTC+8):<b>Qoder 每日 10:00</b>(每台设备每日限领一个国际版账号)、<b>WorkBuddy 每日 00:00</b>,其余产品按 00:00;已领成功的账号按对应业务日记忆跳过,跨过刷新点后当天照常重试。' + esc(zcText) + '。';
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2647
2945
|
/** 在手动排序列表里移动一个 id;列表不存在时先按当前生效顺序物化一份 */
|
|
2648
2946
|
function moveAccountId(prod, aid, dir) {
|
|
2649
2947
|
const baseIds = sortedAccounts(prod, ofProduct(prod)).map((a) => a.id);
|
|
@@ -2662,6 +2960,11 @@ $('btn-comp').addEventListener('click', async () => {
|
|
|
2662
2960
|
openModal('m-settings');
|
|
2663
2961
|
await loadSettings();
|
|
2664
2962
|
renderCompMenu();
|
|
2963
|
+
// 桌面版才有检查更新入口(走 preload 桥到主进程的更新状态机)
|
|
2964
|
+
if (updBridge) {
|
|
2965
|
+
$('upd-entry').style.display = '';
|
|
2966
|
+
try { const s = await updBridge.state(); updState = s; $('upd-ver').textContent = '当前 v' + (s.current || '?'); } catch {}
|
|
2967
|
+
}
|
|
2665
2968
|
});
|
|
2666
2969
|
$('comp-menu').addEventListener('change', (e) => {
|
|
2667
2970
|
const c = e.target.dataset && e.target.dataset.comp;
|
|
@@ -2719,7 +3022,7 @@ $('btn-export-dash').onclick = () => { $('ex-provider').value = ''; openModal('m
|
|
|
2719
3022
|
$('btn-export').onclick = () => {
|
|
2720
3023
|
const v = state.view;
|
|
2721
3024
|
// ZCode 是单一 provider,直接按 provider 选;其余产品页按产品线(p:);仪表盘 / 10Router 导出全部
|
|
2722
|
-
$('ex-provider').value = v === 'zcode' ? 'zcode' : (v === 'qoder' || v === 'workbuddy' || v === 'mirasim' || v === 'catpaw') ? 'p:' + v : '';
|
|
3025
|
+
$('ex-provider').value = v === 'zcode' ? 'zcode' : (v === 'qoder' || v === 'workbuddy' || v === 'mirasim' || v === 'catpaw' || v === 'trae') ? 'p:' + v : '';
|
|
2723
3026
|
openModal('m-export');
|
|
2724
3027
|
};
|
|
2725
3028
|
$('btn-do-export').onclick = doExport;
|
|
@@ -2781,11 +3084,31 @@ $('btn-logs').onclick = () => {
|
|
|
2781
3084
|
if (!c) { loadLogs(); $('logs').scrollTop = $('logs').scrollHeight; }
|
|
2782
3085
|
};
|
|
2783
3086
|
|
|
3087
|
+
// ── 仪表盘底部两卡:默认收起,点标题展开,展开状态存本地 ──
|
|
3088
|
+
const DASH_FOLD_KEY = 'qd_dash_fold';
|
|
3089
|
+
state.dashFold = (() => { try { return JSON.parse(localStorage.getItem(DASH_FOLD_KEY) || '{}'); } catch { return {}; } })();
|
|
3090
|
+
function applyDashFold() {
|
|
3091
|
+
document.querySelectorAll('.dash-cols .pcard[data-fold]').forEach((el) => {
|
|
3092
|
+
const open = state.dashFold[el.dataset.fold] === true;
|
|
3093
|
+
el.classList.toggle('open', open);
|
|
3094
|
+
el.querySelector('.fold-ico').innerHTML = open ? I.chevUp : I.chevDown;
|
|
3095
|
+
});
|
|
3096
|
+
}
|
|
3097
|
+
document.querySelectorAll('.dash-cols .fold-head').forEach((h) => {
|
|
3098
|
+
h.title = '展开 / 收起';
|
|
3099
|
+
h.addEventListener('click', () => {
|
|
3100
|
+
const k = h.closest('.pcard').dataset.fold;
|
|
3101
|
+
state.dashFold[k] = state.dashFold[k] !== true;
|
|
3102
|
+
try { localStorage.setItem(DASH_FOLD_KEY, JSON.stringify(state.dashFold)); } catch {}
|
|
3103
|
+
applyDashFold();
|
|
3104
|
+
});
|
|
3105
|
+
});
|
|
3106
|
+
applyDashFold();
|
|
3107
|
+
|
|
2784
3108
|
// ── 启动:先直接请求(本机模式无需密钥);只有服务端返回 401 时才询问密钥 ──
|
|
2785
3109
|
initTheme();
|
|
2786
3110
|
$('btn-home').innerHTML = I.github;
|
|
2787
3111
|
applyView();
|
|
2788
|
-
|
|
2789
3112
|
(function boot() {
|
|
2790
3113
|
function start() {
|
|
2791
3114
|
loadStatus().then(loadAccounts).catch((e) => toast(e.message, false));
|
|
@@ -2801,7 +3124,7 @@ applyView();
|
|
|
2801
3124
|
});
|
|
2802
3125
|
loadLogs();
|
|
2803
3126
|
setInterval(loadLogs, 10000);
|
|
2804
|
-
setInterval(async () => { await loadStatus(); await loadZGateway(); render(); }, 30000);
|
|
3127
|
+
setInterval(async () => { await loadStatus(); await loadZNet(); await loadZGateway(); render(); }, 30000);
|
|
2805
3128
|
}
|
|
2806
3129
|
// 先直接请求(本机模式无需密码);需要密码时 api() 会弹框并重试
|
|
2807
3130
|
api('/api/status').then(start).catch((e) => toast(e.code === 'PANEL_KEY'
|