@sidleo3/dsh-chat 0.0.4

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 (47) hide show
  1. package/client/bot-list.js +243 -0
  2. package/client/bot-settings.js +175 -0
  3. package/client/bot-shared-settings.js +561 -0
  4. package/client/chat-ui.js +134 -0
  5. package/client/context-enhancement.js +435 -0
  6. package/client/delivery-targets.js +334 -0
  7. package/client/diagnostics.js +160 -0
  8. package/client/i18n.js +371 -0
  9. package/client/index.js +77 -0
  10. package/client/list-order.js +144 -0
  11. package/client/rpc.js +52 -0
  12. package/client/scoped-mode-editor.js +111 -0
  13. package/client/section.js +250 -0
  14. package/client/session-badges.js +263 -0
  15. package/client/styles.js +960 -0
  16. package/client/version-panel.js +97 -0
  17. package/cordis.patch.yml +5 -0
  18. package/host/bot-model.mjs +53 -0
  19. package/host/bot-settings.mjs +247 -0
  20. package/host/channel-registry.mjs +237 -0
  21. package/host/commands.mjs +857 -0
  22. package/host/deferred.mjs +291 -0
  23. package/host/delivery.mjs +377 -0
  24. package/host/file-log.mjs +169 -0
  25. package/host/guidance.mjs +73 -0
  26. package/host/index.mjs +7 -0
  27. package/host/interactions.mjs +330 -0
  28. package/host/json-store.mjs +144 -0
  29. package/host/log-tail.mjs +63 -0
  30. package/host/panel.mjs +1012 -0
  31. package/host/paths.mjs +50 -0
  32. package/host/plugin.mjs +873 -0
  33. package/host/prompt-context.mjs +70 -0
  34. package/host/rpc.mjs +147 -0
  35. package/host/session-keys.mjs +25 -0
  36. package/host/session-store.mjs +187 -0
  37. package/host/sessions.mjs +1348 -0
  38. package/host/tools.mjs +283 -0
  39. package/lib/client.js +4431 -0
  40. package/lib/index.js +5676 -0
  41. package/package.json +63 -0
  42. package/shared/access-policy.mjs +263 -0
  43. package/shared/channel-rail.mjs +156 -0
  44. package/shared/context-enhancement.mjs +415 -0
  45. package/shared/contract.mjs +120 -0
  46. package/shared/panel-sections.mjs +76 -0
  47. package/shared/reply-reference.mjs +115 -0
