creditdaddy 1.3.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/README.md +24 -2
- package/bin/creditdaddy.js +131 -2
- package/package.json +29 -29
- package/src/bgdaemon.js +332 -0
- package/src/checkin.js +3 -2
- package/src/daemon.js +65 -3
- package/src/logger.js +57 -12
- package/src/minimaxClient.js +10 -1
- package/src/minimaxGateway.js +14 -8
- package/src/panel.html +252 -14
- package/src/store.js +16 -1
- package/src/tenrouterAccounts.js +31 -3
- package/src/traeClient.js +6 -1
- package/src/traeGateway.js +679 -0
- package/src/zcodeClient.js +69 -11
- package/src/zcodeGateway.js +25 -13
- package/src/zcodePlanShape.json +3 -3
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; }
|
|
@@ -182,6 +186,13 @@
|
|
|
182
186
|
.pcard.open .fold-body { display: block; }
|
|
183
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; }
|
|
184
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); }
|
|
185
196
|
.collapsed .logs { display: none; }
|
|
186
197
|
|
|
187
198
|
/* modal */
|
|
@@ -200,8 +211,10 @@
|
|
|
200
211
|
.pane { display: none; } .pane.active { display: block; }
|
|
201
212
|
.field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 10px; }
|
|
202
213
|
.field label { font-size: 12px; color: var(--text-2); font-weight: 600; }
|
|
203
|
-
.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; }
|
|
204
216
|
.input:focus { border-color: #4f86ff; }
|
|
217
|
+
.input:disabled { opacity: .6; cursor: not-allowed; }
|
|
205
218
|
.input.mono { font-family: ui-monospace, Consolas, monospace; }
|
|
206
219
|
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
207
220
|
.hint { font-size: 12px; color: var(--text-3); line-height: 1.7; }
|
|
@@ -416,6 +429,7 @@
|
|
|
416
429
|
<button class="btn" id="zt-open" style="display:none"></button>
|
|
417
430
|
</span>
|
|
418
431
|
<span id="t-actions" style="display:none">
|
|
432
|
+
<button class="btn" id="tt-gw"></button>
|
|
419
433
|
<button class="btn" id="tt-open" style="display:none"></button>
|
|
420
434
|
</span>
|
|
421
435
|
<span id="m-actions" style="display:none">
|
|
@@ -444,6 +458,11 @@
|
|
|
444
458
|
<button data-log-tab="run" class="active">运行日志</button>
|
|
445
459
|
<button data-log-tab="zcode">ZCode 网关</button>
|
|
446
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>
|
|
447
466
|
</div>
|
|
448
467
|
<button class="btn" id="btn-logs">展开</button>
|
|
449
468
|
</div>
|
|
@@ -510,6 +529,7 @@
|
|
|
510
529
|
<input class="input" id="ex-password" type="password" placeholder="至少 4 位" autocomplete="new-password">
|
|
511
530
|
</div>
|
|
512
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>
|
|
513
533
|
<div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="btn-do-export">导出</button></div>
|
|
514
534
|
</div>
|
|
515
535
|
</div>
|
|
@@ -533,6 +553,12 @@
|
|
|
533
553
|
<label>版本与更新</label>
|
|
534
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>
|
|
535
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>
|
|
536
562
|
<div class="modal-actions"><button class="btn" data-close>关闭</button></div>
|
|
537
563
|
</div>
|
|
538
564
|
</div>
|
|
@@ -641,14 +667,14 @@
|
|
|
641
667
|
<div class="modal-title"><span>ZCode 体验包接口</span><button class="close" data-close>✕</button></div>
|
|
642
668
|
<div class="hint" id="zgw-url" style="margin-bottom:10px"></div>
|
|
643
669
|
<div class="field">
|
|
644
|
-
<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>
|
|
645
671
|
</div>
|
|
646
672
|
<div class="field" id="zgw-lan-fields">
|
|
647
673
|
<label class="check"><input type="checkbox" id="zgw-lan"> 对局域网开放(重启后绑定 0.0.0.0)</label>
|
|
648
674
|
<label>IP 白名单(每行一个,支持 192.168.31.* 通配)</label>
|
|
649
675
|
<textarea class="input" id="zgw-allow" rows="4" placeholder="192.168.31.101"></textarea>
|
|
650
676
|
</div>
|
|
651
|
-
<div class="hint">开启后,领到的体验包额度变成本机的一个补全接口——10Router 建一个 anthropic-compatible 节点指向本机 /
|
|
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>
|
|
652
678
|
<div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="zgw-ok">保存</button></div>
|
|
653
679
|
</div>
|
|
654
680
|
</div>
|
|
@@ -662,7 +688,7 @@
|
|
|
662
688
|
<label class="check"><input type="checkbox" id="mgw-on"> 启用本地接口(/gateway/minimax/v1/messages)</label>
|
|
663
689
|
</div>
|
|
664
690
|
<div class="field" id="mgw-lan-fields">
|
|
665
|
-
<label class="check"><input type="checkbox" id="mgw-lan">
|
|
691
|
+
<label class="check"><input type="checkbox" id="mgw-lan"> 对局域网开放(重启后绑定 0.0.0.0)</label>
|
|
666
692
|
<label>IP 白名单(每行一个,支持 192.168.31.* 通配)</label>
|
|
667
693
|
<textarea class="input" id="mgw-allow" rows="4" placeholder="192.168.31.101"></textarea>
|
|
668
694
|
</div>
|
|
@@ -671,6 +697,24 @@
|
|
|
671
697
|
</div>
|
|
672
698
|
</div>
|
|
673
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
|
+
|
|
674
718
|
<input type="file" id="file-input" accept=".json,application/json" style="display:none">
|
|
675
719
|
<div id="toast"></div>
|
|
676
720
|
|
|
@@ -700,7 +744,7 @@ const I = {
|
|
|
700
744
|
};
|
|
701
745
|
|
|
702
746
|
const $ = (id) => document.getElementById(id);
|
|
703
|
-
const state = { accounts: [], quotas: {}, status: null, busy: {}, view: 'dash', region: '', addProduct: '', logTab: 'run' };
|
|
747
|
+
const state = { accounts: [], quotas: {}, status: null, busy: {}, view: 'dash', region: '', addProduct: '', logTab: 'run', logLevel: 'all' };
|
|
704
748
|
const VIEWS = ['dash', 'qoder', 'workbuddy', 'zcode', 'mirasim', 'catpaw', 'trae', 'minimax', 'tenrouter'];
|
|
705
749
|
// 10Router 集成:config 来自 /api/tenrouter,quotas 来自 /api/tenrouter/quotas
|
|
706
750
|
const tr = { config: null, quotas: null, error: null, loading: false, syncing: false };
|
|
@@ -853,7 +897,7 @@ const CLIENT_LABEL = { qoder: 'Qoder', workbuddy: 'WorkBuddy', zcode: 'ZCode', m
|
|
|
853
897
|
// 额度单位为 Credits/时长而非「积分」口径的产品(不计入剩余积分合计)
|
|
854
898
|
const CREDIT_VIEWS = new Set(['zcode', 'mirasim', 'catpaw']);
|
|
855
899
|
// 网关调用日志:tag → 所属产品页(无网关的产品不展示日志卡)
|
|
856
|
-
const GW_LOG_TAG = { zcode: 'ZCODE-GW', minimax: 'MINIMAX-GW' };
|
|
900
|
+
const GW_LOG_TAG = { zcode: 'ZCODE-GW', minimax: 'MINIMAX-GW', trae: 'TRAE-GW' };
|
|
857
901
|
const provOf = (p) => PROV[p] || { prod: 'qoder', prodLabel: p, region: '', cls: '' };
|
|
858
902
|
const provBadges = (p, regionOnly, flavor) => {
|
|
859
903
|
const v = provOf(p);
|
|
@@ -964,8 +1008,20 @@ function applyTheme(t) {
|
|
|
964
1008
|
function initTheme() {
|
|
965
1009
|
let t = null;
|
|
966
1010
|
try { t = localStorage.getItem('qd_theme'); } catch (e) {}
|
|
967
|
-
|
|
1011
|
+
const mq = window.matchMedia ? matchMedia('(prefers-color-scheme: dark)') : null;
|
|
1012
|
+
if (!t) t = mq && mq.matches ? 'dark' : 'light';
|
|
968
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
|
+
}
|
|
969
1025
|
$('btn-theme').onclick = () => {
|
|
970
1026
|
const n = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
|
|
971
1027
|
applyTheme(n);
|
|
@@ -1142,7 +1198,9 @@ function cardHtml(a) {
|
|
|
1142
1198
|
+ (isCurrent ? '<span class="pill cur" title="' + esc(CLIENT_LABEL[a.product] || '') + ' 客户端当前登录的账号">客户端当前</span>' : '')
|
|
1143
1199
|
+ (a.product === 'zcode' && isGatewayCurrent(a) ? '<span class="pill gw" title="体验包接口最近一次补全用的账号(多账号时按轮换自动选择,与客户端登录无关)">接口使用中</span>' : '')
|
|
1144
1200
|
+ (a.product === 'minimax' && isMiniMaxGatewayCurrent(a) ? '<span class="pill gw" title="本地接口最近一次补全用的账号(多账号时按轮换自动选择,与客户端登录无关)">接口使用中</span>' : '')
|
|
1201
|
+
+ (a.product === 'trae' && isTraeGatewayCurrent(a) ? '<span class="pill gw" title="本地接口最近一次补全用的账号(多账号时按轮换自动选择,与客户端登录无关)">接口使用中</span>' : '')
|
|
1145
1202
|
+ (a.isPat ? '<span class="badge">PAT</span>' : '')
|
|
1203
|
+
+ qoderWebBadge(a)
|
|
1146
1204
|
+ (a.verified === false ? '<span class="pill fail">token 无效</span>' : '')
|
|
1147
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>' : '')
|
|
1148
1206
|
+ '</div><div class="ops">'
|
|
@@ -1183,6 +1241,11 @@ function isMiniMaxGatewayCurrent(a) {
|
|
|
1183
1241
|
const last = state.mGatewayStats && state.mGatewayStats.lastAccount;
|
|
1184
1242
|
return Boolean(last) && (a.name === last || a.uid === last || a.id === last);
|
|
1185
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
|
+
}
|
|
1186
1249
|
|
|
1187
1250
|
// 客户端当前登录判定:WorkBuddy / mirasim / 妙手 / MiniMax 按 uid;ZCode 按 uid 或 email(早期导入的账号可能只存了其中一个)
|
|
1188
1251
|
function isClientCurrent(a) {
|
|
@@ -1295,6 +1358,9 @@ function renderProd() {
|
|
|
1295
1358
|
}
|
|
1296
1359
|
$('t-actions').style.display = prod === 'trae' ? 'contents' : 'none';
|
|
1297
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 白名单';
|
|
1298
1364
|
const tc = (state.status && state.status.traeClient) || {};
|
|
1299
1365
|
const canOpen = tc.installed && window.creditdaddy && typeof window.creditdaddy.openTraeClient === 'function';
|
|
1300
1366
|
$('tt-open').style.display = canOpen ? '' : 'none';
|
|
@@ -1600,6 +1666,21 @@ document.addEventListener('click', async (e) => {
|
|
|
1600
1666
|
} catch (err) { toast(String(err && err.message || err), false); }
|
|
1601
1667
|
return;
|
|
1602
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
|
+
}
|
|
1603
1684
|
if (e.target.closest('#mt-gw')) {
|
|
1604
1685
|
// MiniMax 本地接口弹窗:开关 + 局域网 + 白名单设置
|
|
1605
1686
|
$('mgw-on').checked = state.mGateway === true;
|
|
@@ -1631,7 +1712,7 @@ document.addEventListener('click', async (e) => {
|
|
|
1631
1712
|
const allow = Array.isArray(state.zGatewayAllow) ? state.zGatewayAllow : [];
|
|
1632
1713
|
$('zgw-allow').value = (allow.length ? allow : (state.zGatewaySuggest ? [state.zGatewaySuggest] : [])).join('\n');
|
|
1633
1714
|
const st = state.zGatewayStats;
|
|
1634
|
-
$('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)'
|
|
1635
1716
|
+ (st && st.lastCallAt
|
|
1636
1717
|
? '<br>最近调用 ' + esc(String(st.lastCallAt).slice(11, 19)) + ' · 共 ' + st.calls + ' 次' + (st.lastAccount ? ' · 最后用号 ' + esc(st.lastAccount) : '')
|
|
1637
1718
|
: '<br>尚未有调用');
|
|
@@ -1685,6 +1766,18 @@ async function loadMGateway() {
|
|
|
1685
1766
|
applyLogTabs(); // 网关开关变化影响日志标签显隐
|
|
1686
1767
|
} catch (e) {}
|
|
1687
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(); // 网关开关变化影响日志标签显隐
|
|
1779
|
+
} catch (e) {}
|
|
1780
|
+
}
|
|
1688
1781
|
|
|
1689
1782
|
// ── 10Router ──
|
|
1690
1783
|
function quotaPct(q) {
|
|
@@ -2212,9 +2305,13 @@ function applyLogTabs() {
|
|
|
2212
2305
|
let show = true;
|
|
2213
2306
|
if (t === 'zcode') show = Boolean(state.zGateway);
|
|
2214
2307
|
if (t === 'minimax') show = Boolean(state.mGateway);
|
|
2308
|
+
if (t === 'trae') show = Boolean(state.tGateway);
|
|
2215
2309
|
b.style.display = show ? '' : 'none';
|
|
2216
2310
|
b.classList.toggle('active', t === state.logTab);
|
|
2217
2311
|
});
|
|
2312
|
+
document.querySelectorAll('#log-filter button').forEach((b) => {
|
|
2313
|
+
b.classList.toggle('active', b.dataset.logLevel === state.logLevel);
|
|
2314
|
+
});
|
|
2218
2315
|
}
|
|
2219
2316
|
async function loadCurrentLogs() {
|
|
2220
2317
|
const el = $('logs');
|
|
@@ -2228,11 +2325,15 @@ async function loadCurrentLogs() {
|
|
|
2228
2325
|
const data = await api('/api/logs?tag=' + encodeURIComponent(GW_LOG_TAG[state.logTab]));
|
|
2229
2326
|
logs = data.logs || [];
|
|
2230
2327
|
}
|
|
2328
|
+
if (state.logLevel !== 'all') {
|
|
2329
|
+
logs = logs.filter((l) => l.level === 'warn' || l.level === 'error');
|
|
2330
|
+
}
|
|
2231
2331
|
const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 8;
|
|
2232
2332
|
const withTag = state.logTab === 'run';
|
|
2333
|
+
const ico = (lvl) => lvl === 'error' ? '✗' : lvl === 'warn' ? '⚠' : lvl === 'info' ? '·' : '';
|
|
2233
2334
|
el.innerHTML = logs.length
|
|
2234
|
-
? logs.map((l) => '<div class="' + esc(l.level) + '">[' + esc(l.at) + '] ' + (withTag ? esc(l.tag) + ' · ' : '') + esc(l.msg) + '</div>').join('')
|
|
2235
|
-
: '(暂无)';
|
|
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' ? '(暂无)' : '(暂无警告或错误)');
|
|
2236
2337
|
if (atBottom) el.scrollTop = el.scrollHeight;
|
|
2237
2338
|
} catch (e) {}
|
|
2238
2339
|
}
|
|
@@ -2528,6 +2629,57 @@ function okDialog() { const v = $('dlg-field').style.display === 'none' ? true :
|
|
|
2528
2629
|
$('dlg-ok').addEventListener('click', okDialog);
|
|
2529
2630
|
$('dlg-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') okDialog(); });
|
|
2530
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
|
+
|
|
2531
2683
|
// ── 检查更新(桌面版):主进程推状态(update:state),面板只管渲染 ──
|
|
2532
2684
|
const updBridge = window.creditdaddy && window.creditdaddy.update ? window.creditdaddy.update : null;
|
|
2533
2685
|
let updState = null;
|
|
@@ -2633,6 +2785,7 @@ function syncZgwLanFields() {
|
|
|
2633
2785
|
$('zgw-on').addEventListener('change', syncZgwLanFields);
|
|
2634
2786
|
$('zgw-ok').addEventListener('click', async () => {
|
|
2635
2787
|
const allow = $('zgw-allow').value.split('\n').map((x) => x.trim()).filter(Boolean);
|
|
2788
|
+
const oldLan = state.zGatewayLan;
|
|
2636
2789
|
try {
|
|
2637
2790
|
const r = await api('/api/zcode-gateway', { method: 'PUT', json: { enabled: $('zgw-on').checked, lan: $('zgw-lan').checked, allow } });
|
|
2638
2791
|
state.zGateway = r.enabled === true;
|
|
@@ -2640,6 +2793,7 @@ $('zgw-ok').addEventListener('click', async () => {
|
|
|
2640
2793
|
state.zGatewayAllow = r.allow || [];
|
|
2641
2794
|
renderStatus(); render();
|
|
2642
2795
|
closeModal('m-zgw');
|
|
2796
|
+
if (await maybePromptRestartForLan(oldLan, state.zGatewayLan)) return;
|
|
2643
2797
|
toast('体验包接口:' + (r.enabled ? '已开启' : '已关闭')
|
|
2644
2798
|
+ (r.enabled && r.lan ? ',局域网开放' + (r.allow && r.allow.length ? ',白名单 ' + r.allow.join('、') : ',白名单为空——白名单外需虚拟 key') + ';绑定改动重启后生效' : ''));
|
|
2645
2799
|
} catch (err) { toast(err.message, false); }
|
|
@@ -2654,6 +2808,7 @@ function syncMgwLanFields() {
|
|
|
2654
2808
|
$('mgw-on').addEventListener('change', syncMgwLanFields);
|
|
2655
2809
|
$('mgw-ok').addEventListener('click', async () => {
|
|
2656
2810
|
const allow = $('mgw-allow').value.split('\n').map((x) => x.trim()).filter(Boolean);
|
|
2811
|
+
const oldLan = state.mGatewayLan;
|
|
2657
2812
|
try {
|
|
2658
2813
|
const r = await api('/api/minimax-gateway', { method: 'PUT', json: { enabled: $('mgw-on').checked, lan: $('mgw-lan').checked, allow } });
|
|
2659
2814
|
state.mGateway = r.enabled === true;
|
|
@@ -2661,8 +2816,32 @@ $('mgw-ok').addEventListener('click', async () => {
|
|
|
2661
2816
|
state.mGatewayAllow = r.allow || [];
|
|
2662
2817
|
renderStatus(); render();
|
|
2663
2818
|
closeModal('m-mgw');
|
|
2819
|
+
if (await maybePromptRestartForLan(oldLan, state.mGatewayLan)) return;
|
|
2664
2820
|
toast('MiniMax 接口:' + (r.enabled ? '已开启' : '已关闭')
|
|
2665
|
-
+ (r.enabled && r.lan ? ',局域网开放' + (r.allow && r.allow.length ? ',白名单 ' + r.allow.join('、') : '') : ''));
|
|
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('、') : '') + ';绑定改动重启后生效' : ''));
|
|
2666
2845
|
} catch (err) { toast(err.message, false); }
|
|
2667
2846
|
});
|
|
2668
2847
|
|
|
@@ -2844,6 +3023,53 @@ async function addToken() {
|
|
|
2844
3023
|
b.disabled = false;
|
|
2845
3024
|
}
|
|
2846
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
|
+
|
|
2847
3073
|
// 导出
|
|
2848
3074
|
async function doExport() {
|
|
2849
3075
|
const password = $('ex-password').value;
|
|
@@ -3187,13 +3413,15 @@ $('btn-checkin-all').onclick = () => checkinAll($('btn-checkin-all'));
|
|
|
3187
3413
|
$('btn-checkin').onclick = () => checkinAll($('btn-checkin'), state.view);
|
|
3188
3414
|
$('btn-add-dash').onclick = () => openAdd('');
|
|
3189
3415
|
$('btn-add').onclick = () => openAdd(state.view);
|
|
3190
|
-
$('btn-export-dash').onclick = () => { $('ex-provider').value = ''; openModal('m-export'); };
|
|
3416
|
+
$('btn-export-dash').onclick = () => { $('ex-provider').value = ''; refreshExportNote(); openModal('m-export'); };
|
|
3191
3417
|
$('btn-export').onclick = () => {
|
|
3192
3418
|
const v = state.view;
|
|
3193
3419
|
// ZCode 是单一 provider,直接按 provider 选;其余产品页按产品线(p:);仪表盘 / 10Router 导出全部
|
|
3194
3420
|
$('ex-provider').value = v === 'zcode' ? 'zcode' : (v === 'qoder' || v === 'workbuddy' || v === 'mirasim' || v === 'catpaw' || v === 'trae' || v === 'minimax') ? 'p:' + v : '';
|
|
3421
|
+
refreshExportNote();
|
|
3195
3422
|
openModal('m-export');
|
|
3196
3423
|
};
|
|
3424
|
+
$('ex-provider').addEventListener('change', refreshExportNote);
|
|
3197
3425
|
$('btn-do-export').onclick = doExport;
|
|
3198
3426
|
$('btn-pass-ok').onclick = () => submitImport($('pass-input').value);
|
|
3199
3427
|
$('pass-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') submitImport($('pass-input').value); });
|
|
@@ -3220,6 +3448,7 @@ function applyView() {
|
|
|
3220
3448
|
$('view-10r').classList.toggle('active', state.view === 'tenrouter');
|
|
3221
3449
|
if (state.view === 'tenrouter') loadZGateway();
|
|
3222
3450
|
if (state.view === 'minimax') loadMGateway();
|
|
3451
|
+
if (state.view === 'trae') loadTGateway();
|
|
3223
3452
|
if (state.view === 'tenrouter' && tr.config && tr.config.configured && !tr.quotas && !tr.loading) loadTrQuotas();
|
|
3224
3453
|
followLogTab();
|
|
3225
3454
|
applyLogTabs();
|
|
@@ -3267,6 +3496,14 @@ $('log-tabs').addEventListener('click', (e) => {
|
|
|
3267
3496
|
loadCurrentLogs();
|
|
3268
3497
|
$('logs').scrollTop = $('logs').scrollHeight;
|
|
3269
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
|
+
});
|
|
3270
3507
|
|
|
3271
3508
|
// ── 仪表盘底部两卡:默认收起,点标题展开,展开状态存本地 ──
|
|
3272
3509
|
const DASH_FOLD_KEY = 'qd_dash_fold';
|
|
@@ -3300,6 +3537,7 @@ applyView();
|
|
|
3300
3537
|
loadZNet();
|
|
3301
3538
|
loadZGateway();
|
|
3302
3539
|
loadMGateway();
|
|
3540
|
+
loadTGateway();
|
|
3303
3541
|
loadTrConfig().then(() => loadTrQuotas());
|
|
3304
3542
|
setInterval(() => loadTrQuotas(), 5 * 60 * 1000);
|
|
3305
3543
|
// 账号余额(ZCode 剩余 token 等)自动刷新:可见时每 2 分钟一轮;从托盘/后台切回时若超过 1 分钟没刷过立即补一轮
|
|
@@ -3309,7 +3547,7 @@ applyView();
|
|
|
3309
3547
|
});
|
|
3310
3548
|
loadLogs();
|
|
3311
3549
|
setInterval(loadLogs, 10000);
|
|
3312
|
-
setInterval(async () => { await loadStatus(); await loadZNet(); await loadZGateway(); await loadMGateway(); render(); }, 30000);
|
|
3550
|
+
setInterval(async () => { await loadStatus(); await loadZNet(); await loadZGateway(); await loadMGateway(); await loadTGateway(); render(); }, 30000);
|
|
3313
3551
|
}
|
|
3314
3552
|
// 先直接请求(本机模式无需密码);需要密码时 api() 会弹框并重试
|
|
3315
3553
|
api('/api/status').then(start).catch((e) => toast(e.code === 'PANEL_KEY'
|
package/src/store.js
CHANGED
|
@@ -98,7 +98,16 @@ export async function loadAccounts() {
|
|
|
98
98
|
|
|
99
99
|
export async function saveAccounts(accounts) {
|
|
100
100
|
await ensureDir();
|
|
101
|
-
|
|
101
|
+
const file = ACCOUNTS_FILE();
|
|
102
|
+
const bak = file + '.bak';
|
|
103
|
+
try {
|
|
104
|
+
// 只有当已有账号文件存在且非空时才建立/更新 .bak 备份
|
|
105
|
+
const existing = await fs.readFile(file, 'utf8').catch(() => null);
|
|
106
|
+
if (existing && existing.trim().length > 2) {
|
|
107
|
+
await atomicWrite(bak, existing);
|
|
108
|
+
}
|
|
109
|
+
} catch {}
|
|
110
|
+
await atomicWrite(file, JSON.stringify(accounts, null, 2));
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
// 进程内串行队列:所有「读-改-写」都经由 withAccounts,避免并发请求互相覆盖
|
|
@@ -200,6 +209,12 @@ export function publicAccount(a) {
|
|
|
200
209
|
phone: maskPhone(meta.phone),
|
|
201
210
|
domain: meta.domain || null,
|
|
202
211
|
flavor: a.provider === 'zcode' ? zcodeFlavor(meta) : null,
|
|
212
|
+
// Qoder 网页会话新鲜度(10Router issue #44):只回时间戳、绝不回 Cookie。
|
|
213
|
+
// 10Router 的「套餐内 Credits」明细依赖导出的这份会话;面板据此在
|
|
214
|
+
// 导出/同步缺会话或明显过期的 Qoder 账号时给出提示。
|
|
215
|
+
qoderWebCapturedAt: String(a.provider).startsWith('qoder')
|
|
216
|
+
? (meta.qoderWebSession?.capturedAt || null)
|
|
217
|
+
: null,
|
|
203
218
|
canSwitch: Boolean(meta.session?.account || meta.credentials || meta.qoderAuth || a.provider === 'catpaw' || a.provider === 'trae'),
|
|
204
219
|
verified: a.verified ?? null,
|
|
205
220
|
createdAt: a.createdAt,
|
package/src/tenrouterAccounts.js
CHANGED
|
@@ -92,6 +92,9 @@ async function syncViaTransfer({ endpoint, cookie, groups }) {
|
|
|
92
92
|
results.push({
|
|
93
93
|
provider: target, count: list.length, status: (d.failed || 0) ? 'partial' : 'ok',
|
|
94
94
|
imported: d.imported || 0, updated: d.updated || 0, skipped: d.skipped || 0, failed: d.failed || 0,
|
|
95
|
+
// 10Router issue #44:导入端会探测刚同步过去的网页会话是否仍可用,
|
|
96
|
+
// 失败清单直接回显到本端摘要(见 syncAccountsTo10r 的 sessionWarns)。
|
|
97
|
+
webSessions: d.webSessions || null,
|
|
95
98
|
});
|
|
96
99
|
}
|
|
97
100
|
return results;
|
|
@@ -103,9 +106,15 @@ async function syncViaProviders({ endpoint, key, groups }) {
|
|
|
103
106
|
for (const [target, list] of groups) {
|
|
104
107
|
const row = { provider: target, count: list.length, status: 'ok', imported: 0, skipped: 0, failed: 0 };
|
|
105
108
|
for (const a of list) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
// issue #44:qoder 的 apikey 连接同样按 providerSpecificData 读网页会话
|
|
110
|
+
// (usage 侧 getQoderUsage 不区分 authType),所以新建连接时把会话一并
|
|
111
|
+
// 挂上;已存在的同名连接此接口不更新(409),缺失由同步摘要点名提醒。
|
|
112
|
+
const web = a.meta?.qoderWebSession?.cookie && String(a.provider).startsWith('qoder')
|
|
113
|
+
? { creditDaddyWebSession: { cookie: a.meta.qoderWebSession.cookie, capturedAt: a.meta.qoderWebSession.capturedAt || null, userId: a.uid || null } }
|
|
114
|
+
: null;
|
|
115
|
+
const body = { provider: target, apiKey: a.token, name: connName(a) };
|
|
116
|
+
if (web) body.providerSpecificData = { userId: a.uid || null, ...web };
|
|
117
|
+
const r = await trFetch(`${endpoint}/api/providers`, { key, method: 'POST', body });
|
|
109
118
|
if (r.status === 201 || r.status === 200) row.imported++;
|
|
110
119
|
else if (r.status === 409) row.skipped++;
|
|
111
120
|
else { row.failed++; row.error = r.data?.error || `HTTP ${r.status}`; }
|
|
@@ -163,6 +172,25 @@ export async function syncAccountsTo10r({ endpoint, key, adminPassword, accounts
|
|
|
163
172
|
if (sum('skipped')) parts.push(`已存在 ${sum('skipped')}`);
|
|
164
173
|
if (sum('failed')) parts.push(`失败 ${sum('failed')}`);
|
|
165
174
|
if (unmapped.size) parts.push('跳过 ' + [...unmapped].map(([p, n]) => `${p} ×${n}`).join('、'));
|
|
175
|
+
// issue #44:Qoder 网页会话决定 10Router 端「套餐内 Credits」能不能显示。
|
|
176
|
+
// OAuth 通道:10Router 导入时已逐账号探测,失效清单原样带回(deadNames);
|
|
177
|
+
// 两条通道都要点名本地就没有会话的账号(apikey 通道 webSessions 恒为
|
|
178
|
+
// null,条件自动适用;OAuth 通道里已被探测过的组不重复报)。
|
|
179
|
+
const deadNames = [];
|
|
180
|
+
for (const r of results) {
|
|
181
|
+
for (const f of r.webSessions?.failures || []) deadNames.push(f.name);
|
|
182
|
+
}
|
|
183
|
+
const sessionless = [];
|
|
184
|
+
for (const [target, list] of groups) {
|
|
185
|
+
if (!String(target).startsWith('qoder')) continue;
|
|
186
|
+
for (const a of list) {
|
|
187
|
+
if (results.find((r) => r.provider === target)?.webSessions == null && !a.meta?.qoderWebSession?.cookie) {
|
|
188
|
+
sessionless.push(connName(a));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (sessionless.length) parts.push(`未带网页会话 ${sessionless.length}(${sessionless.join('、')},在 CreditDaddy 重新浏览器登录 Qoder 后再同步可补齐逐资源包明细)`);
|
|
193
|
+
if (deadNames.length) parts.push(`网页会话已失效 ${deadNames.length}(${deadNames.join('、')},重新登录 Qoder 网页后再次同步)`);
|
|
166
194
|
const summary = (channel === 'oauth' ? '账号同步完成(OAuth 通道):' : '账号同步完成(apikey 通道,token 过期后需重新同步):')
|
|
167
195
|
+ (parts.join(',') || '无变化')
|
|
168
196
|
+ (failed.length ? `;${failed.map((r) => r.provider + ':' + (r.error || r.status)).join(';')}` : '');
|
package/src/traeClient.js
CHANGED
|
@@ -223,6 +223,11 @@ export async function checkinTrae(account, ctx = {}) {
|
|
|
223
223
|
return { ...base, status: 'already', message: '今天已签到', uid };
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
// 签到应得额度:status 接口里通常预告 credits / extra_credits(如 100 积分),
|
|
227
|
+
// claim 接口有时只返回 { code: 0, message: "success" } 而不包含 credits 字段,作为 fallback 避免 +0
|
|
228
|
+
const TRAE_DEFAULT_DAILY_CREDITS = 100;
|
|
229
|
+
const expectedCredits = Number(findField(r.body, 'credits') ?? findField(r.body, 'extra_credits')) || TRAE_DEFAULT_DAILY_CREDITS;
|
|
230
|
+
|
|
226
231
|
const c = await post(CLAIM_URL, account);
|
|
227
232
|
if (c.code !== 0) {
|
|
228
233
|
ctx.log?.(`Trae 领取失败:${c.message || `code ${c.code}`}`);
|
|
@@ -233,7 +238,7 @@ export async function checkinTrae(account, ctx = {}) {
|
|
|
233
238
|
uid,
|
|
234
239
|
};
|
|
235
240
|
}
|
|
236
|
-
const claimed = Number(findField(c.body, 'credits')) ||
|
|
241
|
+
const claimed = Number(findField(c.body, 'credits')) || expectedCredits;
|
|
237
242
|
return { ...base, status: 'checked-in', claimedAmount: claimed, message: claimed ? `签到成功,+${claimed} 积分` : '签到成功', uid };
|
|
238
243
|
}
|
|
239
244
|
|