tavern-harness 0.1.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 (56) hide show
  1. package/README.md +161 -0
  2. package/README.zh.md +123 -0
  3. package/bin/cli.js +48 -0
  4. package/index.html +35 -0
  5. package/package.json +50 -0
  6. package/public/favicon.png +0 -0
  7. package/public/tavern-harness-logo.png +0 -0
  8. package/sandbox-server.mjs +997 -0
  9. package/src/App.tsx +268 -0
  10. package/src/assets/tavern-handbook.md +39 -0
  11. package/src/components/AchievementModal.tsx +158 -0
  12. package/src/components/CharactersView.tsx +904 -0
  13. package/src/components/ChatView.tsx +1639 -0
  14. package/src/components/ConfirmationDialog.tsx +56 -0
  15. package/src/components/Dashboard.tsx +180 -0
  16. package/src/components/DeleteConfirmDialog.tsx +48 -0
  17. package/src/components/FileDisplayModal.tsx +556 -0
  18. package/src/components/GameplayDialogs.tsx +191 -0
  19. package/src/components/NewSessionMenu.tsx +362 -0
  20. package/src/components/SettingsView.tsx +517 -0
  21. package/src/components/Sidebar.tsx +392 -0
  22. package/src/components/StatsView.tsx +189 -0
  23. package/src/components/Toasts.tsx +20 -0
  24. package/src/components/WorkspaceFileManagerModal.tsx +216 -0
  25. package/src/components/shared.tsx +369 -0
  26. package/src/core/achievements.ts +85 -0
  27. package/src/core/fileDownload.ts +67 -0
  28. package/src/core/gameplay.ts +521 -0
  29. package/src/core/horizontalWheel.ts +93 -0
  30. package/src/core/i18n.ts +1403 -0
  31. package/src/core/linkedScroll.ts +236 -0
  32. package/src/core/markdown.ts +170 -0
  33. package/src/core/openai.ts +451 -0
  34. package/src/core/openingGreeting.ts +41 -0
  35. package/src/core/prompts.ts +254 -0
  36. package/src/core/proxy.ts +58 -0
  37. package/src/core/sillyTavernImporter.ts +317 -0
  38. package/src/core/stats.ts +91 -0
  39. package/src/core/strings.ts +19 -0
  40. package/src/core/toolDefinitions.ts +400 -0
  41. package/src/core/tools/builtinTools.ts +38 -0
  42. package/src/core/tools/generatedSkillExecutor.ts +706 -0
  43. package/src/core/tools/generatedWorkspace.ts +13 -0
  44. package/src/core/tools/toolExecutor.ts +718 -0
  45. package/src/core/turnLoop.ts +211 -0
  46. package/src/db/database.ts +647 -0
  47. package/src/main.tsx +23 -0
  48. package/src/store/store.ts +2419 -0
  49. package/src/theme/chat.css +1227 -0
  50. package/src/theme/global.css +1377 -0
  51. package/src/theme/theme.ts +143 -0
  52. package/src/theme/views.css +1430 -0
  53. package/src/types/models.ts +364 -0
  54. package/src/vite-env.d.ts +1 -0
  55. package/tsconfig.json +21 -0
  56. package/vite.config.ts +273 -0