@@ -0,0 +1,960 @@
1
+ /**
2
+ * dsh-chat 的共享样式。使用 DSH 的主题变量,因此跟随明暗主题与品牌色。
3
+ * 所有类名以 `dchat-` 前缀,避免与其他插件的样式冲突。
4
+ *
5
+ * @module dsh-chat/client/styles
6
+ */
7
+
8
+ const STYLE_ID = 'dsh-chat-styles';
9
+
10
+ const CSS = `
11
+ .dchat-page {
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: 16px;
15
+ height: 100%;
16
+ min-height: 0;
17
+ color: var(--dsw-alias-label-primary);
18
+ font-family: var(--dsw-font-family);
19
+ }
20
+ .dchat-header {
21
+ display: flex;
22
+ align-items: baseline;
23
+ justify-content: space-between;
24
+ gap: 12px;
25
+ /* 窄栏下入口换行,而不是把品牌名挤出容器。 */
26
+ flex-wrap: wrap;
27
+ }
28
+ /* 右上角入口组(诊断 / 版本与更新):形态一致、放不下就换行。 */
29
+ .dchat-headerActions {
30
+ display: flex;
31
+ align-items: baseline;
32
+ gap: 8px;
33
+ flex-wrap: wrap;
34
+ }
35
+ .dchat-brand {
36
+ display: flex;
37
+ align-items: baseline;
38
+ gap: 8px;
39
+ }
40
+ .dchat-brandName {
41
+ font-size: 16px;
42
+ font-weight: 600;
43
+ }
44
+ .dchat-brandHint {
45
+ font-size: 12px;
46
+ color: var(--dsw-alias-label-tertiary);
47
+ }
48
+ .dchat-layout {
49
+ display: flex;
50
+ gap: 16px;
51
+ align-items: flex-start;
52
+ min-height: 0;
53
+ flex: 1;
54
+ }
55
+ .dchat-rail {
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: 4px;
59
+ /* 只有「图标 + 渠道名」,副标题在右栏标题下,所以这里可以窄一点,把宽度让给右栏。 */
60
+ width: 148px;
61
+ flex: none;
62
+ }
63
+ .dchat-channel {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 8px;
67
+ padding: 8px 10px;
68
+ /* 边框始终占位(未选中是透明的),选中/未选中的外框一样大,列表才不会"一卡片 + 一裸行"。 */
69
+ border: 1px solid transparent;
70
+ border-radius: 8px;
71
+ background: transparent;
72
+ color: inherit;
73
+ font: inherit;
74
+ text-align: left;
75
+ cursor: pointer;
76
+ }
77
+ .dchat-channel:hover {
78
+ background: var(--dsw-alias-interactive-bg-hover);
79
+ }
80
+ .dchat-channel[aria-selected='true'] {
81
+ background: var(--dsw-alias-bg-layer-2);
82
+ border-color: var(--dsw-alias-border-l2);
83
+ /* 选中的第二个信号:左侧品牌色竖条(只靠底色在浅色主题下不够明显)。 */
84
+ box-shadow: inset 2px 0 0 0 var(--dsw-alias-brand-primary);
85
+ }
86
+ .dchat-channelMark {
87
+ display: inline-flex;
88
+ align-items: center;
89
+ justify-content: center;
90
+ width: 22px;
91
+ height: 22px;
92
+ border-radius: 6px;
93
+ background: var(--dsw-alias-bg-layer-2);
94
+ border: 1px solid var(--dsw-alias-border-l2);
95
+ font-size: 12px;
96
+ font-weight: 600;
97
+ flex: none;
98
+ }
99
+ .dchat-channelMarkIcon {
100
+ width: auto;
101
+ height: auto;
102
+ border: 0;
103
+ background: none;
104
+ }
105
+ .dchat-channelMarkIcon svg {
106
+ width: 20px;
107
+ height: 20px;
108
+ display: block;
109
+ }
110
+ .dchat-channelLabel {
111
+ min-width: 0;
112
+ }
113
+ .dchat-channelLabel strong {
114
+ display: block;
115
+ font-size: 13px;
116
+ font-weight: 500;
117
+ overflow: hidden;
118
+ text-overflow: ellipsis;
119
+ white-space: nowrap;
120
+ }
121
+ .dchat-panel {
122
+ flex: 1;
123
+ min-width: 0;
124
+ display: flex;
125
+ flex-direction: column;
126
+ gap: 12px;
127
+ }
128
+ .dchat-card {
129
+ border: 1px solid var(--dsw-alias-border-l2);
130
+ border-radius: 10px;
131
+ background: var(--dsw-alias-bg-layer-1);
132
+ padding: 14px 16px;
133
+ display: flex;
134
+ flex-direction: column;
135
+ gap: 10px;
136
+ }
137
+ .dchat-groupTitle {
138
+ margin: 0;
139
+ font-size: 12px;
140
+ font-weight: 600;
141
+ color: var(--dsw-alias-label-secondary);
142
+ }
143
+ .dchat-check {
144
+ display: inline-flex;
145
+ align-items: center;
146
+ gap: 6px;
147
+ font-size: 12px;
148
+ color: var(--dsw-alias-label-secondary);
149
+ white-space: nowrap;
150
+ }
151
+ .dchat-check input {
152
+ margin: 0;
153
+ }
154
+ .dchat-policyGrid {
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: 14px;
158
+ }
159
+ .dchat-policyGrid > * + * {
160
+ border-top: 1px solid var(--dsw-alias-separator-primary);
161
+ padding-top: 14px;
162
+ }
163
+ /* 作用域标题行:模式下拉按内容宽度,不要像操作行里的下拉那样吃掉整行。 */
164
+ .dchat-policyHead {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: 8px;
168
+ }
169
+ .dchat-policyHead > select {
170
+ width: auto;
171
+ flex: none;
172
+ }
173
+ .dchat-actions > .dchat-check {
174
+ flex: none;
175
+ }
176
+ /* 卡片套卡片(机器人卡里放渠道/机器人级设置块)时,内层去边框、改成分隔线:
177
+ 两层边框 + 两层 padding 会让缩进和视觉重量都乱掉。 */
178
+ .dchat-card .dchat-card {
179
+ border: 0;
180
+ border-radius: 0;
181
+ background: transparent;
182
+ padding: 12px 0 0;
183
+ border-top: 1px solid var(--dsw-alias-separator-primary);
184
+ }
185
+ .dchat-cardHeader {
186
+ display: flex;
187
+ align-items: center;
188
+ justify-content: space-between;
189
+ gap: 8px 12px;
190
+ /* 放不下时把操作整块折到下一行,而不是把标题/描述压到一字一行:
191
+ 中文的 min-content 只有 1 个字,flex 一旦压缩就会逐字竖排(见 .dchat-botRow 同款处理)。 */
192
+ flex-wrap: wrap;
193
+ }
194
+ .dchat-cardHeading {
195
+ /* 长标题靠省略号收,不抢操作的宽度。 */
196
+ min-width: 0;
197
+ }
198
+ .dchat-cardTitle {
199
+ font-size: 14px;
200
+ font-weight: 600;
201
+ overflow: hidden;
202
+ text-overflow: ellipsis;
203
+ white-space: nowrap;
204
+ }
205
+ .dchat-cardDescription {
206
+ margin: 0;
207
+ font-size: 12px;
208
+ line-height: 1.6;
209
+ color: var(--dsw-alias-label-secondary);
210
+ }
211
+ .dchat-list {
212
+ display: flex;
213
+ flex-direction: column;
214
+ gap: 6px;
215
+ margin: 0;
216
+ padding: 0;
217
+ list-style: none;
218
+ }
219
+ .dchat-listItem {
220
+ display: flex;
221
+ align-items: center;
222
+ justify-content: space-between;
223
+ gap: 10px;
224
+ font-size: 12px;
225
+ color: var(--dsw-alias-label-secondary);
226
+ }
227
+ /* 名单/属主行里的 id 可能很长(open_id 有 35 字符)。它是 flex 项,默认 min-width:auto
228
+ 不肯缩,就会把同排的按钮挤出容器(窄栏直接横向溢出)。 */
229
+ .dchat-listItem .dchat-code {
230
+ min-width: 0;
231
+ max-width: 100%;
232
+ overflow: hidden;
233
+ text-overflow: ellipsis;
234
+ white-space: nowrap;
235
+ }
236
+ /* 左侧的文字(名字、说明)允许换行、允许收缩——不许把右侧的操作顶出去。 */
237
+ .dchat-listItem > :not(.dchat-actions) {
238
+ min-width: 0;
239
+ overflow-wrap: anywhere;
240
+ }
241
+ /* 白名单行:名字 + id 放同一个块里(id 才是判定用的值,名字只是给人看的)。
242
+ 两个都允许收缩并省略号,谁长谁让位——名字很长时不许把 id 顶出容器。 */
243
+ .dchat-policyEntry {
244
+ display: flex;
245
+ align-items: baseline;
246
+ gap: 6px;
247
+ min-width: 0;
248
+ }
249
+ .dchat-policyEntry > * {
250
+ min-width: 0;
251
+ overflow: hidden;
252
+ text-overflow: ellipsis;
253
+ white-space: nowrap;
254
+ }
255
+ .dchat-policyName {
256
+ color: var(--dsw-alias-label-primary);
257
+ }
258
+ .dchat-code {
259
+ font-family: var(--dsw-font-markdown-code-block-small, ui-monospace, SFMono-Regular, monospace);
260
+ font-size: 12px;
261
+ padding: 1px 5px;
262
+ border-radius: 4px;
263
+ background: var(--dsw-alias-markdown-code-block);
264
+ color: var(--dsw-alias-label-primary);
265
+ }
266
+ .dchat-solo {
267
+ display: flex;
268
+ flex-direction: column;
269
+ gap: 12px;
270
+ flex: 1;
271
+ min-height: 0;
272
+ overflow-y: auto;
273
+ /* 进了机器人设置就占满整宽:左栏渠道列表此时没有意义。 */
274
+ width: 100%;
275
+ }
276
+ .dchat-panelBar {
277
+ display: flex;
278
+ align-items: center;
279
+ gap: 8px;
280
+ }
281
+ .dchat-botList {
282
+ display: flex;
283
+ flex-direction: column;
284
+ gap: 8px;
285
+ margin: 0;
286
+ padding: 0;
287
+ list-style: none;
288
+ }
289
+ .dchat-botRow {
290
+ display: flex;
291
+ align-items: center;
292
+ justify-content: space-between;
293
+ gap: 12px;
294
+ border: 1px solid var(--dsw-alias-border-l2);
295
+ border-radius: 8px;
296
+ padding: 10px 12px;
297
+ }
298
+ /* 「设置」按钮永远是内容宽度:flex 默认的 min-width:auto 会让它在窄栏里被压成一个字宽。 */
299
+ .dchat-grip {
300
+ /*
301
+ * 拖动把手:只能拖它,不让整行可拖——整行可拖会与"点一下切换渠道/机器人"抢手势,
302
+ * 而且 button 元素在部分浏览器上 draggable 不生效。移动端不支持 HTML5 拖放,
303
+ * 所以它只是桌面端的便利功能(顺序本身不影响任何行为)。
304
+ */
305
+ flex: none;
306
+ cursor: grab;
307
+ color: var(--dsw-alias-label-tertiary);
308
+ font-size: 12px;
309
+ line-height: 1;
310
+ letter-spacing: -1px;
311
+ user-select: none;
312
+ }
313
+ .dchat-grip:active {
314
+ cursor: grabbing;
315
+ }
316
+ /* 拖动经过的可落点:只做高亮,不改布局(改动布局会让拖动手感抖)。 */
317
+ .dchat-dropTarget {
318
+ border-color: var(--dsw-alias-brand-primary) !important;
319
+ box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary);
320
+ }
321
+ .dchat-botRow > .dchat-button {
322
+ flex: none;
323
+ }
324
+ .dchat-botMain {
325
+ display: flex;
326
+ flex-direction: column;
327
+ gap: 4px;
328
+ flex: 1 1 auto;
329
+ min-width: 0;
330
+ }
331
+ .dchat-botTitle {
332
+ display: flex;
333
+ align-items: center;
334
+ gap: 8px;
335
+ font-size: 13px;
336
+ min-width: 0;
337
+ }
338
+ /* 名称(可能是很长的 botId)负责截断,状态点保持自身宽度不被压缩。 */
339
+ .dchat-botTitle > strong {
340
+ min-width: 0;
341
+ overflow: hidden;
342
+ text-overflow: ellipsis;
343
+ white-space: nowrap;
344
+ }
345
+ .dchat-botTitle > :not(strong) {
346
+ flex: none;
347
+ white-space: nowrap;
348
+ }
349
+ .dchat-botMeta {
350
+ display: flex;
351
+ align-items: center;
352
+ gap: 10px;
353
+ font-size: 12px;
354
+ color: var(--dsw-alias-label-tertiary);
355
+ /* 放不下就整项换行,绝不让中文按字折行(每个汉字都是断行点,会被压成竖排)。 */
356
+ flex-wrap: wrap;
357
+ }
358
+ .dchat-botMeta > * {
359
+ white-space: nowrap;
360
+ /*
361
+ * 加拖动把手之后窄栏(320px)会差几个像素:flex 项默认 min-width:auto 不会缩,
362
+ * 于是「最近 09-19 10:21」整块顶出去。允许这些片段自己收成省略号,
363
+ * 而不是把整行撑破——标题与名称在上面一行,仍然读得到。
364
+ */
365
+ min-width: 0;
366
+ overflow: hidden;
367
+ text-overflow: ellipsis;
368
+ }
369
+ /* 账号可能很长:让它省略号截断,别把卡片撑破(flex 项默认 min-width:auto 不会缩)。 */
370
+ .dchat-botMeta > .dchat-code {
371
+ min-width: 0;
372
+ max-width: 100%;
373
+ overflow: hidden;
374
+ text-overflow: ellipsis;
375
+ }
376
+ .dchat-botError {
377
+ font-size: 12px;
378
+ color: var(--dsw-alias-state-error-primary);
379
+ word-break: break-word;
380
+ }
381
+ .dchat-versionMeta {
382
+ display: inline-flex;
383
+ align-items: center;
384
+ gap: 8px;
385
+ }
386
+ /* 诊断面板里的渠道块头:标题与状态点在两侧,放不下就整块换行。 */
387
+ .dchat-diagHead {
388
+ display: flex;
389
+ align-items: center;
390
+ justify-content: space-between;
391
+ gap: 8px;
392
+ flex-wrap: wrap;
393
+ }
394
+ /* 标题不折行(折了就是"逐字竖排")——它整块换行靠上面那条 wrap。 */
395
+ .dchat-diagHead > .dchat-groupTitle {
396
+ white-space: nowrap;
397
+ }
398
+ /* 诊断面板里每个渠道/每个日志各一块:竖排。
399
+ .dchat-entry 是"可点击的折叠行"(横排),拿来装这些块会把它们挤成一条条细条。 */
400
+ .dchat-diagSection {
401
+ display: flex;
402
+ flex-direction: column;
403
+ gap: 6px;
404
+ width: 100%;
405
+ }
406
+ .dchat-diagSection > .dchat-list {
407
+ min-width: 0;
408
+ }
409
+ /* 机器人块:状态行 + 若干行错误/提示,竖直排列。 */
410
+ .dchat-diagBot {
411
+ display: flex;
412
+ flex-direction: column;
413
+ gap: 4px;
414
+ min-width: 0;
415
+ }
416
+ /* 诊断面板里的机器人行:一行放不下时整体换行,而不是把 id 顶出容器。
417
+ min-width: 0 是必须的:这些块的内容(35 字符的 open_id)会把 flex 项的
418
+ min-width: auto 撑成 min-content,于是整块比卡片还宽、把页面顶出滚动条(真机测到过)。 */
419
+ .dchat-diagRow {
420
+ flex-wrap: wrap;
421
+ min-width: 0;
422
+ }
423
+ .dchat-diagRow > .dchat-code {
424
+ flex: 1 1 auto;
425
+ min-width: 0;
426
+ }
427
+ .dchat-diagRow > .dchat-diagMeta {
428
+ flex: 0 1 auto;
429
+ min-width: 0;
430
+ }
431
+ /* 诊断面板里机器人行的右侧元信息(状态 · 已处理 · 时间)。
432
+ 整块不折行(折了就成了"逐字竖排"),放不下时靠 .dchat-diagRow 换行。 */
433
+ .dchat-diagMeta {
434
+ font-size: 12px;
435
+ color: var(--dsw-alias-label-secondary);
436
+ white-space: nowrap;
437
+ }
438
+ /* 日志尾部:等宽、限高、双向可滚(滚动发生在块内,不会把整页撑宽)。 */
439
+ .dchat-logTail {
440
+ white-space: pre;
441
+ max-height: 220px;
442
+ overflow: auto;
443
+ font-size: 11px;
444
+ line-height: 1.5;
445
+ }
446
+ .dchat-updateHint {
447
+ display: flex;
448
+ flex-direction: column;
449
+ gap: 6px;
450
+ margin: 0;
451
+ }
452
+ .dchat-codeBlock {
453
+ display: block;
454
+ padding: 6px 8px;
455
+ overflow-x: auto;
456
+ white-space: nowrap;
457
+ }
458
+ .dchat-empty {
459
+ display: flex;
460
+ flex-direction: column;
461
+ gap: 8px;
462
+ align-items: flex-start;
463
+ border: 1px dashed var(--dsw-alias-border-l3);
464
+ border-radius: 10px;
465
+ padding: 18px;
466
+ color: var(--dsw-alias-label-secondary);
467
+ }
468
+ .dchat-emptyTitle {
469
+ font-size: 14px;
470
+ font-weight: 600;
471
+ color: var(--dsw-alias-label-primary);
472
+ }
473
+ .dchat-actions {
474
+ display: flex;
475
+ gap: 8px;
476
+ align-items: center;
477
+ /* 操作块可以缩到"最宽的那个按钮",这样下面那条 flex-wrap 才会真的生效。
478
+ 曾经写的是 flex: none:内容再宽也不缩,于是窄栏里(320px 的 hub 页头)
479
+ 整块直接顶出容器 15px —— 正是布局守门量出来的那一条。
480
+ 按钮自身仍是 flex: none,不会出现"逐字竖排"。 */
481
+ flex: 0 1 auto;
482
+ min-width: 0;
483
+ /* 放不下时按钮自己换行,而不是把整块顶出容器(窄栏里"下拉+两个按钮"就会溢出)。 */
484
+ flex-wrap: wrap;
485
+ }
486
+ /* 只给卡片头用:折到第二行时仍然靠右。
487
+ 不能写在 .dchat-actions 上——auto 外边距会取消交叉轴的 stretch,
488
+ 让"竖排容器里的操作行"退化成内容宽度(发送行的下拉框就缩成一小截)。 */
489
+ .dchat-cardHeader > .dchat-actions {
490
+ margin-left: auto;
491
+ }
492
+ .dchat-actions > * {
493
+ white-space: nowrap;
494
+ }
495
+ /* 只有按钮和状态点"永不压缩";输入类必须能缩,否则会撑出横向滚动:
496
+ .dchat-select 是 width:100%,配 flex:none 会先占满整行,再把同排的按钮挤出容器。 */
497
+ .dchat-actions > .dchat-button,
498
+ .dchat-actions > .dchat-status {
499
+ flex: none;
500
+ }
501
+ .dchat-actions > select,
502
+ .dchat-actions > input {
503
+ flex: 1 1 0;
504
+ min-width: 0;
505
+ width: auto;
506
+ }
507
+ .dchat-button {
508
+ border: 1px solid var(--dsw-alias-border-l2);
509
+ border-radius: 6px;
510
+ background: transparent;
511
+ color: inherit;
512
+ font: inherit;
513
+ font-size: 12px;
514
+ padding: 4px 10px;
515
+ cursor: pointer;
516
+ /* 中文按钮被压窄时会二字竖排,任何按钮都不允许折行。 */
517
+ white-space: nowrap;
518
+ }
519
+ .dchat-button:hover:not(:disabled) {
520
+ background: var(--dsw-alias-interactive-bg-hover);
521
+ }
522
+ .dchat-button:disabled {
523
+ opacity: 0.5;
524
+ cursor: not-allowed;
525
+ }
526
+ /* 次要入口(如「版本与更新」):文字链接形态,不和 DSH 自己的实心按钮抢注意力。 */
527
+ .dchat-buttonLink {
528
+ border-color: transparent;
529
+ background: transparent;
530
+ color: var(--dsw-alias-link);
531
+ padding-left: 4px;
532
+ padding-right: 4px;
533
+ }
534
+ .dchat-buttonLink:hover:not(:disabled) {
535
+ background: transparent;
536
+ text-decoration: underline;
537
+ }
538
+ /* 不可逆操作:触发按钮只染文字,确认按钮才用实底,避免两个同级灰按钮里藏着删除。 */
539
+ .dchat-buttonDanger {
540
+ color: var(--dsw-alias-state-error-primary);
541
+ }
542
+ .dchat-buttonDangerSolid {
543
+ background: var(--dsw-alias-state-error-primary);
544
+ border-color: transparent;
545
+ color: #fff;
546
+ }
547
+ .dchat-buttonDangerSolid:hover:not(:disabled) {
548
+ background: var(--dsw-alias-state-error-primary);
549
+ opacity: 0.88;
550
+ }
551
+ .dchat-status {
552
+ display: inline-flex;
553
+ align-items: center;
554
+ gap: 5px;
555
+ font-size: 11px;
556
+ color: var(--dsw-alias-label-tertiary);
557
+ /* 状态点自己永远不折行也不压缩(窄栏里曾被压成「运行正/常」)。 */
558
+ flex: none;
559
+ white-space: nowrap;
560
+ }
561
+ .dchat-status::before {
562
+ content: '';
563
+ width: 6px;
564
+ height: 6px;
565
+ border-radius: 50%;
566
+ background: currentColor;
567
+ }
568
+ .dchat-status[data-tone='success'] { color: var(--dsw-alias-state-success-primary); }
569
+ .dchat-status[data-tone='warning'] { color: var(--dsw-alias-state-warn-primary); }
570
+ .dchat-status[data-tone='error'] { color: var(--dsw-alias-state-error-primary); }
571
+ .dchat-error {
572
+ font-size: 12px;
573
+ color: var(--dsw-alias-state-error-primary);
574
+ }
575
+ /* 部分成功的告警(例:机器人加上了、但顺带那步没做成):不是失败,但绝不能不说。 */
576
+ .dchat-warning {
577
+ font-size: 12px;
578
+ color: var(--dsw-alias-state-warn-primary);
579
+ }
580
+ .dchat-buttonPrimary {
581
+ background: var(--dsw-alias-brand-primary);
582
+ border-color: transparent;
583
+ color: #fff;
584
+ }
585
+ /* 接入表单:两条路(扫码新建 / 手动接入)各占一段,段间一条细线。 */
586
+ .dchat-onboardSection {
587
+ display: flex;
588
+ flex-direction: column;
589
+ gap: 8px;
590
+ min-width: 0;
591
+ }
592
+ .dchat-onboardTitle {
593
+ margin: 0;
594
+ font-size: 13px;
595
+ font-weight: 600;
596
+ color: var(--dsw-alias-label-primary);
597
+ }
598
+ .dchat-onboardDivider {
599
+ width: 100%;
600
+ margin: 14px 0;
601
+ border: 0;
602
+ border-top: 1px solid var(--dsw-alias-border-l2);
603
+ }
604
+ /* 二维码:固定尺寸、别被 flex 压扁(窄栏里 width:100% 的图片会变形)。 */
605
+ .dchat-onboardQr {
606
+ flex: none;
607
+ align-self: flex-start;
608
+ border-radius: 6px;
609
+ background: #fff;
610
+ }
611
+ .dchat-onboardLink {
612
+ color: var(--dsw-alias-brand-primary);
613
+ font-size: 12px;
614
+ overflow-wrap: anywhere;
615
+ }
616
+ .dchat-entry {
617
+ display: flex;
618
+ align-items: center;
619
+ gap: 10px;
620
+ width: 100%;
621
+ padding: 10px 12px;
622
+ border: 1px solid var(--dsw-alias-border-l2);
623
+ border-radius: 8px;
624
+ background: var(--dsw-alias-bg-layer-1);
625
+ color: inherit;
626
+ font: inherit;
627
+ text-align: left;
628
+ cursor: pointer;
629
+ }
630
+ .dchat-entry:hover:not(:disabled) {
631
+ background: var(--dsw-alias-interactive-bg-hover);
632
+ }
633
+ .dchat-entry:disabled {
634
+ opacity: 0.6;
635
+ cursor: not-allowed;
636
+ }
637
+ .dchat-entryLabel {
638
+ font-size: 13px;
639
+ font-weight: 500;
640
+ }
641
+ .dchat-entryStatus {
642
+ font-size: 12px;
643
+ color: var(--dsw-alias-label-tertiary);
644
+ margin-left: auto;
645
+ }
646
+ .dchat-entryStatus[data-active='true'] {
647
+ color: var(--dsw-alias-state-success-primary);
648
+ }
649
+ .dchat-entryArrow {
650
+ color: var(--dsw-alias-label-tertiary);
651
+ }
652
+ /* 折叠态的入口行放在卡片里时,跟内层设置块用同一种形态(分隔线 + 整宽 + 同字号标题),
653
+ 否则它 40px 高的圆角小盒子夹在两张展开卡片中间,看起来像根分隔线。 */
654
+ .dchat-card .dchat-entry {
655
+ width: 100%;
656
+ border: 0;
657
+ border-top: 1px solid var(--dsw-alias-separator-primary);
658
+ border-radius: 0;
659
+ background: transparent;
660
+ padding: 12px 0 0;
661
+ }
662
+ .dchat-card .dchat-entry .dchat-entryLabel {
663
+ font-size: 14px;
664
+ font-weight: 600;
665
+ }
666
+ .dchat-backdrop {
667
+ position: fixed;
668
+ inset: 0;
669
+ background: rgba(0, 0, 0, 0.45);
670
+ display: flex;
671
+ align-items: flex-start;
672
+ justify-content: center;
673
+ padding: 40px 16px;
674
+ overflow: auto;
675
+ /* 设置弹层自身是 fixed + z-index:1000,渠道弹窗必须压在其上。 */
676
+ z-index: 1100;
677
+ }
678
+ .dchat-dialog {
679
+ width: min(640px, 100%);
680
+ /* 自己滚,而不是让外层滚动:否则内容变高时底部按钮会掉出视口。 */
681
+ max-height: calc(100vh - 80px);
682
+ box-sizing: border-box;
683
+ overflow: auto;
684
+ background: var(--dsw-alias-bg-base);
685
+ border: 1px solid var(--dsw-alias-border-l2);
686
+ border-radius: 12px;
687
+ padding: 16px 18px;
688
+ display: flex;
689
+ flex-direction: column;
690
+ gap: 12px;
691
+ }
692
+ .dchat-dialogHeader {
693
+ display: flex;
694
+ align-items: center;
695
+ justify-content: space-between;
696
+ gap: 10px;
697
+ }
698
+ .dchat-dialogFooter {
699
+ display: flex;
700
+ justify-content: flex-end;
701
+ gap: 8px;
702
+ /* 内容很长时按钮始终粘在弹窗底部。 */
703
+ position: sticky;
704
+ bottom: -16px;
705
+ margin: 0 -18px -16px;
706
+ padding: 10px 18px 14px;
707
+ background: var(--dsw-alias-bg-base);
708
+ border-top: 1px solid var(--dsw-alias-border-l2);
709
+ }
710
+ .dchat-tabs {
711
+ display: flex;
712
+ gap: 6px;
713
+ border-bottom: 1px solid var(--dsw-alias-border-l2);
714
+ }
715
+ .dchat-tab {
716
+ border: none;
717
+ background: transparent;
718
+ color: var(--dsw-alias-label-secondary);
719
+ font: inherit;
720
+ font-size: 13px;
721
+ padding: 6px 10px;
722
+ border-bottom: 2px solid transparent;
723
+ cursor: pointer;
724
+ }
725
+ .dchat-tab[aria-selected='true'] {
726
+ color: var(--dsw-alias-label-primary);
727
+ border-bottom-color: var(--dsw-alias-brand-primary);
728
+ }
729
+ .dchat-tabPanel {
730
+ display: flex;
731
+ flex-direction: column;
732
+ gap: 14px;
733
+ }
734
+ .dchat-tabPanel[hidden] {
735
+ display: none;
736
+ }
737
+ /*
738
+ * hidden 属性靠 UA 样式里的 [hidden]{display:none} 生效,而上面那条 display:flex 是作者样式
739
+ * ——优先级更高,于是"隐藏"的页签照样显示:私聊与群聊两个页签看起来一模一样(真机截图就是
740
+ * 两张内容完全相同的页签)。任何给元素写了 display 的地方,用 hidden 都要补这条。
741
+ * 注意:本文件是模板字符串,注释里**不能出现反引号**(会把模板提前闭合,历史上栽过一次)。
742
+ */
743
+
744
+ .dchat-contextGlobal,
745
+ .dchat-contextTargets {
746
+ display: flex;
747
+ flex-direction: column;
748
+ gap: 8px;
749
+ }
750
+ .dchat-contextSwitchRow {
751
+ display: flex;
752
+ align-items: center;
753
+ justify-content: space-between;
754
+ gap: 10px;
755
+ }
756
+ .dchat-contextSwitchLabel {
757
+ font-size: 13px;
758
+ }
759
+ .dchat-contextLegendRow,
760
+ .dchat-contextLegend {
761
+ font-size: 12px;
762
+ color: var(--dsw-alias-label-secondary);
763
+ }
764
+ .dchat-contextFields {
765
+ display: grid;
766
+ grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
767
+ gap: 4px 12px;
768
+ }
769
+ .dchat-contextField {
770
+ display: flex;
771
+ align-items: center;
772
+ gap: 6px;
773
+ font-size: 12px;
774
+ }
775
+ .dchat-contextField code {
776
+ font-size: 11px;
777
+ color: var(--dsw-alias-label-tertiary);
778
+ }
779
+ .dchat-contextGuidance {
780
+ display: flex;
781
+ flex-direction: column;
782
+ gap: 6px;
783
+ }
784
+ .dchat-contextGuidanceHeader {
785
+ display: flex;
786
+ align-items: center;
787
+ justify-content: space-between;
788
+ gap: 10px;
789
+ }
790
+ .dchat-textarea,
791
+ .dchat-targetField input,
792
+ .dchat-input,
793
+ .dchat-select {
794
+ width: 100%;
795
+ box-sizing: border-box;
796
+ border: 1px solid var(--dsw-alias-border-l2);
797
+ border-radius: 6px;
798
+ background: var(--dsw-alias-bg-layer-1);
799
+ color: inherit;
800
+ font: inherit;
801
+ font-size: 12px;
802
+ padding: 6px 8px;
803
+ }
804
+ .dchat-textarea {
805
+ resize: vertical;
806
+ min-height: 72px;
807
+ }
808
+ .dchat-targetList {
809
+ display: flex;
810
+ flex-direction: column;
811
+ gap: 10px;
812
+ margin: 0;
813
+ padding: 0;
814
+ list-style: none;
815
+ }
816
+ .dchat-targetRow {
817
+ border: 1px solid var(--dsw-alias-border-l2);
818
+ border-radius: 8px;
819
+ padding: 10px 12px;
820
+ display: flex;
821
+ flex-direction: column;
822
+ gap: 8px;
823
+ background: var(--dsw-alias-bg-layer-1);
824
+ }
825
+ .dchat-targetHead {
826
+ display: flex;
827
+ align-items: center;
828
+ justify-content: space-between;
829
+ gap: 10px;
830
+ }
831
+ .dchat-targetEnable,
832
+ .dchat-targetMerge {
833
+ display: flex;
834
+ align-items: center;
835
+ gap: 6px;
836
+ font-size: 12px;
837
+ color: var(--dsw-alias-label-secondary);
838
+ }
839
+ .dchat-targetGrid {
840
+ display: grid;
841
+ grid-template-columns: 1fr 1fr;
842
+ gap: 8px;
843
+ }
844
+ .dchat-targetField {
845
+ display: flex;
846
+ flex-direction: column;
847
+ gap: 3px;
848
+ font-size: 12px;
849
+ }
850
+ .dchat-scopeGrid {
851
+ display: flex;
852
+ flex-direction: column;
853
+ gap: 12px;
854
+ }
855
+ .dchat-scopeRow {
856
+ display: flex;
857
+ flex-direction: column;
858
+ gap: 4px;
859
+ }
860
+ .dchat-scopeLabel {
861
+ font-size: 12px;
862
+ color: var(--dsw-alias-label-secondary);
863
+ }
864
+ .dchat-panelSections {
865
+ display: flex;
866
+ flex-direction: column;
867
+ gap: 8px;
868
+ }
869
+ .dchat-panelSectionsHead,
870
+ .dchat-panelSectionsRow {
871
+ display: flex;
872
+ align-items: center;
873
+ gap: 12px;
874
+ }
875
+ /* 名称占满剩余宽度,两个勾选框固定靠右:窄栏下也不会把名称压成竖排。 */
876
+ .dchat-panelSectionsName {
877
+ flex: 1 1 auto;
878
+ min-width: 0;
879
+ font-size: 13px;
880
+ overflow-wrap: anywhere;
881
+ }
882
+ .dchat-panelSectionsHead > .dchat-scopeLabel:first-child {
883
+ flex: 1 1 auto;
884
+ min-width: 0;
885
+ }
886
+ .dchat-panelSectionsHead > .dchat-scopeLabel:not(:first-child),
887
+ .dchat-panelSectionsCheck {
888
+ flex: none;
889
+ width: 48px;
890
+ text-align: center;
891
+ }
892
+ .dchat-panelSectionsCheck {
893
+ display: flex;
894
+ justify-content: center;
895
+ }
896
+ .dchat-panelSectionsCheck input {
897
+ margin: 0;
898
+ }
899
+ .dchat-notice {
900
+ margin: 0;
901
+ font-size: 12px;
902
+ color: var(--dsw-alias-state-success-primary);
903
+ }
904
+ .dchat-deliveryRow {
905
+ display: flex;
906
+ align-items: center;
907
+ justify-content: space-between;
908
+ gap: 12px;
909
+ }
910
+ .dchat-deliveryMeta {
911
+ display: flex;
912
+ flex-direction: column;
913
+ gap: 2px;
914
+ min-width: 0;
915
+ }
916
+ .dchat-deliveryMeta small {
917
+ font-size: 12px;
918
+ color: var(--dsw-alias-label-secondary);
919
+ /* anywhere 而不是 break-all:只在真的放不下时才断长串,不会把普通词也切碎。 */
920
+ overflow-wrap: anywhere;
921
+ }
922
+ .dchat-deliverySend {
923
+ display: flex;
924
+ flex-direction: column;
925
+ gap: 8px;
926
+ }
927
+ `;
928
+
929
+ /** 已安装实例计数:多个实例共用一份 <style>,最后一个卸载才移除。 */
930
+ let installations = 0;
931
+ let styleElement = null;
932
+
933
+ /**
934
+ * 安装共享样式。
935
+ *
936
+ * @param doc - 目标 document(默认 globalThis.document)。
937
+ * @returns 卸载函数。
938
+ */
939
+ export function installChatStyles(doc = globalThis.document) {
940
+ if (!doc?.head) return () => {};
941
+ installations += 1;
942
+ if (!styleElement) {
943
+ styleElement = doc.createElement('style');
944
+ styleElement.id = STYLE_ID;
945
+ styleElement.textContent = CSS;
946
+ doc.head.appendChild(styleElement);
947
+ }
948
+ let released = false;
949
+ return () => {
950
+ if (released) return;
951
+ released = true;
952
+ installations -= 1;
953
+ if (installations > 0) return;
954
+ try {
955
+ styleElement?.remove();
956
+ } finally {
957
+ styleElement = null;
958
+ }
959
+ };
960
+ }