dsh-codinput 0.3.6 → 0.3.8

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.
@@ -92,6 +92,15 @@ const ZH: Dict = {
92
92
  'perm.preset.danger-full-access': '完全权限',
93
93
  'set.enabled': '启用接管',
94
94
  'set.enabledDesc': '关闭即恢复官方输入框,草稿不丢;/codinput 同样可切换',
95
+ 'set.userMarkdown': '用户消息 Markdown 渲染',
96
+ 'set.userMarkdownDesc': '右侧自己发出的消息按 Markdown 排版(标题/列表/代码块等);关闭恢复官方纯文本',
97
+ 'umsg.copy': '复制',
98
+ 'umsg.copied': '已复制',
99
+ 'umsg.referenceSummary': '引用:{labels}',
100
+ 'umsg.clock.md': '{m}月{d}日',
101
+ 'umsg.clock.ymd': '{y}年{m}月{d}日',
102
+ 'umsg.extraBlock': '额外内容块',
103
+ 'umsg.image': '图片',
95
104
  'set.lineNumbers': '显示行号',
96
105
  'set.lineNumbersDesc': '当前行与行号高亮常开',
97
106
  'set.font': '编辑器字体',
@@ -196,6 +205,15 @@ const EN: Dict = {
196
205
  'perm.preset.danger-full-access': 'Full access',
197
206
  'set.enabled': 'Take over composer',
198
207
  'set.enabledDesc': 'Turning it off restores the official composer; the draft is kept. /codinput toggles it too.',
208
+ 'set.userMarkdown': 'Markdown for user messages',
209
+ 'set.userMarkdownDesc': 'Render your own (right-side) messages as Markdown — headings, lists, code blocks; off restores the official plain text',
210
+ 'umsg.copy': 'Copy',
211
+ 'umsg.copied': 'Copied',
212
+ 'umsg.referenceSummary': 'Referenced: {labels}',
213
+ 'umsg.clock.md': '{m}/{d}',
214
+ 'umsg.clock.ymd': '{y}/{m}/{d}',
215
+ 'umsg.extraBlock': 'Extra content block',
216
+ 'umsg.image': 'Image',
199
217
  'set.lineNumbers': 'Show line numbers',
200
218
  'set.lineNumbersDesc': 'The current line and its gutter number stay highlighted',
201
219
  'set.font': 'Editor font',
@@ -291,16 +309,18 @@ export function installI18n(locale: unknown): void {
291
309
  * 的翻译函数;宿主 face 未就绪或 bind 失败返回 null。结果按 ns 缓存。
292
310
  */
293
311
  export function bindForeign(ns: string): ((key: string, params?: TranslateParams) => string) | null {
294
- if (!foreignBinds.has(ns)) {
295
- let bound: ((key: string, params?: TranslateParams) => string) | null = null;
296
- try {
297
- bound = hostFace?.bind?.(ns) ?? null;
298
- } catch {
299
- bound = null;
300
- }
301
- foreignBinds.set(ns, bound);
312
+ const cached = foreignBinds.get(ns);
313
+ if (cached) return cached;
314
+ // 只缓存成功的绑定:宿主 locale 服务可能晚于首次调用就绪,null 缓存会让
315
+ // 之后所有文案永远拿不到官方词典。
316
+ let bound: ((key: string, params?: TranslateParams) => string) | null = null;
317
+ try {
318
+ bound = hostFace?.bind?.(ns) ?? null;
319
+ } catch {
320
+ bound = null;
302
321
  }
303
- return foreignBinds.get(ns) ?? null;
322
+ if (bound) foreignBinds.set(ns, bound);
323
+ return bound;
304
324
  }
305
325
 
306
326
  export function subscribeI18n(fn: () => void): () => void {
@@ -17,6 +17,7 @@ import { diagError, diagWindow, runGuarded } from './diag';
17
17
  import { loadPrefs, subscribePrefs } from './prefs';
18
18
  import { isAnySidebarInputCarried, subscribeSidebarInput } from './sideinput';
19
19
  import { barRegistration, CodinputBar } from './components/CodinputBar';
20
+ import { userMarkdownRegistrations } from './components/UserMessageMarkdown';
20
21
  import { sidebarBodyRegistration, sidebarTabDefinition, sidebarTitleRegistration } from './sidebar';
21
22
  import { settingsRegistration } from './settings';
22
23
  import { registerCodinputCommand } from './command';
@@ -55,7 +56,12 @@ export function apply(ctx: ClientContext): void {
55
56
  const orig = w.console.error.bind(w.console);
56
57
  w.console.error = (...args: unknown[]): void => {
57
58
  const first = args.find((a) => a instanceof Error);
58
- if (first) (w.__dshCodinputErrs as unknown as Error[]).push(first as Error);
59
+ if (first) {
60
+ // 与 diagError 同口径限长:长会话里 console.error 不应无界堆积。
61
+ const errs = w.__dshCodinputErrs as unknown as unknown[];
62
+ errs.push(first);
63
+ if (errs.length > 200) errs.splice(0, errs.length - 200);
64
+ }
59
65
  orig(...args);
60
66
  };
61
67
  }
@@ -121,6 +127,46 @@ export function apply(ctx: ClientContext): void {
121
127
  subscribeSidebarInput(reconcileBar);
122
128
  });
123
129
 
130
+ // ---- 用户消息 Markdown:conversation.chat.node 键控座 user/steering 遮蔽 ----
131
+ // 独立于接管开关,自带 prefs.userMarkdown;关闭即卸载条目把消息渲染还座
132
+ // 官方(同 key 低 priority 者胜,动态包守卫强制分配负秩)。
133
+ runGuarded('md.register', () => {
134
+ const mdSeats = userMarkdownRegistrations();
135
+ const mdMounted = new Map<string, () => void>();
136
+ const mdWanted = (): boolean => loadPrefs().userMarkdown;
137
+ const mountMdSeat = (seat: (typeof mdSeats)[number]): void => {
138
+ const key = seat.options.key ?? '';
139
+ if (mdMounted.has(key) || !mdWanted()) return;
140
+ mdMounted.set(key, ctx.slots.register(seat.options, seat.component));
141
+ diagWindow().__dshCodinputShell[`md.${key}`] = 'mounted';
142
+ };
143
+ const unmountMdSeat = (key: string): void => {
144
+ const disposer = mdMounted.get(key);
145
+ if (!disposer) return;
146
+ mdMounted.delete(key);
147
+ try {
148
+ disposer();
149
+ diagWindow().__dshCodinputShell[`md.${key}`] = 'unmounted';
150
+ } catch (error) {
151
+ diagError('md.dispose', error);
152
+ }
153
+ };
154
+ const reconcileMd = (): void => {
155
+ for (const seat of mdSeats) {
156
+ const key = seat.options.key ?? '';
157
+ if (mdWanted()) {
158
+ tryRegister(() => mountMdSeat(seat), `md.${key}`);
159
+ } else {
160
+ unmountMdSeat(key);
161
+ }
162
+ }
163
+ };
164
+ reconcileMd();
165
+ runGuarded('md.prefs', () => {
166
+ subscribePrefs(reconcileMd);
167
+ });
168
+ });
169
+
124
170
  // ---- 侧边模式:sidebarRightTabs 两段式注册(dockkit 承担停靠/浮动) ----
125
171
  runGuarded('sidebar.register', () => {
126
172
  ctx.inject(['slots', 'sidebarRightTabs', 'sidebarRight'], (scope: Record<string, unknown>) => {
@@ -17,6 +17,8 @@ export type CodinputMode = 'normal' | 'float' | 'side';
17
17
  export interface CodinputPrefs {
18
18
  /** 启用接管:关闭即恢复官方输入框(卸载遮蔽条目)。 */
19
19
  enabled: boolean;
20
+ /** 用户消息 Markdown 渲染(键控座 conversation.chat.node 的 user/steering 遮蔽)。 */
21
+ userMarkdown: boolean;
20
22
  /** 显示行号(当前行与行号高亮常开)。 */
21
23
  lineNumbers: boolean;
22
24
  /** 编辑器字体(默认 JetBrains Mono)。 */
@@ -43,6 +45,7 @@ export interface CodinputPrefs {
43
45
 
44
46
  export const DEFAULT_PREFS: CodinputPrefs = {
45
47
  enabled: true,
48
+ userMarkdown: true,
46
49
  lineNumbers: true,
47
50
  fontFamily: "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
48
51
  sendHotkey: 'mod+enter',
@@ -176,6 +176,13 @@ export function CodinputSettingsSection(): JSX.Element {
176
176
  <Row title={t("set.enabled")} desc={t("set.enabledDesc")}>
177
177
  <Switch on={prefs.enabled} label={t("set.enabled")} onToggle={() => update({ enabled: !prefs.enabled })} />
178
178
  </Row>
179
+ <Row title={t("set.userMarkdown")} desc={t("set.userMarkdownDesc")}>
180
+ <Switch
181
+ on={prefs.userMarkdown}
182
+ label={t("set.userMarkdown")}
183
+ onToggle={() => update({ userMarkdown: !prefs.userMarkdown })}
184
+ />
185
+ </Row>
179
186
  <Row title={t("set.lineNumbers")} desc={t("set.lineNumbersDesc")}>
180
187
  <Switch on={prefs.lineNumbers} label={t("set.lineNumbers")} onToggle={() => update({ lineNumbers: !prefs.lineNumbers })} />
181
188
  </Row>
@@ -412,13 +412,19 @@ button.dci-attach-body { color: inherit; font: inherit; text-align: left; backgr
412
412
  height: 36px; color: var(--dsw-alias-label-primary, inherit); border: none; border-radius: 18px; outline: none;
413
413
  padding: 0 14px; font-size: 13px; min-width: 260px; max-width: 320px; }
414
414
  .dci-set-input:focus { outline: 2px solid var(--dsw-alias-border-l3, transparent); }
415
+ /* 开关:官方 primitives Switch 逐值(Switch.module.css)。ON 轨道走
416
+ --dsw-alias-brand-primary——该 token 自 0.2.0-rc.2 起浅色主题即近黑(#0f1115)、
417
+ 深色主题近白(#f9fafb),桌面 rc.3 同款;同 token 跟随宿主主题,就是官方样式的来源。 */
415
418
  .dci-switch { box-sizing: border-box; position: relative; flex: none; width: 36px; height: 20px; padding: 2px;
416
- border: 0; border-radius: 10px; background: var(--dsw-alias-border-l3, color-mix(in srgb, currentColor 30%, transparent));
419
+ border: 0; border-radius: 999px; corner-shape: round;
420
+ background: var(--dsw-alias-border-l3, color-mix(in srgb, currentColor 30%, transparent));
417
421
  cursor: pointer; }
418
422
  .dci-switch[aria-checked="true"] { background: var(--dsw-alias-brand-primary, color-mix(in srgb, currentColor 70%, transparent)); }
419
- .dci-switch:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, currentColor); outline-offset: 2px; }
420
- .dci-switch::after { content: ''; display: block; width: 16px; height: 16px; border-radius: 50%;
423
+ .dci-switch:disabled { cursor: default; opacity: 0.5; }
424
+ .dci-switch:focus-visible { outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary, currentColor)); outline-offset: 2px; }
425
+ .dci-switch::after { content: ''; display: block; width: 16px; height: 16px; border-radius: 50%; corner-shape: round;
421
426
  background: var(--dsw-alias-label-primary-foreground, #fff); transition: transform 120ms ease; }
427
+ .dci-switch[aria-checked="false"]::after { background: var(--dsw-alias-switch-thumb, #fff); }
422
428
  .dci-switch[aria-checked="true"]::after { transform: translateX(16px); }
423
429
 
424
430
 
@@ -440,15 +446,17 @@ button.dci-attach-body { color: inherit; font: inherit; text-align: left; backgr
440
446
  padding: 4px 0 0; font-size: calc(var(--dsh-content-font-size-secondary, 13px) - 1px);
441
447
  line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
442
448
  justify-content: center; gap: 12px; display: flex; }
443
- .dci-stat-anchor { min-width: 0; display: inline-flex; position: relative; }
449
+ .dci-stat-anchor { min-width: 0; display: inline-flex; }
444
450
 
445
- /* 数据行详情面板(官方 bRhRbq 复刻:锚点上方弹出) */
446
- .dci-stat-panel { z-index: 1100; box-sizing: border-box; background: var(--dsw-menu-surface-fill, var(--dsw-specific-menu, rgba(30, 30, 34, 0.98)));
451
+ /* 数据行详情面板(官方 bRhRbq 复刻:portal 到 body、fixed、锚点上方弹出)。
452
+ 背景与圆角逐值同官方:--dsw-specific-menu 优先(macOS 桌面上官方把它覆写成不透明),
453
+ 旧宿主/皮肤缺该 token 时回退 --dsw-menu-surface-fill 链,不落到硬编码深色。 */
454
+ .dci-stat-panel { z-index: 1100; box-sizing: border-box; background: var(--dsw-specific-menu, var(--dsw-menu-surface-fill, rgba(30, 30, 34, 0.98)));
447
455
  backdrop-filter: var(--dsw-menu-backdrop-filter, none);
448
456
  color: var(--dsw-alias-label-secondary, inherit); --dsw-elevation-stroke-color: var(--dsw-alias-border-l1, transparent);
449
457
  width: max-content; min-width: min(300px, 100vw - 24px); max-width: min(440px, 100vw - 24px); text-align: left;
450
458
  box-shadow: var(--dsw-elevation-prominent, 0 10px 34px rgba(0, 0, 0, 0.35)); cursor: default; border: 0;
451
- border-radius: 12px; padding: 16px; font-size: 12px; line-height: 18px; position: absolute; bottom: calc(100% + 8px); left: 0; }
459
+ border-radius: var(--dsw-radius-lg, 12px); padding: 16px; font-size: 12px; line-height: 18px; position: fixed; }
452
460
  .dci-stat-panelTitle { color: var(--dsw-alias-label-primary, inherit); justify-content: space-between; gap: 16px;
453
461
  margin-bottom: 8px; font-weight: 500; display: flex; }
454
462
  .dci-stat-panelLabel { align-items: center; gap: 6px; min-width: 0; display: inline-flex; }
@@ -460,36 +468,40 @@ button.dci-attach-body { color: inherit; font: inherit; text-align: left; backgr
460
468
  .dci-stat-details dt, .dci-stat-details dd { min-width: 0; margin: 0; }
461
469
  .dci-stat-details dd { color: var(--dsw-alias-label-secondary, inherit); font-variant-numeric: tabular-nums; text-align: right; }
462
470
  .dci-stat-pill { font: inherit; box-sizing: border-box; max-width: 100%; color: var(--dsw-alias-label-tertiary, currentColor);
463
- white-space: nowrap; background: transparent; border: none; border-radius: 24px; align-items: center; gap: 6px;
471
+ white-space: nowrap; background: transparent; border: none; border-radius: 999px; corner-shape: round;
472
+ line-height: inherit; align-items: center; gap: 6px;
464
473
  padding: 1px 8px; display: inline-flex; cursor: pointer; font-variant-numeric: tabular-nums; }
465
474
  .dci-stat-pill svg { flex: none; width: 14px; height: 14px; }
466
- .dci-stat-pill:hover { background: var(--dsw-alias-interactive-bg-hover, color-mix(in srgb, currentColor 8%, transparent));
475
+ .dci-stat-pill:hover, .dci-stat-pill[aria-expanded="true"] { background: var(--dsw-alias-interactive-bg-hover, color-mix(in srgb, currentColor 8%, transparent));
467
476
  color: var(--dsw-alias-label-secondary, inherit); }
468
477
  .dci-stat-label { text-overflow: ellipsis; min-width: 0; overflow: hidden; }
469
478
  .dci-stat-sep { color: var(--dsw-alias-separator-primary, currentColor); margin: 0 6px; }
470
479
 
471
480
  /* ===== 上下文小圈(官方 JObwrW 复刻:双环仪表 + 分段面板)
472
481
  0.1.7-rc.2 起官方把它挪进卡片下方 dock 行(stats 胶囊之后),trigger 变为「圈+百分比」药丸 ===== */
473
- .dci-gauge-root { flex: none; display: inline-flex; position: relative; }
482
+ .dci-gauge-root { flex: none; display: inline-flex; }
474
483
  .dci-gauge-trigger { border-radius: var(--dsw-radius-sm, 8px); color: var(--dsw-alias-label-tertiary, currentColor);
475
- font-family: inherit; font-size: 13px; font-variant-numeric: tabular-nums; line-height: 20px; white-space: nowrap;
484
+ font-family: inherit; font-size: var(--dsh-content-font-size-secondary, 13px); font-variant-numeric: tabular-nums;
485
+ line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px)); white-space: nowrap;
476
486
  cursor: pointer; background: transparent; border: none; flex: none; align-items: center; gap: 6px; padding: 1px 8px; display: inline-flex; }
477
487
  .dci-gauge-trigger:hover, .dci-gauge-trigger[aria-expanded="true"] { background: var(--dsw-alias-interactive-bg-hover, color-mix(in srgb, currentColor 8%, transparent));
478
488
  color: var(--dsw-alias-label-secondary, inherit); }
479
489
  .dci-gauge-track { fill: none; stroke: var(--dsw-alias-border-l3, currentColor); stroke-width: 2px; }
480
490
  .dci-gauge-fill { fill: none; stroke: var(--dsw-alias-label-tertiary, currentColor); stroke-width: 2px; stroke-linecap: round; }
481
- .dci-gauge-panel { z-index: 1100; box-sizing: border-box; background: var(--dsw-menu-surface-fill, var(--dsw-specific-menu, rgba(30, 30, 34, 0.98)));
491
+ /* 面板 portal 到 body + fixed,left/top 由 useAnchoredPanel(官方 useAnchoredPosition
492
+ 移植:面板左缘对齐锚点左缘、面板底距锚点顶 8px、视口 margin 12 夹取)下发。 */
493
+ .dci-gauge-panel { z-index: 1100; box-sizing: border-box; background: var(--dsw-specific-menu, var(--dsw-menu-surface-fill, rgba(30, 30, 34, 0.98)));
482
494
  backdrop-filter: var(--dsw-menu-backdrop-filter, none);
483
495
  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1, transparent); width: min(264px, 100vw - 24px);
484
496
  box-shadow: var(--dsw-elevation-prominent, 0 10px 34px rgba(0, 0, 0, 0.35));
485
497
  color: var(--dsw-alias-label-secondary, inherit); cursor: default; border: 0; border-radius: var(--dsw-radius-lg, 12px); padding: 12px;
486
- font-size: 12px; line-height: 20px; position: absolute; bottom: calc(100% + 8px); right: 0; }
498
+ font-size: 12px; line-height: 20px; position: fixed; }
487
499
  .dci-gauge-header { align-items: center; gap: 6px; display: flex; }
488
500
  .dci-gauge-figures { font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-primary, inherit); margin-left: auto; font-weight: 500; }
489
501
  .dci-gauge-percent { color: var(--dsw-alias-label-primary, inherit); font-weight: 500; }
490
502
  .dci-gauge-headline { color: var(--dsw-alias-label-tertiary, currentColor); }
491
503
  .dci-gauge-bar { background: var(--dsw-alias-interactive-bg-hover, color-mix(in srgb, currentColor 8%, transparent));
492
- border-radius: 999px; gap: 1px; height: 4px; margin: 10px 0 12px; display: flex; overflow: hidden; }
504
+ border-radius: 999px; corner-shape: round; gap: 1px; height: 4px; margin: 10px 0 12px; display: flex; overflow: hidden; }
493
505
  .dci-gauge-segment { background: var(--meter-tint, var(--dsw-alias-label-tertiary, currentColor)); border-radius: 1px;
494
506
  flex: 0 0 auto; min-width: 2px; height: 100%; }
495
507
  .dci-gauge-swatch { background: var(--meter-tint, currentColor); vertical-align: baseline; border-radius: 2px;
@@ -537,12 +549,134 @@ button.dci-attach-body { color: inherit; font: inherit; text-align: left; backgr
537
549
  .dci-btn-primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover, currentColor); }
538
550
  .dci-btn-cancel { min-width: 72px; }
539
551
  .dci-btn-confirm { min-width: 136px; }
552
+
553
+ /* ===== 用户消息 Markdown(chat.node 键控座遮蔽条目)=====
554
+ 气泡/操作行/附件行取值逐条移植官方 MessageItem.module.css 与
555
+ MessageIconActions.module.css;Markdown 排版逐条移植官方
556
+ MarkdownText.module.css;引用 chip 逐条移植官方 user-text.module.css。
557
+ 官方走 --dsw-* token 的取值这里同样走 token(换肤全盘跟随)。 */
558
+ .dci-umsg-row { flex-direction: column; align-items: flex-end; gap: 6px; display: flex; }
559
+ .dci-umsg-stack { min-width: 0; max-width: min(calc(var(--dsh-chat-content-width, 748px) * 0.702), 82%);
560
+ flex-direction: column; align-items: flex-end; gap: 8px; display: flex; }
561
+ .dci-umsg-bubble { background: var(--dsw-specific-bubble); border-radius: var(--dsw-radius-xl); max-width: 100%;
562
+ font-size: var(--dsh-content-font-size, 14px); line-height: calc(22px + var(--dsh-content-font-delta, 0px));
563
+ color: var(--dsw-alias-label-primary); white-space: pre-wrap; word-break: break-word; padding: 10px 16px; }
564
+ .dci-umsg-refsum { color: var(--dsw-alias-label-tertiary);
565
+ font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(18px + var(--dsh-content-font-delta-secondary, 0px)); }
566
+ .dci-umsg-attrow { flex-wrap: wrap; justify-content: flex-end; gap: 8px; max-width: 100%; display: flex; }
567
+ .dci-umsg-file { border: 0.5px solid var(--dsw-alias-border-l2, #0000001f); border-radius: var(--dsw-radius-xl);
568
+ background: var(--dsw-specific-input-major, transparent); box-sizing: border-box; flex: 0 0 240px;
569
+ align-items: center; gap: 10px; width: 240px; min-height: 64px; padding: 8px 12px; display: inline-flex; }
570
+ .dci-umsg-fileglyph { flex: none; width: 28px; height: 28px; }
571
+ .dci-umsg-filecontent { flex-direction: column; flex: 1; min-width: 0; display: flex; }
572
+ .dci-umsg-filename { white-space: nowrap; text-overflow: ellipsis; color: var(--dsw-alias-label-primary);
573
+ font-size: 14px; font-weight: 500; line-height: 22px; overflow: hidden; }
574
+ .dci-umsg-filemeta { white-space: nowrap; text-overflow: ellipsis; color: var(--dsw-alias-label-tertiary, #00000073);
575
+ font-size: 12px; line-height: 15px; overflow: hidden; }
576
+ .dci-umsg-actions { height: calc(28px + var(--dsh-content-font-delta, 0px)); align-items: center; gap: 8px; display: flex; }
577
+ .dci-umsg-time { font-size: var(--dsh-content-font-size-secondary, 13px);
578
+ line-height: calc(24px + var(--dsh-content-font-delta, 0px)); color: var(--dsw-alias-label-tertiary); white-space: nowrap; padding-right: 12px; }
579
+ .dci-umsg-action { width: calc(28px + var(--dsh-content-font-delta, 0px)); height: calc(28px + var(--dsh-content-font-delta, 0px));
580
+ border-radius: var(--dsw-radius-sm); color: var(--dsw-alias-label-tertiary); cursor: pointer; background: 0 0;
581
+ border: none; justify-content: center; align-items: center; padding: 6px; display: inline-flex; }
582
+ .dci-umsg-action svg { width: calc(15px + var(--dsh-content-font-delta, 0px)); height: calc(15px + var(--dsh-content-font-delta, 0px)); }
583
+ .dci-umsg-action:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-secondary); }
584
+ /* 悬停显形(官方 MessageItem.module.css 同款):data-actions-reveal 挂在 assistant 的
585
+ turn-tail 根上、不在用户消息的祖先链里,用户消息的实际驱动只有 flowItem 的
586
+ data-chat-flow-kind 兄弟关系(后一条 user/steering 存在即隐藏,悬停/聚焦显形)。 */
587
+ @media (hover: hover) {
588
+ :is([data-chat-flow-kind="user"], [data-chat-flow-kind="steering"]):has(~ :is([data-chat-flow-kind="user"], [data-chat-flow-kind="steering"])) .dci-umsg-actions { opacity: 0; transition: opacity 80ms; }
589
+ :is([data-chat-flow-kind="user"], [data-chat-flow-kind="steering"]):hover .dci-umsg-actions,
590
+ :is([data-chat-flow-kind="user"], [data-chat-flow-kind="steering"]):focus-within .dci-umsg-actions { opacity: 1; }
591
+ }
592
+ .dci-umsg-extra { margin: 8px 0 0; padding: 8px; border: 0.5px solid var(--dsw-alias-border-l2);
593
+ border-radius: var(--dsw-radius-sm); font-family: var(--ds-font-family-code); font-size: 12px; line-height: 18px;
594
+ overflow: auto; max-height: 200px; white-space: pre; }
595
+
596
+ /* Markdown 排版(官方 MarkdownText.module.css 逐条移植,.markdown → 本根)。 */
597
+ .dci-umsg-md { min-width: 0; overflow-wrap: anywhere; font: var(--dsw-font-markdown-base);
598
+ color: var(--dsw-alias-label-primary); white-space: normal; }
599
+ .dci-umsg-md strong { font-weight: 600; }
600
+ .dci-umsg-md h1 { font: var(--dsw-font-markdown-h1); margin: 32px 0 16px; }
601
+ .dci-umsg-md h2 { font: var(--dsw-font-markdown-h2); margin: 32px 0 16px; }
602
+ .dci-umsg-md h3 { font: var(--dsw-font-markdown-h3); margin: 32px 0 16px; }
603
+ .dci-umsg-md h4 { font: var(--dsw-font-markdown-h4); margin: 16px 0; }
604
+ .dci-umsg-md :where(h5, h6) { font: var(--dsw-font-markdown-base-strong); margin: 16px 0; }
605
+ .dci-umsg-md :where(h1, h2, h3, h4, h5, h6) strong { font-weight: inherit; }
606
+ .dci-umsg-md p { margin: 16px 0; }
607
+ .dci-umsg-md :where(h4, h5, h6) + :where(ul, ol) { margin-top: 8px; }
608
+ .dci-umsg-md :where(h4, h5, h6):has(+ :where(ul, ol)) { margin-bottom: 8px; }
609
+ .dci-umsg-md a { color: var(--dsw-alias-link); font-weight: 500;
610
+ transition: box-shadow var(--ds-transition-duration) var(--ds-ease-in-out); position: relative; text-decoration: none;
611
+ border-left: 3px solid rgb(255 255 255 / 0); border-right: 3px solid rgb(255 255 255 / 0);
612
+ border-top: 2px solid rgb(255 255 255 / 0); border-bottom: 2px solid rgb(255 255 255 / 0);
613
+ margin-left: -3px; margin-right: -3px; }
614
+ .dci-umsg-md a:hover, .dci-umsg-md a:focus { outline: none; text-decoration: underline dotted var(--dsw-alias-link); text-underline-offset: 3px; }
615
+ .dci-umsg-md a:focus-visible { box-shadow: 0 0 0 2px var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
616
+ .dci-umsg-md :where(ul, ol) { margin: 16px 0; padding-left: 18px; }
617
+ .dci-umsg-md li:not(:first-child) { margin-top: 6px; }
618
+ .dci-umsg-md li > :where(ul, ol) { margin-top: 4px; }
619
+ .dci-umsg-md li::marker { line-height: 24px; color: var(--dsw-alias-label-secondary); }
620
+ .dci-umsg-md :where(ul, ol) ol { list-style-position: inside; padding-left: 0; }
621
+ .dci-umsg-md :where(ul, ol) ol li p { display: inline; }
622
+ .dci-umsg-md li > p { margin: 8px 0; }
623
+ .dci-umsg-md li > *:first-child { margin-top: 0; }
624
+ .dci-umsg-md hr { display: block; border: none; height: 0.5px; margin: 32px 0; background: var(--dsw-alias-border-l2); }
625
+ .dci-umsg-md blockquote { border-left: 2px solid var(--dsw-alias-label-caption); margin: 16px 0 0; padding-left: 14px; }
626
+ /* 围栏代码块:官方 CodeBlock.module.css 的 .block :where(pre) 逐值——底色/圆角/
627
+ 字体走官方 token(换肤跟随),气泡内靠底色与气泡面区分。语言横幅仍不做
628
+ (组件头注释的已知边界)。 */
629
+ .dci-umsg-md pre { margin: 16px 0; padding: 16px; font: var(--dsw-font-markdown-code-block, var(--ds-font-family-code));
630
+ background: var(--dsw-alias-markdown-code-block, color-mix(in srgb, currentColor 6%, transparent));
631
+ color: var(--dsw-alias-label-primary); white-space: pre-wrap; word-break: break-all; overflow-x: auto;
632
+ border-radius: var(--dsw-radius-lg); }
633
+ .dci-umsg-md pre code { font: inherit; background: none; padding: 0; }
634
+ .dci-umsg-md :not(pre) > code { display: inline-flex; align-items: center; box-sizing: border-box;
635
+ font: var(--dsw-font-markdown-code); font-family: var(--ds-font-family-code); font-size: 0.875em !important;
636
+ /* 底色与围栏代码块同一 token(--dsw-alias-markdown-code-block,产品决策 2026-10-29:
637
+ 两种代码面同色)。官方 --dsw-alias-markdown-inline-code 对页面底设计(深色
638
+ rgb(41,41,41)),进用户气泡(深色 rgb(44,44,46))只差 3/255 不可辨,故不取它。 */
639
+ background-color: var(--dsw-alias-markdown-code-block, color-mix(in srgb, currentColor 6%, transparent));
640
+ border: 0.5px solid var(--dsw-alias-border-l1);
641
+ border-radius: var(--dsw-radius-sm); padding: 0 5px; }
642
+ .dci-umsg-md :where(h1, h2, h3, h4, h5, h6) code { font: inherit; font-family: var(--ds-font-family-code); }
643
+ .dci-umsg-md input[type='checkbox'] { margin: 0 8px 0 0; accent-color: var(--dsw-alias-label-secondary); }
644
+ .dci-umsg-md img { display: block; width: auto; max-width: 100%; height: auto; margin: 0;
645
+ border-radius: var(--dsw-radius-md); background: var(--dsw-alias-bg-base); object-fit: contain; }
646
+ .dci-umsg-tscroll { max-width: 100%; overflow-x: auto; overscroll-behavior-x: contain; }
647
+ .dci-umsg-tscroll table { border-collapse: collapse; width: max-content; max-width: max-content; }
648
+ .dci-umsg-tscroll th { text-align: start; padding: 10px 16px; border-bottom: 0.5px solid var(--dsw-alias-border-l3);
649
+ border-top: none; font: var(--dsw-font-markdown-table-head); max-width: min(30vw, 320px); min-width: 100px; }
650
+ .dci-umsg-tscroll td { padding: 10px 16px; border-bottom: 0.5px solid var(--dsw-alias-border-l2);
651
+ font: var(--dsw-font-markdown-table); max-width: min(30vw, 320px); min-width: 100px; }
652
+ .dci-umsg-tscroll th:first-child, .dci-umsg-tscroll td:first-child { padding-left: 0; }
653
+ .dci-umsg-tscroll td:last-child { padding-right: 0; }
654
+ .dci-umsg-tscroll table code { font-size: 11px; }
655
+ .dci-umsg-md > :first-child, .dci-umsg-md p:first-child { margin-top: 0 !important; }
656
+ .dci-umsg-md > :last-child, .dci-umsg-md p:last-child { margin-bottom: 0 !important; }
657
+
658
+ /* 引用 chip(官方 user-text.module.css 逐条移植):inline 走消费方基线,
659
+ 可点 chip 用官方 fileMention 的链接语言。 */
660
+ .dci-umsg-chip { display: inline; max-width: 100%; margin: 0 2px; color: var(--dsw-alias-state-business-primary);
661
+ font-weight: 500; white-space: inherit; overflow-wrap: anywhere; text-align: inherit; }
662
+ button.dci-umsg-chip { margin: 0; padding: 0; border: none; background: none; font: inherit;
663
+ color: var(--dsw-alias-link); text-decoration: none; cursor: pointer; }
664
+ button.dci-umsg-chip:hover, button.dci-umsg-chip:focus { outline: none; text-decoration: underline dotted var(--dsw-alias-link); text-underline-offset: 3px; }
665
+ button.dci-umsg-chip:focus-visible { box-shadow: 0 0 0 2px var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
666
+ .dci-umsg-chip[data-chip="slash"] { font-family: var(--dsw-font-markdown-code-font-family); }
667
+ .dci-umsg-chipicon { width: 1em; height: 1em; margin-right: 4px; vertical-align: -0.125em; }
540
668
  `;
541
669
 
542
670
  export function injectStyles(): void {
543
- if (document.getElementById(STYLE_ID)) return;
544
- const el = document.createElement('style');
545
- el.id = STYLE_ID;
546
- el.textContent = CSS;
547
- document.head.appendChild(el);
671
+ const el = document.getElementById(STYLE_ID);
672
+ // 已有本插件样式(宿主热替换/原地更新插件后重 apply)时按内容原位刷新:
673
+ // 旧 <style> 还挂着会让新版新增的类(如 dci-umsg-*)永远注入不进去。
674
+ if (el) {
675
+ if (el.textContent !== CSS) el.textContent = CSS;
676
+ return;
677
+ }
678
+ const tag = document.createElement('style');
679
+ tag.id = STYLE_ID;
680
+ tag.textContent = CSS;
681
+ document.head.appendChild(tag);
548
682
  }