@@ -0,0 +1,1227 @@
1
+ /* ============================================================
2
+ 聊天视图 / 消息气泡 / 输入框 / Markdown 增强
3
+ ============================================================ */
4
+
5
+ /* ---------- 消息行 ---------- */
6
+ .msg-row {
7
+ display: flex;
8
+ gap: 11px;
9
+ align-items: flex-start;
10
+ }
11
+ .msg-row.user-row {
12
+ flex-direction: row-reverse;
13
+ }
14
+ .msg-body {
15
+ flex: 1;
16
+ min-width: 0;
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: 5px;
20
+ }
21
+ .user-row .msg-body {
22
+ align-items: flex-end;
23
+ }
24
+ .msg-head {
25
+ display: flex;
26
+ align-items: baseline;
27
+ gap: 8px;
28
+ padding: 0 4px;
29
+ }
30
+ .msg-head.right {
31
+ flex-direction: row-reverse;
32
+ }
33
+ .msg-name {
34
+ font-weight: 700;
35
+ font-size: 12.5px;
36
+ font-family: var(--font-serif);
37
+ letter-spacing: 0.4px;
38
+ color: var(--text-dim);
39
+ }
40
+ .msg-model {
41
+ font-size: 10.5px;
42
+ font-family: var(--font-mono);
43
+ color: var(--text-faint);
44
+ background: var(--surface-2);
45
+ border: 1px solid var(--border-soft);
46
+ padding: 1px 6px;
47
+ border-radius: 6px;
48
+ max-width: 220px;
49
+ overflow: hidden;
50
+ text-overflow: ellipsis;
51
+ white-space: nowrap;
52
+ }
53
+ .msg-time {
54
+ font-size: 10.5px;
55
+ color: var(--text-faint);
56
+ font-family: var(--font-mono);
57
+ }
58
+ .user-avatar {
59
+ background: var(--grad-from);
60
+ }
61
+
62
+ .bubble {
63
+ background: var(--bubble-npc);
64
+ border: 1px solid var(--bubble-border);
65
+ border-radius: var(--radius);
66
+ border-top-left-radius: 4px;
67
+ padding: 11px 15px;
68
+ font-size: 14px;
69
+ box-shadow: var(--shadow);
70
+ min-width: 0;
71
+ max-width: 100%;
72
+ word-break: break-word;
73
+ }
74
+ /* 消息内容带宽:NPC 气泡恒定全宽,用户气泡右对齐,右边缘平齐。
75
+ - NPC 侧:msg-body 起点在头像(40px)+gap(11px)=51px 处;气泡恒定宽 = msg-body 宽 - 43px,
76
+ 右边缘固定于 W-43px(用户头像占位 32+11=43 的最外侧)
77
+ - 用户侧:msg-body 终点在 W-43px;气泡右对齐(右边缘恒为 W-43px,与 NPC 平齐),
78
+ 最长时左边缘到达 NPC 头像位 51px
79
+ - 效果:任意时刻 NPC 右边缘 == 用户右边缘;用户长气泡左边缘 == NPC 左边缘 */
80
+ .msg-row:not(.user-row) .msg-body > .bubble,
81
+ .msg-row:not(.user-row) .msg-body > .tool-calls,
82
+ .msg-row:not(.user-row) .msg-body > .collp,
83
+ .msg-row:not(.user-row) .msg-body > .msg-metrics {
84
+ width: calc(100% - 43px);
85
+ max-width: none;
86
+ align-self: flex-start;
87
+ }
88
+ .msg-row.user-row .msg-body > .bubble,
89
+ .msg-row.user-row .msg-body > .tool-calls,
90
+ .msg-row.user-row .msg-body > .collp,
91
+ .msg-row.user-row .msg-body > .msg-metrics {
92
+ max-width: calc(100% - 51px);
93
+ align-self: flex-end;
94
+ }
95
+ .bubble-user {
96
+ background: var(--bubble-user);
97
+ border-color: var(--bubble-border-user);
98
+ border-radius: var(--radius);
99
+ border-top-right-radius: 4px;
100
+ }
101
+ .msg-row.streaming .bubble {
102
+ position: relative;
103
+ z-index: 1;
104
+ border-color: color-mix(in srgb, var(--primary) 44%, transparent);
105
+ box-shadow:
106
+ inset 0 0 0 1px color-mix(in srgb, var(--primary) 10%, transparent),
107
+ inset 0 0 8px color-mix(in srgb, var(--glow) 16%, transparent),
108
+ 0 0 0 1px color-mix(in srgb, var(--primary) 20%, transparent),
109
+ 0 0 8px color-mix(in srgb, var(--glow) 34%, transparent),
110
+ 0 0 16px color-mix(in srgb, var(--glow) 12%, transparent);
111
+ }
112
+ .stream-cursor {
113
+ display: inline-block;
114
+ width: 7px;
115
+ height: 15px;
116
+ background: var(--primary);
117
+ border-radius: 2px;
118
+ margin-left: 2px;
119
+ vertical-align: -2px;
120
+ animation: pulse 0.8s infinite;
121
+ }
122
+ .msg-metrics {
123
+ font-size: 10.5px;
124
+ font-family: var(--font-mono);
125
+ color: var(--text-faint);
126
+ padding: 0 4px;
127
+ letter-spacing: 0.2px;
128
+ }
129
+
130
+ /* ---------- 气泡编辑按钮(常驻显示,位于气泡内最右侧) ---------- */
131
+ .bubble-content-row {
132
+ display: flex;
133
+ align-items: flex-start;
134
+ justify-content: space-between;
135
+ gap: 8px;
136
+ }
137
+ .bubble-content-row .bubble-text {
138
+ flex: 1;
139
+ min-width: 0;
140
+ }
141
+ .msg-edit-btn {
142
+ display: inline-flex;
143
+ align-items: center;
144
+ justify-content: center;
145
+ width: 24px;
146
+ height: 24px;
147
+ flex-shrink: 0;
148
+ margin-top: 1px;
149
+ border: none;
150
+ border-radius: 7px;
151
+ background: none;
152
+ color: var(--text-faint);
153
+ cursor: pointer;
154
+ transition: background 0.15s, color 0.15s;
155
+ }
156
+ .msg-edit-btn:hover {
157
+ background: var(--surface-2);
158
+ color: var(--primary);
159
+ }
160
+
161
+ /* ---------- 气泡内联编辑 ---------- */
162
+ .bubble-editor {
163
+ display: flex;
164
+ flex-direction: column;
165
+ gap: 8px;
166
+ width: 100%;
167
+ max-width: 640px;
168
+ }
169
+ .bubble-editor textarea {
170
+ width: 100%;
171
+ border: 1px solid color-mix(in srgb, var(--primary) 48%, transparent);
172
+ box-shadow:
173
+ inset 0 0 0 1px color-mix(in srgb, var(--primary) 10%, transparent),
174
+ inset 0 0 8px color-mix(in srgb, var(--glow) 18%, transparent),
175
+ 0 0 0 1px color-mix(in srgb, var(--primary) 20%, transparent),
176
+ 0 0 8px color-mix(in srgb, var(--glow) 36%, transparent),
177
+ 0 0 14px color-mix(in srgb, var(--glow) 14%, transparent);
178
+ font-size: 13.5px;
179
+ }
180
+ .bubble-editor-actions {
181
+ display: flex;
182
+ align-items: center;
183
+ justify-content: space-between;
184
+ gap: 10px;
185
+ }
186
+ .bubble-editor-hint {
187
+ font-size: 10.5px;
188
+ color: var(--text-faint);
189
+ }
190
+
191
+ .sys-banner {
192
+ align-self: center;
193
+ font-size: 11.5px;
194
+ color: var(--text-faint);
195
+ background: var(--surface-2);
196
+ border: 1px dashed var(--border);
197
+ padding: 4px 14px;
198
+ border-radius: 999px;
199
+ max-width: 80%;
200
+ }
201
+ .compression-banner-wrap {
202
+ display: flex;
203
+ justify-content: center;
204
+ width: 100%;
205
+ }
206
+ .compression-summary-body {
207
+ padding: 20px;
208
+ max-height: min(65vh, 620px);
209
+ overflow: auto;
210
+ color: var(--text);
211
+ }
212
+
213
+ /* ---------- 附件 ---------- */
214
+ .attachments {
215
+ display: flex;
216
+ flex-wrap: wrap;
217
+ gap: 8px;
218
+ margin-bottom: 8px;
219
+ }
220
+ .attach-card {
221
+ display: inline-flex;
222
+ align-items: center;
223
+ gap: 7px;
224
+ max-width: 220px;
225
+ padding: 6px 10px;
226
+ border: 1px solid var(--border);
227
+ border-radius: 10px;
228
+ background: var(--surface-2);
229
+ font-size: 12.5px;
230
+ color: var(--text-dim);
231
+ }
232
+ .attach-card-icon {
233
+ font-size: 13px;
234
+ flex-shrink: 0;
235
+ }
236
+ .attach-card-name {
237
+ flex: 1;
238
+ min-width: 0;
239
+ white-space: nowrap;
240
+ overflow: hidden;
241
+ text-overflow: ellipsis;
242
+ font-weight: 600;
243
+ }
244
+
245
+ /* ---------- 工具调用卡片 ---------- */
246
+ .tool-calls {
247
+ display: flex;
248
+ flex-direction: column;
249
+ gap: 6px;
250
+ margin: 2px 0;
251
+ }
252
+ .tool-card {
253
+ border: 1px solid var(--border);
254
+ border-radius: 10px;
255
+ background: var(--surface-2);
256
+ overflow: hidden;
257
+ }
258
+ .tool-card-head {
259
+ display: flex;
260
+ align-items: center;
261
+ gap: 8px;
262
+ width: 100%;
263
+ padding: 7px 12px;
264
+ border: none;
265
+ background: none;
266
+ color: var(--text-dim);
267
+ font-size: 12.5px;
268
+ font-weight: 600;
269
+ text-align: left;
270
+ cursor: pointer;
271
+ }
272
+ .tool-card-head:hover {
273
+ background: var(--surface-3);
274
+ }
275
+ .tool-card.executing .tool-card-head {
276
+ color: var(--primary);
277
+ }
278
+ .tool-card-icon {
279
+ font-size: 13px;
280
+ line-height: 1;
281
+ display: inline-flex;
282
+ align-items: center;
283
+ }
284
+ .tool-card-name {
285
+ flex: 1;
286
+ min-width: 0;
287
+ white-space: nowrap;
288
+ overflow: hidden;
289
+ text-overflow: ellipsis;
290
+ }
291
+ .tool-args {
292
+ margin: 0;
293
+ padding: 8px 14px;
294
+ font-size: 11px;
295
+ line-height: 1.45;
296
+ background: var(--code-bg);
297
+ border-top: 1px solid var(--border-soft);
298
+ white-space: pre-wrap;
299
+ word-break: break-all;
300
+ max-height: 220px;
301
+ overflow: auto;
302
+ }
303
+ /* 工具卡片展开区:参数 + 结果垂直排列 */
304
+ .tool-card-body {
305
+ border-top: 1px solid var(--border-soft);
306
+ }
307
+ .tool-card-body .tool-result {
308
+ border: none;
309
+ border-radius: 0;
310
+ padding: 7px 14px;
311
+ /* 让结果行右侧的「查看」按钮垂直居中(距离上下边缘等距) */
312
+ align-items: center;
313
+ }
314
+ /* 未展开时展示的工具调用结果摘要行 */
315
+ .tool-card-result-line {
316
+ display: flex;
317
+ align-items: flex-start;
318
+ gap: 6px;
319
+ padding: 6px 12px 7px;
320
+ border-top: 1px solid var(--border-soft);
321
+ font-size: 12px;
322
+ font-weight: 600;
323
+ color: var(--success);
324
+ background: color-mix(in srgb, var(--success) 8%, transparent);
325
+ word-break: break-all;
326
+ }
327
+ .tool-card-result-line.err {
328
+ color: var(--danger);
329
+ background: color-mix(in srgb, var(--danger) 8%, transparent);
330
+ }
331
+ .tool-card.has-result .tool-card-name,
332
+ .tool-card.has-result .tool-card-icon {
333
+ color: var(--success);
334
+ }
335
+ .tool-card.has-error .tool-card-name,
336
+ .tool-card.has-error .tool-card-icon {
337
+ color: var(--danger);
338
+ }
339
+ .tool-result {
340
+ display: flex;
341
+ align-items: flex-start;
342
+ gap: 7px;
343
+ font-size: 12px;
344
+ font-weight: 600;
345
+ color: var(--success);
346
+ background: var(--success-soft);
347
+ border: 1px solid color-mix(in srgb, var(--success) 25%, transparent);
348
+ border-radius: 10px;
349
+ padding: 7px 13px;
350
+ width: 100%;
351
+ word-break: break-all;
352
+ }
353
+ /* 展开态工具结果:保留换行与缩进 */
354
+ .tool-result .mono {
355
+ white-space: pre-wrap;
356
+ }
357
+ /* 折叠态摘要行:强制单行省略 */
358
+ .tool-card-result-line .mono {
359
+ white-space: nowrap;
360
+ overflow: hidden;
361
+ text-overflow: ellipsis;
362
+ flex: 1;
363
+ min-width: 0;
364
+ }
365
+ .tool-result.err {
366
+ color: var(--danger);
367
+ background: var(--danger-soft);
368
+ border-color: color-mix(in srgb, var(--danger) 28%, transparent);
369
+ }
370
+ /* 工具结果:无头像,左移对齐气泡起点(头像 32px + gap 11px) */
371
+ .tool-row {
372
+ padding-left: 43px;
373
+ }
374
+
375
+ /* ---------- 折叠面板(thinking / 工具结果) ---------- */
376
+ /* 与工具调用展示框(tool-card)保持一致的视觉:字号、颜色、展开方式 */
377
+ .collp {
378
+ border: 1px solid var(--border);
379
+ border-radius: 10px;
380
+ background: var(--surface-2);
381
+ overflow: hidden;
382
+ margin: 2px 0;
383
+ }
384
+ .collp-head {
385
+ display: flex;
386
+ align-items: center;
387
+ gap: 8px;
388
+ width: 100%;
389
+ padding: 7px 12px;
390
+ border: none;
391
+ background: none;
392
+ color: var(--text-dim);
393
+ font-size: 12.5px;
394
+ font-weight: 600;
395
+ text-align: left;
396
+ cursor: pointer;
397
+ }
398
+ .collp-head:hover {
399
+ background: var(--surface-3);
400
+ }
401
+ .collp-icon {
402
+ font-size: 13px;
403
+ }
404
+ .collp-title {
405
+ flex: 1;
406
+ }
407
+ .collp-arrow {
408
+ font-size: 11px;
409
+ color: var(--text-faint);
410
+ transition: transform 0.18s;
411
+ }
412
+ .collp-body {
413
+ border-top: 1px solid var(--border-soft);
414
+ padding: 0;
415
+ }
416
+ .collp-content {
417
+ max-height: 240px;
418
+ overflow: auto;
419
+ padding: 8px 14px;
420
+ font-size: 11px;
421
+ line-height: 1.6;
422
+ color: var(--text-dim);
423
+ background: var(--code-bg);
424
+ word-break: break-word;
425
+ }
426
+ .collp-content .md {
427
+ font-size: 11px;
428
+ line-height: 1.6;
429
+ color: var(--text-dim);
430
+ }
431
+ .collp-preview {
432
+ padding: 5px 12px 8px;
433
+ font-size: 12px;
434
+ color: var(--text-faint);
435
+ white-space: nowrap;
436
+ overflow: hidden;
437
+ text-overflow: ellipsis;
438
+ }
439
+ /* 流式预览:只显示最新一行,超出省略 */
440
+ .collp-preview.live {
441
+ white-space: nowrap;
442
+ }
443
+ .collp-live {
444
+ display: inline-flex;
445
+ }
446
+ .collp-more {
447
+ padding: 5px 12px 7px;
448
+ border-top: 1px solid var(--border-soft);
449
+ color: var(--text-faint);
450
+ font-size: 10.5px;
451
+ text-align: center;
452
+ }
453
+
454
+ /* ---------- 输入区 ---------- */
455
+ .composer {
456
+ padding: 8px 22px 12px;
457
+ border-top: 1px solid var(--border);
458
+ background: color-mix(in srgb, var(--surface) 62%, transparent);
459
+ backdrop-filter: blur(16px);
460
+ }
461
+ .composer-box {
462
+ position: relative;
463
+ z-index: 1;
464
+ display: flex;
465
+ align-items: flex-end;
466
+ gap: 8px;
467
+ max-width: 820px;
468
+ margin: 0 auto;
469
+ background: var(--surface);
470
+ border: 1px solid var(--border);
471
+ border-radius: 14px;
472
+ padding: 4px 6px 4px 6px;
473
+ box-shadow: 0 0 0 1px transparent, var(--shadow);
474
+ transition: border-color 0.32s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.32s cubic-bezier(0.16, 1, 0.3, 1);
475
+ }
476
+ .composer-box:focus-within {
477
+ border-color: color-mix(in srgb, var(--primary) 48%, transparent);
478
+ box-shadow:
479
+ inset 0 0 0 1px color-mix(in srgb, var(--primary) 12%, transparent),
480
+ inset 0 0 10px color-mix(in srgb, var(--glow) 22%, transparent),
481
+ 0 0 0 1px color-mix(in srgb, var(--primary) 26%, transparent),
482
+ 0 0 10px color-mix(in srgb, var(--glow) 42%, transparent),
483
+ 0 0 20px color-mix(in srgb, var(--glow) 18%, transparent);
484
+ }
485
+ .composer-box textarea {
486
+ flex: 1;
487
+ border: none;
488
+ outline: none;
489
+ background: none;
490
+ resize: none;
491
+ padding: 7px 4px;
492
+ font-size: 14px;
493
+ color: var(--text);
494
+ line-height: 1.5;
495
+ }
496
+ .composer-box textarea::placeholder {
497
+ color: var(--text-faint);
498
+ }
499
+ /* 输入框两端的图标按钮:与发送按钮等大(40×40) */
500
+ .composer-box .composer-attach {
501
+ width: 40px;
502
+ height: 40px;
503
+ border-radius: 12px;
504
+ flex-shrink: 0;
505
+ }
506
+ .composer-box .composer-model-btn {
507
+ display: inline-flex;
508
+ align-items: center;
509
+ gap: 5px;
510
+ height: 40px;
511
+ max-width: 180px;
512
+ padding: 0 10px;
513
+ border-radius: 12px;
514
+ flex-shrink: 0;
515
+ background: var(--surface-2);
516
+ border: 1px solid var(--border-soft);
517
+ color: var(--primary);
518
+ font-size: 12px;
519
+ font-weight: 600;
520
+ transition: background 0.32s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.32s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.32s cubic-bezier(0.16, 1, 0.3, 1);
521
+ }
522
+ .composer-box .composer-model-btn:hover {
523
+ background: color-mix(in srgb, var(--primary) 8%, transparent);
524
+ border-color: color-mix(in srgb, var(--primary) 28%, transparent);
525
+ }
526
+ /* 弹层打开(激活态)才显示辉光 */
527
+ .composer-box .composer-model-btn.open {
528
+ background: var(--primary-soft);
529
+ border-color: color-mix(in srgb, var(--primary) 44%, transparent);
530
+ box-shadow:
531
+ 0 0 0 1px color-mix(in srgb, var(--primary) 22%, transparent),
532
+ 0 0 8px color-mix(in srgb, var(--glow) 38%, transparent),
533
+ 0 0 16px color-mix(in srgb, var(--glow) 16%, transparent);
534
+ }
535
+ .composer-model-label {
536
+ white-space: nowrap;
537
+ overflow: hidden;
538
+ text-overflow: ellipsis;
539
+ max-width: 120px;
540
+ }
541
+ .composer-model {
542
+ position: relative;
543
+ align-self: flex-end;
544
+ }
545
+ .btn-round {
546
+ border-radius: 12px;
547
+ width: 40px;
548
+ height: 40px;
549
+ padding: 0;
550
+ flex-shrink: 0;
551
+ }
552
+
553
+ /* ---------- 模型选择下拉 ---------- */
554
+ .model-popover {
555
+ position: absolute;
556
+ bottom: calc(100% + 10px);
557
+ left: 0;
558
+ width: min(320px, 78vw);
559
+ z-index: 60;
560
+ overflow: hidden;
561
+ display: flex;
562
+ flex-direction: column;
563
+ box-shadow: var(--shadow-lg);
564
+ }
565
+ .model-popover-head {
566
+ display: flex;
567
+ align-items: center;
568
+ justify-content: space-between;
569
+ padding: 9px 12px;
570
+ border-bottom: 1px solid var(--border-soft);
571
+ font-weight: 800;
572
+ font-size: 13px;
573
+ }
574
+ .model-popover-list {
575
+ max-height: 300px;
576
+ overflow-y: auto;
577
+ padding: 6px;
578
+ display: flex;
579
+ flex-direction: column;
580
+ gap: 2px;
581
+ }
582
+ .model-popover-empty {
583
+ padding: 14px;
584
+ font-size: 12.5px;
585
+ color: var(--text-faint);
586
+ line-height: 1.7;
587
+ }
588
+ .model-popover .model-option {
589
+ display: flex;
590
+ align-items: center;
591
+ gap: 9px;
592
+ width: 100%;
593
+ padding: 8px 10px;
594
+ border: none;
595
+ background: none;
596
+ border-radius: 8px;
597
+ color: var(--text);
598
+ font-size: 13px;
599
+ cursor: pointer;
600
+ text-align: left;
601
+ }
602
+ .model-popover .model-option:hover {
603
+ background: var(--surface-2);
604
+ }
605
+ .model-popover .model-option.active {
606
+ background: var(--primary-soft);
607
+ color: var(--primary);
608
+ box-shadow:
609
+ inset 0 0 8px color-mix(in srgb, var(--glow) 18%, transparent),
610
+ 0 0 0 1px color-mix(in srgb, var(--primary) 14%, transparent);
611
+ }
612
+ .model-option-dot {
613
+ font-size: 11px;
614
+ flex-shrink: 0;
615
+ width: 14px;
616
+ text-align: center;
617
+ color: var(--text-faint);
618
+ }
619
+ .model-option.active .model-option-dot {
620
+ color: var(--primary);
621
+ }
622
+ .model-option-name {
623
+ font-size: 12px;
624
+ white-space: nowrap;
625
+ overflow: hidden;
626
+ text-overflow: ellipsis;
627
+ }
628
+
629
+ .composer-hint {
630
+ display: flex;
631
+ justify-content: space-between;
632
+ max-width: 820px;
633
+ margin: 6px auto 0;
634
+ font-size: 10.5px;
635
+ color: var(--text-faint);
636
+ }
637
+ .composer-hint-right {
638
+ font-family: var(--font-mono);
639
+ }
640
+ .attach-preview {
641
+ display: flex;
642
+ flex-wrap: wrap;
643
+ gap: 8px;
644
+ max-width: 820px;
645
+ margin: 0 auto 8px;
646
+ }
647
+ .attach-preview .attach-card {
648
+ background: var(--surface);
649
+ }
650
+ .attach-rm {
651
+ position: absolute;
652
+ top: -6px;
653
+ right: -6px;
654
+ width: 18px;
655
+ height: 18px;
656
+ padding: 0;
657
+ border-radius: 50%;
658
+ border: none;
659
+ background: var(--danger);
660
+ color: #fff;
661
+ display: flex;
662
+ align-items: center;
663
+ justify-content: center;
664
+ cursor: pointer;
665
+ box-shadow: var(--shadow);
666
+ line-height: 1;
667
+ }
668
+ .attach-rm svg {
669
+ display: block;
670
+ margin: auto;
671
+ }
672
+ .attach-card .attach-rm {
673
+ position: static;
674
+ flex-shrink: 0;
675
+ margin-left: 2px;
676
+ }
677
+ .cmd-menu {
678
+ max-width: 820px;
679
+ margin: 0 auto 6px;
680
+ background: var(--surface);
681
+ border: 1px solid var(--border);
682
+ border-radius: 12px;
683
+ overflow: hidden;
684
+ box-shadow: var(--shadow-lg);
685
+ }
686
+ .cmd-item {
687
+ display: flex;
688
+ align-items: center;
689
+ gap: 14px;
690
+ width: 100%;
691
+ padding: 9px 14px;
692
+ border: none;
693
+ background: none;
694
+ color: var(--text);
695
+ cursor: pointer;
696
+ text-align: left;
697
+ }
698
+ .cmd-item:hover {
699
+ background: var(--surface-2);
700
+ }
701
+ .cmd-item.active {
702
+ background: var(--primary-soft);
703
+ box-shadow:
704
+ inset 0 0 8px color-mix(in srgb, var(--glow) 16%, transparent);
705
+ }
706
+ .cmd-item.active .cmd-text,
707
+ .cmd-item.active .cmd-desc {
708
+ color: var(--primary);
709
+ }
710
+ .cmd-text {
711
+ font-family: var(--font-mono);
712
+ font-weight: 700;
713
+ color: var(--primary);
714
+ width: 60px;
715
+ }
716
+ .cmd-desc {
717
+ font-size: 12.5px;
718
+ color: var(--text-dim);
719
+ }
720
+
721
+ /* ---------- Markdown 语义样式 ---------- */
722
+ .md {
723
+ line-height: 1.68;
724
+ word-break: break-word;
725
+ }
726
+ .md > *:first-child {
727
+ margin-top: 0;
728
+ }
729
+ .md > *:last-child {
730
+ margin-bottom: 0;
731
+ }
732
+ .md p {
733
+ margin: 0.35em 0;
734
+ }
735
+ .md h1,
736
+ .md h2,
737
+ .md h3,
738
+ .md h4,
739
+ .md h5,
740
+ .md h6 {
741
+ margin: 0.9em 0 0.4em;
742
+ font-weight: 800;
743
+ line-height: 1.3;
744
+ }
745
+ .md h1 { font-size: 1.45em; border-bottom: 1px solid var(--border-soft); padding-bottom: 0.25em; }
746
+ .md h2 { font-size: 1.28em; border-bottom: 1px solid var(--border-soft); padding-bottom: 0.2em; }
747
+ .md h3 { font-size: 1.15em; }
748
+ .md h4 { font-size: 1.05em; }
749
+ .md ul,
750
+ .md ol {
751
+ padding-left: 1.4em;
752
+ margin: 0.4em 0;
753
+ }
754
+ .md li {
755
+ margin: 0.2em 0;
756
+ }
757
+ .md li > ul,
758
+ .md li > ol {
759
+ margin: 0;
760
+ }
761
+ .md a {
762
+ color: var(--primary);
763
+ text-decoration: underline;
764
+ text-underline-offset: 2px;
765
+ }
766
+ .md a:hover {
767
+ opacity: 0.85;
768
+ }
769
+ .md blockquote {
770
+ margin: 0.5em 0;
771
+ padding: 2px 14px;
772
+ border-left: 3px solid var(--primary-dim);
773
+ color: var(--text-dim);
774
+ background: var(--primary-soft);
775
+ border-radius: 0 8px 8px 0;
776
+ }
777
+ .md hr {
778
+ border: none;
779
+ border-top: 1px solid var(--border);
780
+ margin: 1em 0;
781
+ }
782
+ .md code {
783
+ font-family: var(--font-mono);
784
+ font-size: 0.86em;
785
+ background: var(--code-bg-inline);
786
+ padding: 1.5px 6px;
787
+ border-radius: 6px;
788
+ color: var(--primary);
789
+ }
790
+ .md pre.code-block {
791
+ background: var(--code-bg);
792
+ border: 1px solid var(--border);
793
+ border-radius: 11px;
794
+ padding: 12px 14px;
795
+ overflow-x: auto;
796
+ margin: 0.6em 0;
797
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
798
+ }
799
+ .md pre.code-block code {
800
+ background: none;
801
+ padding: 0;
802
+ color: var(--text);
803
+ font-size: 12.5px;
804
+ line-height: 1.55;
805
+ }
806
+ .md .tok-kw { color: #cc7832; font-weight: 600; }
807
+ .md .tok-str { color: #6a8759; }
808
+ .md .tok-num { color: #6897bb; }
809
+ .md .tok-type { color: #a9b7c6; font-weight: 600; }
810
+ .md .tok-comment { color: var(--text-faint); font-style: italic; }
811
+ [data-theme='light'] .md .tok-kw { color: #7b3f00; }
812
+ [data-theme='light'] .md .tok-str { color: #2e7d32; }
813
+ [data-theme='light'] .md .tok-num { color: #1565c0; }
814
+ [data-theme='light'] .md .tok-type { color: #546e7a; }
815
+ .md table {
816
+ border-collapse: collapse;
817
+ margin: 0.6em 0;
818
+ width: 100%;
819
+ font-size: 0.92em;
820
+ }
821
+ .md th,
822
+ .md td {
823
+ border: 1px solid var(--border);
824
+ padding: 6px 11px;
825
+ text-align: left;
826
+ }
827
+ .md th {
828
+ background: var(--surface-2);
829
+ font-weight: 700;
830
+ }
831
+ .md tr:nth-child(2n) td {
832
+ background: color-mix(in srgb, var(--surface-2) 55%, transparent);
833
+ }
834
+ .md img {
835
+ max-width: 100%;
836
+ border-radius: 10px;
837
+ }
838
+ .mention {
839
+ font-weight: 800;
840
+ font-style: italic;
841
+ color: var(--primary);
842
+ background: var(--primary-soft);
843
+ border-radius: 6px;
844
+ padding: 0 5px;
845
+ }
846
+
847
+ /* ---------- 群聊发言队列面板(右侧,回合制样式) ---------- */
848
+ .turn-queue {
849
+ width: 210px;
850
+ flex-shrink: 0;
851
+ border-left: 1px solid var(--border);
852
+ background: color-mix(in srgb, var(--surface) 55%, transparent);
853
+ backdrop-filter: blur(16px);
854
+ display: flex;
855
+ flex-direction: column;
856
+ min-height: 0;
857
+ padding: 12px 14px;
858
+ gap: 10px;
859
+ }
860
+ .turn-queue-head {
861
+ display: flex;
862
+ align-items: center;
863
+ justify-content: space-between;
864
+ font-family: var(--font-serif);
865
+ font-size: 13.5px;
866
+ font-weight: 700;
867
+ color: var(--text);
868
+ padding-bottom: 8px;
869
+ border-bottom: 1px solid var(--border-soft);
870
+ flex-shrink: 0;
871
+ }
872
+ .turn-queue-loop {
873
+ font-size: 10.5px;
874
+ font-family: var(--font-mono);
875
+ color: var(--text-faint);
876
+ background: var(--surface-2);
877
+ padding: 1px 7px;
878
+ border-radius: 999px;
879
+ }
880
+ .turn-queue-body {
881
+ display: flex;
882
+ flex-direction: column;
883
+ gap: 12px;
884
+ overflow-y: auto;
885
+ flex: 1;
886
+ min-height: 0;
887
+ padding-right: 2px;
888
+ }
889
+ .turn-queue-loop-group {
890
+ display: flex;
891
+ flex-direction: column;
892
+ gap: 4px;
893
+ }
894
+ .turn-queue-loop-head {
895
+ display: flex;
896
+ align-items: center;
897
+ gap: 7px;
898
+ font-size: 10.5px;
899
+ font-weight: 800;
900
+ color: var(--text-faint);
901
+ letter-spacing: 0.4px;
902
+ padding: 2px 2px 0;
903
+ }
904
+ .turn-queue-loop-cur {
905
+ color: var(--primary);
906
+ background: var(--primary-soft);
907
+ border-radius: 999px;
908
+ padding: 1px 7px;
909
+ font-size: 9.5px;
910
+ }
911
+ .turn-queue-list {
912
+ list-style: none;
913
+ margin: 0;
914
+ padding: 0;
915
+ display: flex;
916
+ flex-direction: column;
917
+ gap: 4px;
918
+ }
919
+ .turn-queue-item {
920
+ display: flex;
921
+ align-items: center;
922
+ gap: 8px;
923
+ padding: 5px 8px;
924
+ border-radius: 10px;
925
+ font-size: 12.5px;
926
+ color: var(--text-dim);
927
+ border: 1px solid transparent;
928
+ transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
929
+ }
930
+ /* 可点击定位:整行可点,悬停有反馈 */
931
+ .turn-queue-item {
932
+ cursor: pointer;
933
+ }
934
+ .turn-queue-item:hover {
935
+ background: var(--surface-2);
936
+ border-color: var(--border);
937
+ }
938
+ .turn-queue-name {
939
+ flex: 1;
940
+ min-width: 0;
941
+ white-space: nowrap;
942
+ overflow: hidden;
943
+ text-overflow: ellipsis;
944
+ }
945
+ .turn-queue-idx {
946
+ width: 16px;
947
+ text-align: center;
948
+ font-family: var(--font-mono);
949
+ font-size: 10.5px;
950
+ color: var(--text-faint);
951
+ flex-shrink: 0;
952
+ }
953
+ .turn-queue-item.active {
954
+ position: relative;
955
+ z-index: 1;
956
+ background: var(--primary-soft);
957
+ border-color: color-mix(in srgb, var(--primary) 38%, transparent);
958
+ box-shadow:
959
+ inset 0 0 8px color-mix(in srgb, var(--glow) 20%, transparent),
960
+ 0 0 0 1px color-mix(in srgb, var(--primary) 22%, transparent),
961
+ 0 0 8px color-mix(in srgb, var(--glow) 38%, transparent),
962
+ 0 0 14px color-mix(in srgb, var(--glow) 16%, transparent);
963
+ }
964
+ /* 轮到玩家:整行警告色高亮 */
965
+ .turn-queue-item.waiting-user {
966
+ border-color: color-mix(in srgb, var(--warn) 40%, transparent);
967
+ background: var(--warn-soft);
968
+ }
969
+ /* 正在发言的角色:整行高亮 + 闪烁(无文字指示器) */
970
+ .turn-queue-item.active.playing {
971
+ animation: queueSpeaking 1.2s ease-in-out infinite;
972
+ }
973
+ .turn-queue-item.active.playing .turn-queue-name {
974
+ color: var(--primary);
975
+ }
976
+ @keyframes queueSpeaking {
977
+ 0%,
978
+ 100% {
979
+ background: var(--primary-soft);
980
+ box-shadow:
981
+ inset 0 0 8px color-mix(in srgb, var(--glow) 18%, transparent),
982
+ 0 0 0 1px color-mix(in srgb, var(--primary) 22%, transparent),
983
+ 0 0 8px color-mix(in srgb, var(--glow) 36%, transparent),
984
+ 0 0 14px color-mix(in srgb, var(--glow) 14%, transparent);
985
+ opacity: 1;
986
+ }
987
+ 50% {
988
+ background: color-mix(in srgb, var(--primary) 22%, transparent);
989
+ box-shadow:
990
+ inset 0 0 10px color-mix(in srgb, var(--glow) 26%, transparent),
991
+ 0 0 0 1px color-mix(in srgb, var(--primary) 30%, transparent),
992
+ 0 0 12px color-mix(in srgb, var(--glow) 48%, transparent),
993
+ 0 0 20px color-mix(in srgb, var(--glow) 20%, transparent);
994
+ opacity: 0.55;
995
+ }
996
+ }
997
+ .turn-queue-seat {
998
+ font-size: 9.5px;
999
+ font-weight: 800;
1000
+ color: var(--text-faint);
1001
+ background: var(--surface-2);
1002
+ border-radius: 999px;
1003
+ padding: 1px 7px;
1004
+ flex-shrink: 0;
1005
+ }
1006
+ .turn-queue-foot {
1007
+ margin-top: auto;
1008
+ font-size: 10.5px;
1009
+ color: var(--text-faint);
1010
+ padding-top: 8px;
1011
+ border-top: 1px solid var(--border-soft);
1012
+ flex-shrink: 0;
1013
+ }
1014
+
1015
+ /* 聊天主区 + 右侧队列的横向布局 */
1016
+ .chat-main-row {
1017
+ flex: 1;
1018
+ display: flex;
1019
+ min-height: 0;
1020
+ }
1021
+ .chat-main-col {
1022
+ flex: 1;
1023
+ min-width: 0;
1024
+ display: flex;
1025
+ flex-direction: column;
1026
+ }
1027
+ .chat-main-col .chat-scroll {
1028
+ flex: 1;
1029
+ }
1030
+
1031
+ /* ---------- 输入区 @ 自动补全 ---------- */
1032
+ .composer-input-wrap {
1033
+ position: relative;
1034
+ flex: 1;
1035
+ display: flex;
1036
+ align-items: flex-end;
1037
+ min-width: 0;
1038
+ }
1039
+ .composer-rich {
1040
+ position: absolute;
1041
+ inset: 0;
1042
+ padding: 7px 4px;
1043
+ font-size: 14px;
1044
+ line-height: 1.5;
1045
+ color: var(--text);
1046
+ white-space: pre-wrap;
1047
+ overflow: auto;
1048
+ pointer-events: none;
1049
+ }
1050
+ .composer-input-wrap textarea {
1051
+ flex: 1;
1052
+ border: none;
1053
+ outline: none;
1054
+ background: none;
1055
+ resize: none;
1056
+ padding: 7px 4px;
1057
+ font-size: 14px;
1058
+ color: var(--text);
1059
+ line-height: 1.5;
1060
+ min-width: 0;
1061
+ }
1062
+ .composer-input-wrap textarea.with-rich {
1063
+ color: transparent;
1064
+ -webkit-text-fill-color: transparent;
1065
+ caret-color: var(--text);
1066
+ position: relative;
1067
+ z-index: 1;
1068
+ }
1069
+ .mention.mention-live,
1070
+ .cmd-live {
1071
+ font-weight: inherit;
1072
+ font-style: inherit;
1073
+ background: none;
1074
+ border-radius: 0;
1075
+ padding: 0;
1076
+ color: var(--primary);
1077
+ text-decoration: underline;
1078
+ text-decoration-color: color-mix(in srgb, var(--primary) 55%, transparent);
1079
+ text-underline-offset: 2px;
1080
+ }
1081
+ .mention-pop {
1082
+ position: absolute;
1083
+ bottom: calc(100% + 8px);
1084
+ left: 0;
1085
+ min-width: 190px;
1086
+ max-height: 240px;
1087
+ overflow-y: auto;
1088
+ background: var(--surface);
1089
+ border: 1px solid var(--border);
1090
+ border-radius: 12px;
1091
+ box-shadow: var(--shadow-lg);
1092
+ padding: 5px;
1093
+ z-index: 60;
1094
+ display: flex;
1095
+ flex-direction: column;
1096
+ gap: 1px;
1097
+ }
1098
+ .mention-pop-head {
1099
+ font-size: 10.5px;
1100
+ font-weight: 800;
1101
+ color: var(--text-faint);
1102
+ padding: 4px 8px 6px;
1103
+ letter-spacing: 0.4px;
1104
+ }
1105
+ .mention-item {
1106
+ display: flex;
1107
+ align-items: center;
1108
+ gap: 7px;
1109
+ width: 100%;
1110
+ padding: 6px 8px;
1111
+ border: none;
1112
+ background: none;
1113
+ border-radius: 8px;
1114
+ color: var(--text);
1115
+ font-size: 12.5px;
1116
+ cursor: pointer;
1117
+ text-align: left;
1118
+ }
1119
+ .mention-item:hover {
1120
+ background: var(--surface-2);
1121
+ }
1122
+ .mention-item.active {
1123
+ background: var(--primary-soft);
1124
+ color: var(--primary);
1125
+ box-shadow:
1126
+ inset 0 0 8px color-mix(in srgb, var(--glow) 16%, transparent);
1127
+ }
1128
+ .mention-item-at {
1129
+ font-weight: 800;
1130
+ color: var(--primary);
1131
+ }
1132
+
1133
+ /* ---------- 排序弹窗 ---------- */
1134
+ .seg {
1135
+ display: flex;
1136
+ background: var(--surface-2);
1137
+ border: 1px solid var(--border-soft);
1138
+ border-radius: 10px;
1139
+ padding: 3px;
1140
+ margin-bottom: 8px;
1141
+ }
1142
+ .seg-btn {
1143
+ flex: 1;
1144
+ border: none;
1145
+ background: none;
1146
+ padding: 7px 10px;
1147
+ border-radius: 8px;
1148
+ font-size: 12.5px;
1149
+ font-weight: 700;
1150
+ color: var(--text-dim);
1151
+ cursor: pointer;
1152
+ }
1153
+ .seg-btn.active {
1154
+ background: var(--surface);
1155
+ color: var(--primary);
1156
+ box-shadow: var(--shadow);
1157
+ }
1158
+ .sort-list {
1159
+ display: flex;
1160
+ flex-direction: column;
1161
+ gap: 4px;
1162
+ }
1163
+ .sort-item {
1164
+ display: flex;
1165
+ align-items: center;
1166
+ gap: 9px;
1167
+ padding: 7px 9px;
1168
+ border: 1px solid var(--border-soft);
1169
+ border-radius: 10px;
1170
+ background: var(--surface-2);
1171
+ font-size: 13px;
1172
+ color: var(--text);
1173
+ cursor: grab;
1174
+ user-select: none;
1175
+ touch-action: none; /* dnd-kit:允许触摸设备拖拽 */
1176
+ /* 平滑过渡:拖拽时 transform 由 dnd-kit 驱动,这里只给结束回弹加过渡 */
1177
+ transition: box-shadow 0.15s ease, opacity 0.15s ease;
1178
+ }
1179
+ .sort-item:hover {
1180
+ border-color: color-mix(in srgb, var(--primary) 28%, transparent);
1181
+ }
1182
+ .sort-item:active {
1183
+ cursor: grabbing;
1184
+ }
1185
+ /* 正在拖拽的卡片:抬升 + 阴影 */
1186
+ .sort-item.dragging {
1187
+ box-shadow:
1188
+ 0 0 0 1px color-mix(in srgb, var(--primary) 30%, transparent),
1189
+ 0 0 10px color-mix(in srgb, var(--glow) 42%, transparent),
1190
+ 0 0 24px color-mix(in srgb, var(--glow) 16%, transparent),
1191
+ var(--shadow-lg);
1192
+ border-color: color-mix(in srgb, var(--primary) 45%, transparent);
1193
+ cursor: grabbing;
1194
+ }
1195
+ .sort-grip {
1196
+ color: var(--text-faint);
1197
+ font-size: 13px;
1198
+ flex-shrink: 0;
1199
+ }
1200
+ .sort-name {
1201
+ flex: 1;
1202
+ min-width: 0;
1203
+ white-space: nowrap;
1204
+ overflow: hidden;
1205
+ text-overflow: ellipsis;
1206
+ }
1207
+ .sort-tag {
1208
+ font-size: 9.5px;
1209
+ font-weight: 800;
1210
+ color: var(--primary);
1211
+ background: var(--primary-soft);
1212
+ border-radius: 999px;
1213
+ padding: 1px 7px;
1214
+ flex-shrink: 0;
1215
+ }
1216
+ .math-block {
1217
+ text-align: center;
1218
+ margin: 0.7em 0;
1219
+ overflow-x: auto;
1220
+ padding: 6px 0;
1221
+ }
1222
+ .math-fallback {
1223
+ opacity: 0.8;
1224
+ }
1225
+ .md .katex {
1226
+ font-size: 1.08em;
1227
+ }