llm-api-gateway-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.env.example +10 -0
  2. package/README.md +1127 -0
  3. package/cli-agent.js +666 -0
  4. package/cli-anthropic.js +236 -0
  5. package/cli-claude-code.js +317 -0
  6. package/cli-openai.js +212 -0
  7. package/completions/_llm-api-gateway-cli +65 -0
  8. package/completions/llm-api-gateway-cli.bash +64 -0
  9. package/completions/llm-api-gateway-cli.fish +43 -0
  10. package/images/chat.png +0 -0
  11. package/images/settings.png +0 -0
  12. package/images/task.png +0 -0
  13. package/lib/agent.js +607 -0
  14. package/lib/commands.js +468 -0
  15. package/lib/common.js +196 -0
  16. package/lib/config.js +70 -0
  17. package/lib/configcmd.js +230 -0
  18. package/lib/hub.js +1494 -0
  19. package/lib/jsonstore.js +49 -0
  20. package/lib/mcp.js +375 -0
  21. package/lib/memory.js +109 -0
  22. package/lib/plandoc.js +178 -0
  23. package/lib/pricing.js +52 -0
  24. package/lib/runner.js +234 -0
  25. package/lib/runstore.js +96 -0
  26. package/lib/secrets.js +198 -0
  27. package/lib/sessionstore.js +269 -0
  28. package/lib/settings.js +517 -0
  29. package/lib/tasksession.js +594 -0
  30. package/lib/taskstore.js +740 -0
  31. package/lib/tools.js +927 -0
  32. package/package.json +55 -0
  33. package/public/app.js +1055 -0
  34. package/public/index.html +167 -0
  35. package/public/manual.css +215 -0
  36. package/public/manual.html +381 -0
  37. package/public/manual.js +186 -0
  38. package/public/models.js +121 -0
  39. package/public/render.js +250 -0
  40. package/public/styles.css +955 -0
  41. package/public/task-slash.js +493 -0
  42. package/public/task.css +739 -0
  43. package/public/task.html +220 -0
  44. package/public/task.js +3127 -0
  45. package/public/theme.js +91 -0
  46. package/public/tint.js +261 -0
  47. package/scripts/install.ps1 +537 -0
  48. package/scripts/install.sh +510 -0
  49. package/server.js +14 -0
  50. package/task-server.js +15 -0
@@ -0,0 +1,955 @@
1
+ /* 主题变量:深色是默认,浅色由 html[data-theme="light"] 覆盖。
2
+ 没手动选过时跟随系统(防闪白脚本 + theme.js)。 */
3
+ :root {
4
+ /* 层次:底 → 浮起 → 更浮起 */
5
+ --bg: #0f1115;
6
+ --bg-soft: #161a21;
7
+ --bg-raised: #1c212a;
8
+ --bg-code: #12151b;
9
+ --bg-deep: #0b0d11;
10
+ --raised-translucent: rgba(28, 33, 42, .92);
11
+ --overlay: rgba(6, 8, 11, .72);
12
+
13
+ /* 描边:普通 / 悬停 / 聚焦 / 虚线 */
14
+ --border: #272d38;
15
+ --border-strong: #39414f;
16
+ --border-focus: #34435c;
17
+ --border-dashed: #3a4a63;
18
+
19
+ --text: #e6e9ef;
20
+ --text-dim: #9aa4b2;
21
+ --text-faint: #6b7482;
22
+
23
+ --accent: #4f8cff;
24
+ --accent-hover: #3f7cf0;
25
+ --accent-soft: #1d2c44;
26
+ --on-accent: #fff;
27
+
28
+ --danger: #ff6b6b;
29
+ --danger-soft: rgba(255, 107, 107, .07);
30
+ --danger-border: rgba(255, 107, 107, .28);
31
+ --danger-ring: rgba(255, 107, 107, .12);
32
+
33
+ --ok: #3ddc97;
34
+ --ok-soft: #223027;
35
+ --ok-ring: rgba(61, 220, 151, .12);
36
+
37
+ --warn: #f0b429;
38
+ --warn-soft: rgba(240, 180, 41, .06);
39
+ --warn-soft-strong: rgba(240, 180, 41, .16);
40
+ --warn-border: rgba(240, 180, 41, .45);
41
+ --warn-ring: rgba(240, 180, 41, .12);
42
+
43
+ --plan: #61afef;
44
+ --plan-soft: rgba(97, 175, 239, .14);
45
+
46
+ --scrollbar: #2a3038;
47
+ --scrollbar-hover: #39414f;
48
+ --shadow: 0 24px 60px rgba(0, 0, 0, .5);
49
+ --shadow-strong: 0 12px 30px rgba(0, 0, 0, .45);
50
+ --shadow-xs: 0 1px 2px rgba(0, 0, 0, .18);
51
+
52
+ --radius: 10px;
53
+ --radius-sm: 8px;
54
+ --t: 140ms cubic-bezier(.4, 0, .2, 1);
55
+ --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
56
+
57
+ color-scheme: dark;
58
+ }
59
+
60
+ html[data-theme="light"] {
61
+ --bg: #f5f6f8;
62
+ --bg-soft: #ffffff;
63
+ --bg-raised: #ffffff;
64
+ --bg-code: #f2f4f7;
65
+ --bg-deep: #fafbfc;
66
+ --raised-translucent: rgba(255, 255, 255, .94);
67
+ --overlay: rgba(24, 28, 36, .42);
68
+
69
+ --border: #e2e6ec;
70
+ --border-strong: #c3cbd8;
71
+ --border-focus: #9db4d8;
72
+ --border-dashed: #c3cbd8;
73
+
74
+ --text: #1a1d23;
75
+ --text-dim: #5b6472;
76
+ --text-faint: #8b94a3;
77
+
78
+ --accent: #2563eb;
79
+ --accent-hover: #1d4ed8;
80
+ --accent-soft: #e8f0ff;
81
+ --on-accent: #fff;
82
+
83
+ --danger: #d92d20;
84
+ --danger-soft: rgba(217, 45, 32, .06);
85
+ --danger-border: rgba(217, 45, 32, .26);
86
+ --danger-ring: rgba(217, 45, 32, .14);
87
+
88
+ --ok: #0f7a57;
89
+ --ok-soft: #e3f5ed;
90
+ --ok-ring: rgba(15, 122, 87, .14);
91
+
92
+ --warn: #b45309;
93
+ --warn-soft: rgba(180, 83, 9, .07);
94
+ --warn-soft-strong: rgba(180, 83, 9, .14);
95
+ --warn-border: rgba(180, 83, 9, .38);
96
+ --warn-ring: rgba(180, 83, 9, .16);
97
+
98
+ --plan: #1d6fb8;
99
+ --plan-soft: rgba(29, 111, 184, .11);
100
+
101
+ --scrollbar: #ccd2db;
102
+ --scrollbar-hover: #aeb6c2;
103
+ --shadow: 0 20px 50px rgba(24, 28, 36, .16);
104
+ --shadow-strong: 0 10px 26px rgba(24, 28, 36, .14);
105
+ --shadow-xs: 0 1px 2px rgba(24, 28, 36, .10);
106
+
107
+ color-scheme: light;
108
+ }
109
+
110
+ * { box-sizing: border-box; }
111
+
112
+ html, body {
113
+ height: 100%;
114
+ margin: 0;
115
+ }
116
+
117
+ body {
118
+ background: var(--bg);
119
+ color: var(--text);
120
+ font: 14px/1.6 system-ui, -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif;
121
+ -webkit-font-smoothing: antialiased;
122
+ transition: background-color var(--t), color var(--t);
123
+ }
124
+
125
+ .app {
126
+ display: flex;
127
+ height: 100%;
128
+ overflow: hidden;
129
+ }
130
+
131
+ /* ---------- 会话侧边栏 ---------- */
132
+
133
+ .sidebar {
134
+ width: 244px;
135
+ flex: none;
136
+ display: flex;
137
+ flex-direction: column;
138
+ border-right: 1px solid var(--border);
139
+ background: var(--bg-code);
140
+ }
141
+
142
+ .sidebar-head {
143
+ padding: 12px;
144
+ border-bottom: 1px solid var(--border);
145
+ display: flex;
146
+ flex-direction: column;
147
+ gap: 8px; /* 「新任务」与「新对话」之间的间距 */
148
+ }
149
+
150
+ .btn.block { width: 100%; }
151
+
152
+ /* 「新任务」跳到任务模式(另一个端口),用 ↗ 明示会开新页面 */
153
+ .task-entry {
154
+ display: flex;
155
+ align-items: center;
156
+ justify-content: center;
157
+ gap: 6px;
158
+ text-decoration: none;
159
+ box-sizing: border-box;
160
+ }
161
+
162
+ .task-entry .ext-mark { font-size: 11px; opacity: .55; }
163
+
164
+ .task-entry:hover .ext-mark { opacity: 1; }
165
+
166
+ /* 任务页顶栏里回聊天页的反向入口 */
167
+ .chat-entry { display: inline-flex; align-items: center; gap: 5px; text-decoration: none; box-sizing: border-box; }
168
+ .chat-entry .ext-mark { font-size: 11px; opacity: .55; }
169
+ .chat-entry:hover .ext-mark { opacity: 1; }
170
+
171
+ .session-list { flex: 1; overflow-y: auto; padding: 8px; }
172
+
173
+ .session-item {
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 4px;
177
+ border-radius: 8px;
178
+ padding: 2px 4px 2px 8px;
179
+ margin-bottom: 2px;
180
+ cursor: pointer;
181
+ }
182
+
183
+ .session-item:hover { background: var(--bg-soft); }
184
+ .session-item.active { background: var(--accent-soft); }
185
+
186
+ .session-main {
187
+ flex: 1;
188
+ min-width: 0;
189
+ display: flex;
190
+ flex-direction: column;
191
+ gap: 1px;
192
+ background: none;
193
+ border: none;
194
+ color: inherit;
195
+ font: inherit;
196
+ text-align: left;
197
+ padding: 6px 0;
198
+ cursor: pointer;
199
+ }
200
+
201
+ .session-title {
202
+ font-size: 13px;
203
+ color: var(--text-dim);
204
+ white-space: nowrap;
205
+ overflow: hidden;
206
+ text-overflow: ellipsis;
207
+ }
208
+
209
+ .session-item.active .session-title { color: var(--text); }
210
+
211
+ .session-time { font-size: 11px; color: var(--text-faint); }
212
+
213
+ .session-acts { display: flex; gap: 2px; opacity: 0; flex: none; }
214
+ .session-item:hover .session-acts { opacity: 1; }
215
+
216
+ .icon-btn {
217
+ border: none;
218
+ background: none;
219
+ color: var(--text-faint);
220
+ font-size: 12px;
221
+ line-height: 1;
222
+ padding: 4px 5px;
223
+ border-radius: 5px;
224
+ cursor: pointer;
225
+ }
226
+
227
+ .icon-btn:hover { background: var(--bg-raised); color: var(--text); }
228
+ .icon-btn.danger:hover { color: var(--danger); }
229
+
230
+ .session-rename {
231
+ width: 100%;
232
+ background: var(--bg);
233
+ border: 1px solid var(--accent);
234
+ border-radius: 6px;
235
+ color: var(--text);
236
+ font: inherit;
237
+ font-size: 13px;
238
+ padding: 5px 7px;
239
+ outline: none;
240
+ margin: 2px 0;
241
+ }
242
+
243
+ .sidebar-foot {
244
+ display: flex;
245
+ align-items: center;
246
+ gap: 6px;
247
+ padding: 8px 10px;
248
+ border-top: 1px solid var(--border);
249
+ font-size: 11px;
250
+ color: var(--text-faint);
251
+ }
252
+
253
+ .foot-sep { opacity: .5; }
254
+ .sidebar-foot .btn.tiny { padding: 2px 8px; font-size: 11px; margin-left: auto; }
255
+
256
+ .sessions-empty { padding: 14px 10px; font-size: 12px; color: var(--text-faint); text-align: center; line-height: 1.7; }
257
+
258
+ .icon-only { padding: 6px 9px; font-size: 14px; line-height: 1; }
259
+
260
+ /* ---------- 主区 ---------- */
261
+
262
+ .main {
263
+ display: flex;
264
+ flex-direction: column;
265
+ flex: 1;
266
+ min-width: 0;
267
+ }
268
+
269
+ .topbar {
270
+ display: flex;
271
+ align-items: center;
272
+ gap: 12px;
273
+ padding: 12px 18px;
274
+ border-bottom: 1px solid var(--border);
275
+ background: var(--bg-soft);
276
+ }
277
+
278
+ .brand {
279
+ display: flex;
280
+ align-items: center;
281
+ gap: 8px;
282
+ }
283
+
284
+ .brand h1 {
285
+ font-size: 15px;
286
+ font-weight: 600;
287
+ margin: 0;
288
+ letter-spacing: .2px;
289
+ }
290
+
291
+ .tag {
292
+ font-size: 11px;
293
+ color: var(--accent);
294
+ background: var(--accent-soft);
295
+ border: 1px solid var(--border-strong);
296
+ border-radius: 999px;
297
+ padding: 1px 8px;
298
+ }
299
+
300
+ .dot {
301
+ width: 8px;
302
+ height: 8px;
303
+ border-radius: 50%;
304
+ background: var(--text-faint);
305
+ flex: none;
306
+ }
307
+
308
+ .dot.ok { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-ring); }
309
+ .dot.err { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-ring); }
310
+
311
+ .meta {
312
+ flex: 1;
313
+ min-width: 0;
314
+ color: var(--text-faint);
315
+ font-family: var(--mono);
316
+ font-size: 12px;
317
+ white-space: nowrap;
318
+ overflow: hidden;
319
+ text-overflow: ellipsis;
320
+ }
321
+
322
+ .topbar-actions { display: flex; gap: 8px; }
323
+
324
+ /* ---------- 消息区 ---------- */
325
+
326
+ .messages {
327
+ flex: 1;
328
+ overflow-y: auto;
329
+ padding: 22px 0 8px;
330
+ scroll-behavior: smooth;
331
+ }
332
+
333
+ .msg {
334
+ display: flex;
335
+ gap: 12px;
336
+ max-width: 900px;
337
+ margin: 0 auto 18px;
338
+ padding: 0 20px;
339
+ }
340
+
341
+ .avatar {
342
+ flex: none;
343
+ width: 26px;
344
+ height: 26px;
345
+ border-radius: 7px;
346
+ display: grid;
347
+ place-items: center;
348
+ font-size: 11px;
349
+ font-weight: 700;
350
+ margin-top: 2px;
351
+ }
352
+
353
+ .msg.user .avatar { background: var(--accent-soft); color: var(--accent); }
354
+ .msg.assistant .avatar { background: var(--ok-soft); color: var(--ok); }
355
+
356
+ .bubble {
357
+ min-width: 0;
358
+ flex: 1;
359
+ padding-top: 2px;
360
+ word-wrap: break-word;
361
+ overflow-wrap: anywhere;
362
+ }
363
+
364
+ .msg.user .bubble {
365
+ background: var(--bg-raised);
366
+ border: 1px solid var(--border);
367
+ border-radius: var(--radius);
368
+ padding: 10px 14px;
369
+ white-space: pre-wrap;
370
+ }
371
+
372
+ .bubble.streaming::after {
373
+ content: "▍";
374
+ color: var(--accent);
375
+ animation: blink 1s steps(2, start) infinite;
376
+ margin-left: 1px;
377
+ }
378
+
379
+ @keyframes blink { to { visibility: hidden; } }
380
+
381
+ .bubble.error {
382
+ color: var(--danger);
383
+ background: var(--danger-soft);
384
+ border: 1px solid var(--danger-border);
385
+ border-radius: var(--radius);
386
+ padding: 10px 14px;
387
+ font-family: var(--mono);
388
+ font-size: 12.5px;
389
+ white-space: pre-wrap;
390
+ }
391
+
392
+ .bubble.notice { color: var(--text-dim); font-style: italic; }
393
+
394
+ /* ---------- 思维链 ---------- */
395
+
396
+ .reasoning {
397
+ margin: 0 0 12px;
398
+ background: var(--bg-code);
399
+ border: 1px solid var(--border);
400
+ border-radius: 8px;
401
+ overflow: hidden;
402
+ }
403
+
404
+ .reasoning > summary {
405
+ padding: 7px 12px;
406
+ font-size: 12px;
407
+ color: var(--text-faint);
408
+ cursor: pointer;
409
+ user-select: none;
410
+ list-style: none;
411
+ }
412
+
413
+ .reasoning > summary::-webkit-details-marker { display: none; }
414
+ .reasoning > summary::before { content: "▸ "; color: var(--accent); }
415
+ .reasoning[open] > summary::before { content: "▾ "; }
416
+ .reasoning > summary:hover { color: var(--text-dim); }
417
+
418
+ .reasoning-body {
419
+ max-height: 320px;
420
+ overflow: auto;
421
+ padding: 9px 12px 10px;
422
+ border-top: 1px solid var(--border);
423
+ font-family: var(--mono);
424
+ font-size: 12px;
425
+ line-height: 1.65;
426
+ color: var(--text-faint);
427
+ white-space: pre-wrap;
428
+ word-break: break-word;
429
+ }
430
+
431
+ /* ---------- Markdown ---------- */
432
+
433
+ .bubble p { margin: 0 0 10px; }
434
+ .bubble > *:last-child { margin-bottom: 0; }
435
+ .bubble h2, .bubble h3, .bubble h4, .bubble h5, .bubble h6 { margin: 16px 0 8px; line-height: 1.3; }
436
+ .bubble h2 { font-size: 17px; }
437
+ .bubble h3 { font-size: 15.5px; }
438
+ .bubble h4, .bubble h5, .bubble h6 { font-size: 14px; }
439
+ .bubble ul, .bubble ol { margin: 0 0 10px; padding-left: 22px; }
440
+ .bubble li { margin: 3px 0; }
441
+ .bubble a { color: var(--accent); }
442
+ .bubble hr { border: none; border-top: 1px solid var(--border); margin: 14px 0; }
443
+ .bubble blockquote {
444
+ margin: 0 0 10px;
445
+ padding: 2px 0 2px 12px;
446
+ border-left: 3px solid var(--border);
447
+ color: var(--text-dim);
448
+ }
449
+
450
+ .bubble code {
451
+ font-family: var(--mono);
452
+ font-size: 12.5px;
453
+ background: var(--bg-raised);
454
+ border: 1px solid var(--border);
455
+ border-radius: 5px;
456
+ padding: 1px 5px;
457
+ }
458
+
459
+ .code-block {
460
+ position: relative;
461
+ margin: 0 0 10px;
462
+ background: var(--bg-deep);
463
+ border: 1px solid var(--border);
464
+ border-radius: var(--radius);
465
+ overflow: auto;
466
+ }
467
+
468
+ .code-block code {
469
+ display: block;
470
+ padding: 12px 14px;
471
+ background: none;
472
+ border: none;
473
+ font-size: 12.5px;
474
+ line-height: 1.55;
475
+ white-space: pre;
476
+ }
477
+
478
+ .code-block .copy {
479
+ position: absolute;
480
+ top: 6px;
481
+ right: 6px;
482
+ font-size: 11px;
483
+ padding: 3px 8px;
484
+ border-radius: 6px;
485
+ border: 1px solid var(--border);
486
+ background: var(--raised-translucent);
487
+ color: var(--text-dim);
488
+ cursor: pointer;
489
+ opacity: 0;
490
+ transition: opacity .12s;
491
+ }
492
+
493
+ .code-block:hover .copy { opacity: 1; }
494
+ .code-block .copy:hover { color: var(--text); border-color: var(--border-strong); }
495
+
496
+ /* Markdown 表格:页面上渲染成真表格(窄屏横向滚动),原文留在 data-md 里供「复制 Markdown」 */
497
+ .md-table {
498
+ position: relative;
499
+ margin: 0 0 10px;
500
+ }
501
+
502
+ .md-table-scroll {
503
+ overflow-x: auto;
504
+ background: var(--bg-deep);
505
+ border: 1px solid var(--border);
506
+ border-radius: var(--radius);
507
+ }
508
+
509
+ .md-table table {
510
+ border-collapse: collapse;
511
+ width: 100%;
512
+ font-size: 12.5px;
513
+ }
514
+
515
+ .md-table th,
516
+ .md-table td {
517
+ padding: 7px 10px;
518
+ border-bottom: 1px solid var(--border);
519
+ border-right: 1px solid var(--border);
520
+ vertical-align: top;
521
+ }
522
+
523
+ .md-table th {
524
+ background: var(--bg-raised);
525
+ font-weight: 600;
526
+ white-space: nowrap;
527
+ }
528
+
529
+ .md-table tr:last-child td { border-bottom: none; }
530
+ .md-table th:last-child, .md-table td:last-child { border-right: none; }
531
+
532
+ .md-table .copy {
533
+ position: absolute;
534
+ top: 6px;
535
+ right: 6px;
536
+ font-size: 11px;
537
+ padding: 3px 8px;
538
+ border-radius: 6px;
539
+ border: 1px solid var(--border);
540
+ background: var(--raised-translucent);
541
+ color: var(--text-dim);
542
+ cursor: pointer;
543
+ opacity: 0;
544
+ transition: opacity .12s;
545
+ }
546
+
547
+ .md-table:hover .copy { opacity: 1; }
548
+ .md-table .copy:hover { color: var(--text); border-color: var(--border-strong); }
549
+
550
+ /* ---------- 空态 ---------- */
551
+
552
+ .empty {
553
+ max-width: 620px;
554
+ margin: 8vh auto 0;
555
+ padding: 0 20px;
556
+ text-align: center;
557
+ color: var(--text-dim);
558
+ }
559
+
560
+ .empty h2 { color: var(--text); font-size: 20px; margin: 0 0 8px; }
561
+ .empty p { margin: 0 0 20px; font-size: 13px; }
562
+ .empty code { font-family: var(--mono); font-size: 12px; color: var(--accent); }
563
+
564
+ .chips {
565
+ display: flex;
566
+ flex-wrap: wrap;
567
+ gap: 8px;
568
+ justify-content: center;
569
+ }
570
+
571
+ .chip {
572
+ background: var(--bg-soft);
573
+ border: 1px solid var(--border);
574
+ color: var(--text-dim);
575
+ border-radius: 999px;
576
+ padding: 6px 14px;
577
+ font-size: 12.5px;
578
+ cursor: pointer;
579
+ transition: .12s;
580
+ }
581
+
582
+ .chip:hover { color: var(--text); border-color: var(--border-strong); background: var(--bg-raised); }
583
+
584
+ /* ---------- 输入区 ---------- */
585
+
586
+ .composer { padding: 8px 20px 18px; }
587
+
588
+ .composer-box {
589
+ max-width: 900px;
590
+ margin: 0 auto;
591
+ background: var(--bg-soft);
592
+ border: 1px solid var(--border);
593
+ border-radius: 14px;
594
+ padding: 10px 12px 8px;
595
+ transition: border-color .15s;
596
+ }
597
+
598
+ .composer-box:focus-within { border-color: var(--border-focus); }
599
+
600
+ #input {
601
+ width: 100%;
602
+ max-height: 220px;
603
+ min-height: 24px;
604
+ resize: none;
605
+ border: none;
606
+ outline: none;
607
+ background: none;
608
+ color: var(--text);
609
+ font: inherit;
610
+ line-height: 1.6;
611
+ }
612
+
613
+ #input::placeholder { color: var(--text-faint); }
614
+
615
+ .composer-bar {
616
+ display: flex;
617
+ align-items: center;
618
+ justify-content: space-between;
619
+ gap: 10px;
620
+ margin-top: 6px;
621
+ }
622
+
623
+ .hint {
624
+ font-size: 12px;
625
+ color: var(--text-faint);
626
+ min-width: 0;
627
+ overflow: hidden;
628
+ text-overflow: ellipsis;
629
+ white-space: nowrap;
630
+ }
631
+
632
+ .hint.err { color: var(--danger); }
633
+ .hint.ok { color: var(--ok); }
634
+
635
+ .composer-buttons { display: flex; gap: 8px; flex: none; }
636
+
637
+ /* ---------- 按钮 ---------- */
638
+
639
+ .btn {
640
+ font: inherit;
641
+ font-size: 13px;
642
+ border-radius: var(--radius-sm);
643
+ border: 1px solid var(--border);
644
+ background: var(--bg-raised);
645
+ color: var(--text);
646
+ padding: 6px 14px;
647
+ cursor: pointer;
648
+ transition: background-color var(--t), border-color var(--t), color var(--t), transform var(--t), box-shadow var(--t);
649
+ }
650
+
651
+ .btn:hover:not(:disabled) { border-color: var(--border-strong); }
652
+
653
+ /* 按下时轻微回弹,比单纯的变色更有反馈 */
654
+ .btn:active:not(:disabled) { transform: translateY(1px); }
655
+
656
+ .btn:disabled { opacity: .45; cursor: not-allowed; }
657
+
658
+ /* 键盘导航才显示焦点环,鼠标点击不显示(避免“点了也有一圈描边”的脏感) */
659
+ .btn:focus-visible,
660
+ .chip:focus-visible,
661
+ .session-item:focus-visible,
662
+ .theme-btn:focus-visible,
663
+ .mode-btn:focus-visible {
664
+ outline: 2px solid var(--accent);
665
+ outline-offset: 2px;
666
+ }
667
+
668
+ .btn.primary {
669
+ background: var(--accent);
670
+ border-color: var(--accent);
671
+ color: var(--on-accent);
672
+ box-shadow: var(--shadow-xs);
673
+ }
674
+ .btn.primary:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }
675
+
676
+ .btn.ghost { background: transparent; color: var(--text-dim); }
677
+ .btn.ghost:hover:not(:disabled) { color: var(--text); background: var(--bg-raised); }
678
+
679
+ /* ---------- 主题切换(跟随系统 / 白天 / 黑夜) ---------- */
680
+
681
+ .theme-switch {
682
+ display: inline-flex;
683
+ align-items: center;
684
+ gap: 2px;
685
+ padding: 2px;
686
+ border: 1px solid var(--border);
687
+ border-radius: var(--radius-sm);
688
+ background: var(--bg-soft);
689
+ }
690
+
691
+ .theme-btn {
692
+ font: inherit;
693
+ font-size: 13px;
694
+ line-height: 1;
695
+ width: 26px;
696
+ height: 24px;
697
+ display: inline-flex;
698
+ align-items: center;
699
+ justify-content: center;
700
+ padding: 0;
701
+ border: 0;
702
+ border-radius: 6px;
703
+ background: transparent;
704
+ color: var(--text-faint);
705
+ cursor: pointer;
706
+ transition: background-color var(--t), color var(--t);
707
+ }
708
+
709
+ .theme-btn:hover { color: var(--text); background: var(--bg-raised); }
710
+
711
+ /* 当前生效的那个高亮;没选过时高亮“跟随系统” */
712
+ .theme-btn.active { background: var(--accent-soft); color: var(--accent); }
713
+
714
+ /* 外部集成传了底色(?bg=…,见 tint.js)时颜色由宿主决定,这个三挡开关就没意义了:
715
+ 在这儿切出来的浅色主题压在你给的深底上还会瞎眼,所以直接收起来 */
716
+ html[data-tint] .theme-switch { display: none; }
717
+
718
+ /* ---------- 设置面板 ---------- */
719
+
720
+ .settings {
721
+ /* 380 是默认宽度:不够就拖左边那条把手往左拉,宽度记在浏览器里(见 wireSettingsResizer) */
722
+ width: 380px;
723
+ position: relative;
724
+ flex: none;
725
+ border-left: 1px solid var(--border);
726
+ background: var(--bg-soft);
727
+ overflow: hidden; /* 滚动交给 .settings-body:这样拖拽把手不会被内容滚走 */
728
+ display: none;
729
+ }
730
+
731
+ .settings.open { display: flex; flex-direction: column; }
732
+
733
+ /* 面板内容的滚动容器(wireSettingsResizer 里建的,两页共用) */
734
+ .settings-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 16px; }
735
+
736
+ /* 往左拖变宽:左边缘那条 10px 的隐形抓手上,hover / 拖动时亮一条线 */
737
+ .settings-resizer {
738
+ position: absolute;
739
+ left: 0;
740
+ top: 0;
741
+ bottom: 0;
742
+ width: 10px;
743
+ cursor: col-resize;
744
+ touch-action: none; /* 触屏上拖动时别被页面滚动抢走 */
745
+ }
746
+
747
+ .settings-resizer::after {
748
+ content: '';
749
+ position: absolute;
750
+ left: 0;
751
+ top: 0;
752
+ bottom: 0;
753
+ width: 1px;
754
+ background: transparent;
755
+ transition: background-color var(--t), width var(--t);
756
+ }
757
+
758
+ .settings-resizer:hover::after, .settings-resizer.dragging::after { background: var(--border-focus); width: 2px; }
759
+
760
+ .settings-head {
761
+ display: flex;
762
+ align-items: center;
763
+ justify-content: space-between;
764
+ margin-bottom: 16px;
765
+ }
766
+
767
+ .settings-head h2 { font-size: 14px; margin: 0; }
768
+
769
+ .field { display: block; margin-bottom: 14px; }
770
+ .field > span { display: block; font-size: 12px; color: var(--text-dim); margin-bottom: 5px; }
771
+ .field small { display: block; font-size: 11px; color: var(--text-faint); margin-top: 5px; }
772
+
773
+ .field input, .field textarea, .field select {
774
+ width: 100%;
775
+ background: var(--bg);
776
+ border: 1px solid var(--border);
777
+ border-radius: 8px;
778
+ color: var(--text);
779
+ font: inherit;
780
+ font-size: 13px;
781
+ padding: 7px 10px;
782
+ outline: none;
783
+ resize: vertical;
784
+ }
785
+
786
+ /* 模型下拉是"只可选"的控件:光标给 pointer,别让它看着像能打字 */
787
+ .field select { resize: none; cursor: pointer; }
788
+
789
+ .field input:focus, .field textarea:focus, .field select:focus { border-color: var(--border-focus); }
790
+
791
+ .row { display: flex; gap: 10px; }
792
+ .row .field { flex: 1; }
793
+
794
+ .check {
795
+ display: flex;
796
+ align-items: center;
797
+ gap: 8px;
798
+ font-size: 13px;
799
+ color: var(--text-dim);
800
+ margin-bottom: 16px;
801
+ cursor: pointer;
802
+ }
803
+
804
+ .check input { accent-color: var(--accent); }
805
+
806
+ .settings-actions { display: flex; gap: 8px; margin-bottom: 18px; }
807
+
808
+ /* 「其他配置」:由 /api/settings 的白名单渲染,所以样式按通用控件写(select / 徽标) */
809
+ .settings-group {
810
+ border-top: 1px solid var(--border);
811
+ padding-top: 14px;
812
+ margin-bottom: 18px;
813
+ }
814
+
815
+ .settings-group > summary {
816
+ cursor: pointer;
817
+ font-size: 12px;
818
+ font-weight: 600;
819
+ color: var(--text-dim);
820
+ margin-bottom: 12px;
821
+ }
822
+
823
+ .settings-group > summary small { font-weight: 400; color: var(--text-faint); margin-left: 6px; }
824
+
825
+ .settings-group .field { margin-bottom: 12px; }
826
+
827
+ /* 说明 + 键名 + 徽标一行放不下就换行:不换行会被 flex 撑破面板宽度,
828
+ 长说明(如 ui.persist / compat.legacyTaskApi)就会把右侧内容顶到看不见的地方 */
829
+ .settings-group .field > span,
830
+ .settings-group .check {
831
+ display: flex;
832
+ flex-wrap: wrap;
833
+ align-items: center;
834
+ gap: 6px;
835
+ min-width: 0;
836
+ }
837
+
838
+ .settings-group .check { align-items: flex-start; margin-bottom: 12px; }
839
+ .settings-group .check span { flex: 1; min-width: 0; overflow-wrap: anywhere; }
840
+
841
+ .cfg-badges { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; }
842
+
843
+ .settings-group select {
844
+ width: 100%;
845
+ background: var(--bg);
846
+ border: 1px solid var(--border);
847
+ border-radius: 8px;
848
+ color: var(--text);
849
+ font: inherit;
850
+ font-size: 13px;
851
+ padding: 7px 10px;
852
+ outline: none;
853
+ }
854
+
855
+ .settings-group select:focus { border-color: var(--border-focus); }
856
+
857
+ .cfg-badge {
858
+ font-size: 10px;
859
+ font-weight: 400;
860
+ padding: 1px 6px;
861
+ border-radius: 999px;
862
+ border: 1px solid var(--border);
863
+ color: var(--text-faint);
864
+ white-space: nowrap;
865
+ }
866
+
867
+ .cfg-badge.restart { color: var(--warn); border-color: var(--warn-border); background: var(--warn-soft); }
868
+
869
+ .cfg-badge.source-file { color: var(--accent); border-color: currentColor; }
870
+
871
+ .cfg-key { font-size: 10px; color: var(--text-faint); font-family: var(--mono); overflow-wrap: anywhere; }
872
+
873
+ .info {
874
+ border-top: 1px solid var(--border);
875
+ padding-top: 14px;
876
+ display: grid;
877
+ gap: 8px;
878
+ }
879
+
880
+ /* 信息行:标签在左、值紧跟在标签右边成列排(**不靠右对齐** —— 靠右时长值会被顶出面板,
881
+ 而且值离标签太远不好对应);标签统一给一个最小宽度,几行的值才能对齐成一列 */
882
+ .info-row { display: flex; justify-content: flex-start; gap: 8px; font-size: 12px; }
883
+ .info-row span { color: var(--text-faint); flex: none; min-width: 4.5em; }
884
+ .info-row code {
885
+ font-family: var(--mono);
886
+ font-size: 11.5px;
887
+ color: var(--text-dim);
888
+ /* flex 项的 min-width 默认是 auto:不写这一行,下面那三行省略号永远不会生效,
889
+ 长值(可用工具清单、网关地址)会把面板内容顶出右边界 */
890
+ min-width: 0;
891
+ flex: 1 1 auto;
892
+ text-align: left;
893
+ overflow: hidden;
894
+ text-overflow: ellipsis;
895
+ white-space: nowrap;
896
+ }
897
+
898
+ /* ---------- 滚动条 ---------- */
899
+
900
+ ::-webkit-scrollbar { width: 9px; height: 9px; }
901
+ ::-webkit-scrollbar-thumb { background: var(--scrollbar); border-radius: 6px; }
902
+ ::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-hover); }
903
+ ::-webkit-scrollbar-track { background: transparent; }
904
+
905
+ @media (min-width: 821px) {
906
+ /* 桌面端用 .collapsed 收起侧边栏,把宽度让给对话区;移动端见下面的 .open */
907
+ .sidebar.collapsed { display: none; }
908
+ }
909
+
910
+ @media (max-width: 820px) {
911
+ .sidebar {
912
+ position: fixed;
913
+ inset: 0 auto 0 0;
914
+ z-index: 12;
915
+ width: min(80vw, 280px);
916
+ transform: translateX(-100%);
917
+ transition: transform .18s ease;
918
+ box-shadow: var(--shadow-strong);
919
+ }
920
+ .sidebar.open { transform: none; }
921
+ .settings { position: fixed; inset: 0 0 0 auto; width: min(92vw, 420px); z-index: 10; box-shadow: var(--shadow); }
922
+ .meta { display: none; }
923
+ }
924
+
925
+ /* ---------- 细节打磨 ---------- */
926
+
927
+ /* 选中文字用主题色,深浅两套下都能看清 */
928
+ ::selection { background: var(--accent-soft); color: var(--text); }
929
+
930
+ /* 滚动条角落那块空白,不处理会在深色下露出浅色方块 */
931
+ ::-webkit-scrollbar-corner { background: transparent; }
932
+
933
+ /* 会话条目:悬停/选中都加一点过渡,避免生硬跳变 */
934
+ .session-item {
935
+ transition: background-color var(--t), box-shadow var(--t);
936
+ }
937
+ .session-item.active { box-shadow: inset 2px 0 0 var(--accent); }
938
+
939
+ /* 快捷提示按钮 */
940
+ .chip {
941
+ transition: background-color var(--t), border-color var(--t), color var(--t), transform var(--t);
942
+ }
943
+ .chip:hover { transform: translateY(-1px); }
944
+
945
+ /* 输入类控件统一过渡 */
946
+ .field input, .field textarea, .composer-box {
947
+ transition: border-color var(--t), box-shadow var(--t), background-color var(--t);
948
+ }
949
+
950
+ /* 顶栏按钮在窄屏下收成图标,主题切换始终保留 */
951
+ @media (max-width: 560px) {
952
+ .topbar-actions { gap: 4px; }
953
+ .theme-btn { width: 24px; }
954
+ }
955
+