dsh-structured 0.8.3 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -1,510 +1,726 @@
1
1
  // Generated from src/client.js by scripts/build.mjs — edit src/, then run `npm run build`.
2
2
  // dsh-structured client module: renders "结论:" blockquotes as cards.
3
- /**
4
- * dsh-structured client
5
- *
6
- * 两层,都是纯 DOM 增强,不改消息内容、不替换 app root:
7
- * 1) 对话 markdown 视觉层:标题字号/层级、表格、代码块、引用块、列表、链接的排版与配色。
8
- * 2) 语义装饰:结论卡片、风险/验收等 callout 色调、标题关键词图标、代码块语言色。
9
- *
10
- * 只给宿主已有节点加 class/attribute,不搬 React 节点;markdown 重渲染后由 MutationObserver
11
- * 重新扫描。主题色全部走 --dsw-alias-*,自动跟随明暗主题。
12
- */
13
- window.__ModuleLoader__.load({
14
- id: 'dsh-structured',
15
- factory() {
16
- const CARD_ATTR = 'data-dsh-structured-card'
17
- const CARD_TYPE_ATTR = 'data-dsh-structured-card-type'
18
- const ICON_ATTR = 'data-dsh-structured-icon'
19
- const TONE_ATTR = 'data-dsh-structured-tone'
20
- const LANG_ATTR = 'data-dsh-structured-lang'
21
- const STATUS_ATTR = 'data-dsh-structured-status'
22
- const STATUS_TABLE_ATTR = 'data-dsh-structured-status-table'
23
- const LEAD_ATTR = 'data-dsh-structured-lead'
24
- const ROW_ATTR = 'data-dsh-structured-row'
25
- const ROOT_ATTR = 'data-dsh-structured'
26
- const STYLE_ID = 'dsh-structured-skin-style'
27
- const LEGACY_STYLE_ID = 'dsh-structured-card-style'
28
-
29
- const CARD_TYPES = new Map([
30
- ['结论', 'conclusion'], ['总结', 'conclusion'], ['答案', 'conclusion'],
31
- ['判断', 'decision'], ['决策', 'decision'],
32
- ['指标', 'metric'], ['数据', 'metric'],
33
- ['风险', 'risk'], ['注意', 'risk'], ['警告', 'risk'],
34
- ['下一步', 'next'], ['行动', 'next'], ['待办', 'next'],
35
- ['验收', 'accept'], ['通过', 'accept'], ['完成', 'accept'],
36
- ['重点', 'key'], ['关键', 'key'],
37
- ])
38
- const ICONS = [
39
- [/结论|总结|答案|判断/, '◆'],
40
- [/方案|计划|步骤|流程/, '▸'],
41
- [/风险|注意|警告|坑/, '⚠'],
42
- [/验收|检查|核对/, '✓'],
43
- [/对比|选型|取舍/, '⇄'],
44
- [/下一步|行动|待办/, '→'],
45
- [/背景|原因|根因/, '◎'],
46
- [/指标|数据|结果/, '▦'],
47
- ]
48
-
49
- const CSS = `
50
- [class*="_markdown_"]{
51
- --ds-accent:#2563eb;
52
- --ds-text:var(--dsw-alias-label-primary,#111827);
53
- --ds-muted:var(--dsw-alias-label-secondary,#6b7280);
54
- --ds-border:var(--dsw-alias-border-l2,rgba(127,127,127,.22));
55
- --ds-surface:var(--dsw-alias-bg-layer-1,#fff);
56
- --ds-surface-2:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.06));
57
- font-size:15px;line-height:1.72;letter-spacing:.01em;
58
- }
59
- [data-theme="dark"] [class*="_markdown_"]{--ds-accent:#60a5fa}
60
- [class*="_markdown_"] p{margin:.72em 0}
61
- [class*="_markdown_"] h1,[class*="_markdown_"] h2,[class*="_markdown_"] h3,
62
- [class*="_markdown_"] h4,[class*="_markdown_"] h5,[class*="_markdown_"] h6{
63
- color:var(--ds-text);line-height:1.32;letter-spacing:-.01em;
64
- }
65
- [class*="_markdown_"] h1{font-size:26px;font-weight:700;margin:32px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--ds-border)}
66
- [class*="_markdown_"] h2{font-size:21px;font-weight:700;margin:28px 0 10px;padding-left:12px;border-left:3px solid var(--ds-accent)}
67
- [class*="_markdown_"] h3{font-size:17px;font-weight:650;margin:22px 0 8px}
68
- [class*="_markdown_"] h4{font-size:15px;font-weight:650;margin:16px 0 6px}
69
- [class*="_markdown_"] h5,[class*="_markdown_"] h6{font-size:13px;font-weight:700;margin:14px 0 6px;color:var(--ds-muted);text-transform:uppercase;letter-spacing:.06em}
70
- [class*="_markdown_"] h2[${ICON_ATTR}]::before,
71
- [class*="_markdown_"] h3[${ICON_ATTR}]::before{
72
- content:attr(${ICON_ATTR});
73
- display:inline-flex;align-items:center;justify-content:center;
74
- width:22px;height:22px;margin-right:8px;border-radius:6px;
75
- background:color-mix(in srgb,var(--ds-accent) 12%,transparent);
76
- color:var(--ds-accent);font-size:12px;font-weight:700;vertical-align:2px;
77
- }
78
- [class*="_markdown_"] strong{font-weight:700;color:var(--ds-text)}
79
- [class*="_markdown_"] a{color:var(--ds-accent);text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--ds-accent) 38%,transparent)}
80
- [class*="_markdown_"] a:hover{border-bottom-color:var(--ds-accent)}
81
- [class*="_markdown_"] ul,[class*="_markdown_"] ol{padding-left:1.5em;margin:.6em 0}
82
- [class*="_markdown_"] li{margin:.28em 0}
83
- [class*="_markdown_"] li::marker{color:var(--ds-accent)}
84
- [class*="_markdown_"] hr{height:1px;border:0;margin:28px 0;background:linear-gradient(90deg,transparent,var(--ds-border),transparent)}
85
- [class*="_markdown_"] table{
86
- width:100%;margin:18px 0;border-collapse:separate;border-spacing:0;
87
- border:1px solid var(--ds-border);border-radius:12px;overflow:hidden;font-size:13.5px;
88
- box-shadow:0 1px 2px rgba(0,0,0,.03);
89
- }
90
- [class*="_markdown_"] thead th{
91
- background:var(--ds-surface-2);
92
- font-size:11px!important;letter-spacing:.07em!important;color:var(--ds-muted);
93
- font-weight:700!important;text-align:left;padding:12px 18px!important;
94
- border-bottom:1px solid var(--ds-border);
95
- }
96
- [class*="_markdown_"] tbody td{
97
- padding:13px 18px!important;line-height:1.55;vertical-align:middle;border-left:0;border-right:0;
98
- border-bottom:1px solid color-mix(in srgb,var(--ds-border) 55%,transparent);
99
- word-break:break-word;
100
- }
101
- [class*="_markdown_"] tbody tr:nth-child(even){background:color-mix(in srgb,var(--ds-surface-2) 45%,transparent)}
102
- [class*="_markdown_"] tbody tr:hover{background:color-mix(in srgb,var(--ds-accent) 5%,transparent)}
103
- [class*="_markdown_"] tbody tr:last-child td{border-bottom:0}
104
- [class*="_markdown_"] table:has(thead th:nth-child(2):last-child) th:first-child,
105
- [class*="_markdown_"] table:has(thead th:nth-child(2):last-child) td:first-child{width:30%}
106
- [class*="_markdown_"] table:not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) th:first-child,
107
- [class*="_markdown_"] table:not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) td:first-child{width:24%}
108
- [class*="_markdown_"] table:not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) th:nth-child(2),
109
- [class*="_markdown_"] table:not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) td:nth-child(2){width:22%}
110
- [class*="_markdown_"] table[${STATUS_TABLE_ATTR}] th:first-child,
111
- [class*="_markdown_"] table[${STATUS_TABLE_ATTR}] td:first-child{
112
- width:68px;text-align:center;padding:7px 8px!important;font-size:11px;font-weight:700;
113
- white-space:nowrap;vertical-align:middle;border-radius:7px;
114
- }
115
- [class*="_markdown_"] table[${STATUS_TABLE_ATTR}] tbody tr[${ROW_ATTR}="success"] td:first-child{background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#16a34a) 12%,transparent);color:var(--dsw-alias-state-success-primary,#16a34a)}
116
- [class*="_markdown_"] table[${STATUS_TABLE_ATTR}] tbody tr[${ROW_ATTR}="warn"] td:first-child{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#d97706) 12%,transparent);color:var(--dsw-alias-state-warn-primary,#d97706)}
117
- [class*="_markdown_"] table[${STATUS_TABLE_ATTR}] tbody tr[${ROW_ATTR}="error"] td:first-child{background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#dc2626) 12%,transparent);color:var(--dsw-alias-state-error-primary,#dc2626)}
118
- [class*="_markdown_"] p[${LEAD_ATTR}="table"]{
119
- border-left:2px solid var(--ds-accent);padding-left:10px;margin:10px 0 8px;
120
- font-size:14px;color:var(--ds-text);
121
- }
122
- [class*="_markdown_"] p[${LEAD_ATTR}="table"] strong{color:var(--ds-accent)}
123
- [class*="_markdown_"] h2+table,[class*="_markdown_"] h2+p,[class*="_markdown_"] h3+table,[class*="_markdown_"] h3+p{margin-top:8px}
124
- [class*="_markdown_"] :not(pre)>code{
125
- background:var(--ds-surface-2);border:1px solid var(--ds-border);border-radius:6px;
126
- padding:.12em .38em;font-size:.9em;color:var(--ds-accent);overflow-wrap:anywhere;
127
- }
128
- [class*="_markdown_"] .md-code-block{
129
- margin:16px 0;border:1px solid var(--ds-border);border-radius:12px;overflow:hidden;
130
- background:var(--ds-surface-2);box-shadow:0 1px 2px rgba(0,0,0,.04);
131
- }
132
- [class*="_markdown_"] .md-code-block > [class*="_bannerWrap"]{
133
- margin:0;padding:0;border:0;background:none;
134
- }
135
- [class*="_markdown_"] .md-code-block [data-code-block-banner]{
136
- display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0;border-radius:0;
137
- padding:6px 12px;font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
138
- color:var(--ds-muted);border-bottom:1px solid var(--ds-border);
139
- background:linear-gradient(90deg,color-mix(in srgb,var(--ds-code-accent,var(--ds-accent)) 14%,transparent),transparent 72%);
140
- }
141
- [class*="_markdown_"] .md-code-block [data-code-block-banner] > [class*="_heading"]{
142
- flex:1;min-width:0;justify-content:flex-start;text-align:left;
143
- }
144
- [class*="_markdown_"] .md-code-block [data-code-block-banner] > [class*="_actions"]{
145
- margin-left:auto;flex:none;
146
- }
147
- [class*="_markdown_"] .md-code-block > [data-code-block-content]{padding:0}
148
- [class*="_markdown_"] .md-code-block [data-code-block-banner]::before{
149
- content:"";width:8px;height:8px;border-radius:50%;flex:none;
150
- background:var(--ds-code-accent,var(--ds-accent));
151
- box-shadow:0 0 0 3px color-mix(in srgb,var(--ds-code-accent,var(--ds-accent)) 18%,transparent);
152
- }
153
- [class*="_markdown_"] .md-code-block pre{margin:0;padding:14px 16px;font-size:13px;line-height:1.65;overflow:auto}
154
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="js"]{--ds-code-accent:#eab308}
155
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="ts"],
156
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="tsx"]{--ds-code-accent:#3b82f6}
157
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="py"],
158
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="python"]{--ds-code-accent:#22c55e}
159
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="bash"],
160
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="sh"],
161
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="powershell"]{--ds-code-accent:#14b8a6}
162
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="json"]{--ds-code-accent:#a855f7}
163
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="html"],
164
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="css"]{--ds-code-accent:#f97316}
165
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="sql"]{--ds-code-accent:#06b6d4}
166
- [class*="_markdown_"] blockquote{
167
- margin:14px 0;padding:10px 14px;border-left:3px solid var(--ds-accent);
168
- background:color-mix(in srgb,var(--ds-accent) 6%,transparent);border-radius:0 10px 10px 0;
169
- }
170
- [class*="_markdown_"] blockquote[${TONE_ATTR}="warn"]{--ds-accent:var(--dsw-alias-state-warn-primary,#d97706)}
171
- [class*="_markdown_"] blockquote[${TONE_ATTR}="success"]{--ds-accent:var(--dsw-alias-state-success-primary,#16a34a)}
172
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]{
173
- border:1px solid color-mix(in srgb,var(--ds-accent) 16%,var(--ds-border));
174
- border-left:3px solid var(--ds-accent);
175
- border-radius:12px;padding:14px 16px 15px;
176
- background:linear-gradient(135deg,color-mix(in srgb,var(--ds-accent) 6%,var(--ds-surface)),var(--ds-surface) 60%);
177
- box-shadow:0 1px 2px rgba(0,0,0,.04),0 12px 32px -24px color-mix(in srgb,var(--ds-accent) 32%,transparent);
178
- }
179
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child{
180
- display:flex;align-items:center;gap:9px;position:relative;
181
- font-size:13px;line-height:1.1;letter-spacing:.06em;text-transform:uppercase;
182
- color:var(--ds-accent);margin-bottom:8px;
183
- }
184
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child::before{
185
- content:"";display:inline-flex;flex:none;
186
- width:26px;height:26px;border-radius:7px;
187
- background:color-mix(in srgb,var(--ds-accent) 12%,transparent);
188
- }
189
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child::after{
190
- content:"";position:absolute;left:5.5px;top:5.5px;width:15px;height:15px;
191
- background-color:var(--ds-accent);
192
- -webkit-mask:var(--ds-card-icon) center/contain no-repeat;
193
- mask:var(--ds-card-icon) center/contain no-repeat;
194
- }
195
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:nth-child(2)>strong:first-child{
196
- display:block;font-size:16px;font-weight:700;line-height:1.45;
197
- letter-spacing:-.01em;color:var(--ds-text);margin-bottom:4px;
198
- }
199
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p{font-size:15.5px;line-height:1.7;font-weight:450;color:var(--ds-text)}
200
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p strong{font-weight:700;color:var(--ds-text)}
201
- [class*="_markdown_"] blockquote[${TONE_ATTR}]>p:first-child>strong:first-child{color:var(--ds-accent)}
202
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="conclusion"]{
203
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3l9 9-9 9-9-9 9-9z' fill='none' stroke='black' stroke-width='1.8' stroke-linejoin='round'/%3E%3C/svg%3E");
204
- }
205
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="decision"]{
206
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 7h6l4 10h6M4 17h6l4-10h6' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
207
- }
208
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="metric"]{
209
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 20V10M12 20V4M19 20v-7' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
210
- }
211
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="risk"]{
212
- --ds-accent:var(--dsw-alias-state-warn-primary,#d97706);
213
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 4l9 16H3L12 4z' fill='none' stroke='black' stroke-width='1.8' stroke-linejoin='round'/%3E%3Cpath d='M12 10v4M12 17v.5' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
214
- }
215
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="next"]{
216
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
217
- }
218
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="accept"]{
219
- --ds-accent:var(--dsw-alias-state-success-primary,#16a34a);
220
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 12.5l5 5L20 6.5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
221
- }
222
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="key"]{
223
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3l2.6 6.2 6.7.5-5.1 4.4 1.6 6.5L12 17.3 6.2 20.6l1.6-6.5L2.7 9.7l6.7-.5L12 3z' fill='none' stroke='black' stroke-width='1.6' stroke-linejoin='round'/%3E%3C/svg%3E");
224
- }
225
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="statement"]{
226
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3l9 9-9 9-9-9 9-9z' fill='none' stroke='black' stroke-width='1.8' stroke-linejoin='round'/%3E%3C/svg%3E");
227
- }
228
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="statement"]>p:first-child>strong:first-child{
229
- font-size:16px;line-height:1.45;letter-spacing:-.01em;text-transform:none;
230
- color:var(--ds-text);margin-bottom:4px;
231
- }
232
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child{margin-top:0}
233
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:last-child{margin-bottom:0}
234
- [class*="_markdown_"] p[${STATUS_ATTR}],[class*="_markdown_"] li[${STATUS_ATTR}]{
235
- border-left:3px solid var(--ds-accent);background:color-mix(in srgb,var(--ds-accent) 7%,transparent);
236
- border-radius:0 6px 6px 0;padding:4px 10px;margin:.4em 0;
237
- }
238
- [class*="_markdown_"] p[${STATUS_ATTR}="success"],[class*="_markdown_"] li[${STATUS_ATTR}="success"]{--ds-accent:var(--dsw-alias-state-success-primary,#16a34a)}
239
- [class*="_markdown_"] p[${STATUS_ATTR}="warn"],[class*="_markdown_"] li[${STATUS_ATTR}="warn"]{--ds-accent:var(--dsw-alias-state-warn-primary,#d97706)}
240
- [class*="_markdown_"] p[${STATUS_ATTR}="error"],[class*="_markdown_"] li[${STATUS_ATTR}="error"]{--ds-accent:var(--dsw-alias-state-error-primary,#dc2626)}
241
- @media (max-width:720px){
242
- [class*="_markdown_"]{font-size:14.5px}
243
- [class*="_markdown_"] h1{font-size:22px}
244
- [class*="_markdown_"] h2{font-size:18px}
245
- [class*="_markdown_"] table{font-size:13px}
246
- [class*="_markdown_"] thead th,[class*="_markdown_"] tbody td{padding:7px 9px}
247
- }
248
- @media (prefers-reduced-motion:reduce){
249
- [class*="_markdown_"] *{transition:none!important;animation:none!important}
250
- }
251
- `
252
-
253
- function iconFor(text) {
254
- for (const [pattern, icon] of ICONS) if (pattern.test(text)) return icon
255
- return null
256
- }
257
-
258
- function firstParagraph(block) {
259
- return [...(block.children ?? [])].find((element) => element.tagName === 'P') ?? null
260
- }
261
-
262
- function labelOf(block) {
263
- const first = firstParagraph(block)
264
- const strong = first?.firstElementChild
265
- if (!strong || strong.tagName !== 'STRONG') return null
266
- return (strong.textContent ?? '').replace(/[::]\s*$/u, '').trim()
267
- }
268
-
269
- function cardTypeFor(block) {
270
- const label = labelOf(block)
271
- return label === null ? null : (CARD_TYPES.get(label) ?? null)
272
- }
273
-
274
- function isLabelLike(text) {
275
- return [...CARD_TYPES.keys()].some((label) => text.startsWith(label) && text !== label)
276
- }
277
-
278
- function inGenui(element) {
279
- try {
280
- return Boolean(element.closest?.('[data-genui], [class*="genui-root"]'))
281
- } catch {
282
- return false
283
- }
284
- }
285
-
286
- function statusFor(text) {
287
- if (/^✅/.test(text)) return 'success'
288
- if (/^⚠/.test(text)) return 'warn'
289
- if (/^❌/.test(text)) return 'error'
290
- return null
291
- }
292
-
293
- function isStatementCard(block) {
294
- const markdown = block.closest?.('[class*="_markdown_"]')
295
- if (!markdown) return false
296
- const first = [...markdown.querySelectorAll('blockquote')].find((element) => !inGenui(element))
297
- if (first !== block) return false
298
- const paragraphs = [...block.children].filter((element) => element.tagName === 'P')
299
- const firstParagraph = paragraphs[0]
300
- const strong = firstParagraph?.firstElementChild
301
- if (!strong || strong.tagName !== 'STRONG') return false
302
- let previous = strong.previousSibling
303
- while (previous) {
304
- if (previous.nodeType === 1) return false
305
- if (previous.nodeType === 3 && (previous.textContent ?? '').trim() !== '') return false
306
- previous = previous.previousSibling
307
- }
308
- const title = (strong.textContent ?? '').trim()
309
- if (title.length === 0 || isLabelLike(title)) return false
310
- let rest = ''
311
- for (let node = strong.nextSibling; node; node = node.nextSibling) rest += node.textContent ?? ''
312
- const secondBody = paragraphs.slice(1).some((element) => (element.textContent ?? '').trim().length > 0)
313
- return rest.trim().length > 0 || secondBody
314
- }
315
-
316
- function decorate(root) {
317
- for (const heading of root.querySelectorAll('[class*="_markdown_"] h2, [class*="_markdown_"] h3')) {
318
- if (inGenui(heading)) continue
319
- const icon = iconFor((heading.textContent ?? '').trim())
320
- if (icon === null) heading.removeAttribute(ICON_ATTR)
321
- else heading.setAttribute(ICON_ATTR, icon)
322
- }
323
-
324
- for (const block of root.querySelectorAll('blockquote')) {
325
- if (inGenui(block)) continue
326
- let type = cardTypeFor(block)
327
- if (type === null && isStatementCard(block)) type = 'statement'
328
- if (type === null) {
329
- block.removeAttribute(CARD_ATTR)
330
- block.removeAttribute(CARD_TYPE_ATTR)
331
- block.removeAttribute(LEAD_ATTR)
332
- block.removeAttribute(TONE_ATTR)
333
- continue
334
- }
335
- block.setAttribute(CARD_ATTR, '1')
336
- block.setAttribute(CARD_TYPE_ATTR, type)
337
- block.removeAttribute(LEAD_ATTR)
338
- block.setAttribute(TONE_ATTR, type === 'risk' ? 'warn' : type === 'accept' ? 'success' : 'info')
339
- }
340
-
341
- for (const element of root.querySelectorAll('[class*="_markdown_"] p, [class*="_markdown_"] li')) {
342
- if (inGenui(element) || element.closest('blockquote, table')) {
343
- element.removeAttribute(STATUS_ATTR)
344
- continue
345
- }
346
- const status = statusFor((element.textContent ?? '').trimStart())
347
- if (status === null) element.removeAttribute(STATUS_ATTR)
348
- else element.setAttribute(STATUS_ATTR, status)
349
- }
350
-
351
- for (const row of root.querySelectorAll('[class*="_markdown_"] table tr')) {
352
- if (inGenui(row) || row.closest('thead')) {
353
- row.removeAttribute(ROW_ATTR)
354
- continue
355
- }
356
- const status = statusFor((row.textContent ?? '').trimStart())
357
- if (status === null) row.removeAttribute(ROW_ATTR)
358
- else row.setAttribute(ROW_ATTR, status)
359
- }
360
-
361
- for (const table of root.querySelectorAll('[class*="_markdown_"] table')) {
362
- if (inGenui(table)) continue
363
- const rows = [...table.querySelectorAll('tbody tr')]
364
- const statusTable =
365
- rows.length > 0 &&
366
- rows.every((row) => statusFor((row.cells?.[0]?.textContent ?? '').trimStart()) !== null)
367
- if (statusTable) table.setAttribute(STATUS_TABLE_ATTR, '1')
368
- else table.removeAttribute(STATUS_TABLE_ATTR)
369
- }
370
-
371
- for (const paragraph of root.querySelectorAll('[class*="_markdown_"] p')) {
372
- if (inGenui(paragraph) || paragraph.closest('blockquote, table')) {
373
- paragraph.removeAttribute(LEAD_ATTR)
374
- continue
375
- }
376
- if (/^表格结论/.test((paragraph.textContent ?? '').trimStart())) paragraph.setAttribute(LEAD_ATTR, 'table')
377
- else paragraph.removeAttribute(LEAD_ATTR)
378
- }
379
-
380
- for (const code of root.querySelectorAll('[class*="_markdown_"] .md-code-block')) {
381
- if (inGenui(code)) continue
382
- const banner = code.querySelector('[data-code-block-banner]')
383
- const raw = (banner?.textContent ?? '').trim().split(/\s+/)[0] ?? ''
384
- const lang = raw.toLowerCase().replace(/[^a-z0-9+#]/g, '')
385
- if (lang.length === 0) code.removeAttribute(LANG_ATTR)
386
- else code.setAttribute(LANG_ATTR, lang)
387
- }
388
- }
389
-
390
- let active = false
391
- let disposeLayer = null
392
-
393
- const enable = () => {
394
- if (active) return
395
- if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') return
396
- if (document.body === null) return
397
- active = true
398
-
399
- document.getElementById(LEGACY_STYLE_ID)?.remove()
400
- document.getElementById(STYLE_ID)?.remove()
401
- const style = document.createElement('style')
402
- style.id = STYLE_ID
403
- style.textContent = CSS
404
- document.head.appendChild(style)
405
- document.documentElement.setAttribute(ROOT_ATTR, 'active')
406
- try {
407
- console.info('[dsh-structured] visual layer active')
408
- } catch {
409
- // console 不可用时忽略。
410
- }
411
-
412
- let disposed = false
413
- let scheduled = false
414
- let pending = null
415
- const run = () => {
416
- if (disposed) return
417
- scheduled = false
418
- pending = null
419
- decorate(document)
420
- }
421
- const schedule = () => {
422
- if (disposed || scheduled) return
423
- scheduled = true
424
- if (typeof requestAnimationFrame === 'function') pending = requestAnimationFrame(run)
425
- else pending = setTimeout(run, 16)
426
- }
427
- const observer = new MutationObserver(schedule)
428
- observer.observe(document.body, { childList: true, subtree: true, characterData: true })
429
- run()
430
-
431
- disposeLayer = () => {
432
- if (disposed) return
433
- disposed = true
434
- active = false
435
- disposeLayer = null
436
- observer.disconnect()
437
- if (pending !== null) {
438
- if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(pending)
439
- else clearTimeout(pending)
440
- }
441
- style.remove()
442
- document.documentElement.removeAttribute(ROOT_ATTR)
443
- for (const attr of [CARD_ATTR, CARD_TYPE_ATTR, ICON_ATTR, TONE_ATTR, LANG_ATTR, STATUS_ATTR, STATUS_TABLE_ATTR, LEAD_ATTR, ROW_ATTR]) {
444
- for (const element of document.querySelectorAll('[' + attr + ']')) element.removeAttribute(attr)
445
- }
446
- try {
447
- console.info('[dsh-structured] visual layer disposed')
448
- } catch {
449
- // console 不可用时忽略。
450
- }
451
- }
452
- }
453
-
454
- const disable = () => {
455
- const layer = disposeLayer
456
- disposeLayer = null
457
- if (typeof layer === 'function') layer()
458
- if (typeof document === 'undefined') return
459
- document.getElementById(STYLE_ID)?.remove()
460
- document.documentElement.removeAttribute(ROOT_ATTR)
461
- for (const attr of [CARD_ATTR, CARD_TYPE_ATTR, ICON_ATTR, TONE_ATTR, LANG_ATTR, STATUS_ATTR, STATUS_TABLE_ATTR, LEAD_ATTR, ROW_ATTR]) {
462
- for (const element of document.querySelectorAll('[' + attr + ']')) element.removeAttribute(attr)
463
- }
464
- }
465
-
466
- const applyVisual = (on) => {
467
- if (on) enable()
468
- else disable()
469
- }
470
-
471
- return {
472
- inject: [],
473
- apply(ctx) {
474
- // 默认开启,保持 0.8.1 的现有观感;随后由插件设置里的 visual 覆盖。
475
- enable()
476
- try {
477
- ctx?.inject?.(['configForms'], (child) => {
478
- try {
479
- const form = child.configForms?.get('dsh-structured')
480
- if (!form || typeof form.getSnapshot !== 'function') return
481
- const read = () => {
482
- try {
483
- const raw = form.getSnapshot()?.value?.visual
484
- const visual = raw && typeof raw.get === 'function' ? raw.get() : raw
485
- applyVisual(visual !== false)
486
- } catch {
487
- // 读取失败时保持当前状态,不打断回复。
488
- }
489
- }
490
- read()
491
- if (typeof form.subscribe === 'function') {
492
- child.effect?.(() => form.subscribe(read), 'dsh-structured: visual preference')
493
- }
494
- } catch {
495
- // configForms 不可用时保持默认开启。
496
- }
497
- })
498
- } catch {
499
- // 无 ctx.inject 的宿主保持默认开启。
500
- }
501
- try {
502
- ctx?.effect?.(() => disable, 'dsh-structured: visual layer')
503
- } catch {
504
- // 没有 effect 释放器时,视觉层保持到页面卸载。
505
- }
506
- return disable
507
- },
508
- }
509
- },
510
- })
3
+ /**
4
+ * dsh-structured client
5
+ *
6
+ * 两层,都是纯 DOM 增强,不改消息内容、不替换 app root:
7
+ * 1) 对话 markdown 视觉层:标题字号/层级、表格、代码块、引用块、列表、链接的排版与配色。
8
+ * 2) 语义装饰:结论卡片、风险/验收等 callout 色调、标题关键词图标、代码块语言色。
9
+ *
10
+ * 只给宿主已有节点加 class/attribute,不搬 React 节点;markdown 重渲染后由 MutationObserver
11
+ * 重新扫描。主题色全部走 --dsw-alias-*,自动跟随明暗主题。
12
+ */
13
+ window.__ModuleLoader__.load({
14
+ id: 'dsh-structured',
15
+ factory(require) {
16
+ const CARD_ATTR = 'data-dsh-structured-card'
17
+ const CARD_TYPE_ATTR = 'data-dsh-structured-card-type'
18
+ const ICON_ATTR = 'data-dsh-structured-icon'
19
+ const TONE_ATTR = 'data-dsh-structured-tone'
20
+ const LANG_ATTR = 'data-dsh-structured-lang'
21
+ const CODE_ATTR = 'data-dsh-structured-code'
22
+ const SKIP_ATTR = 'data-dsh-structured-skip'
23
+ const STATUS_ATTR = 'data-dsh-structured-status'
24
+ const STATUS_TABLE_ATTR = 'data-dsh-structured-status-table'
25
+ const LEAD_ATTR = 'data-dsh-structured-lead'
26
+ const ROW_ATTR = 'data-dsh-structured-row'
27
+ const ROOT_ATTR = 'data-dsh-structured'
28
+ const THEME_ATTR = 'data-dsh-structured-theme'
29
+ const THEMES = ['deepseek', 'codex', 'anthropic']
30
+ const STYLE_ID = 'dsh-structured-skin-style'
31
+ const LEGACY_STYLE_ID = 'dsh-structured-card-style'
32
+
33
+ /** 配置页常量:宿主插件详情页的 bundle 配置 slot(键 = 包名)。 */
34
+ const CONFIG_NS = 'dsh-structured'
35
+ const BUNDLE_CONFIG_SLOT = 'plugins.bundle.config'
36
+
37
+ /** 六个配置字段的界面文案;field 与 host Config schema 对齐,fallback 为 schema 默认值。 */
38
+ const CONFIG_FIELDS = [
39
+ { field: 'theme', kind: 'segmented', fallback: 'deepseek', label: '主题 / Theme', hint: '回复区配色,改完立即生效', options: [['deepseek', 'DeepSeek'], ['codex', 'Codex'], ['anthropic', 'Anthropic']] },
40
+ { field: 'visual', kind: 'switch', fallback: true, label: '视觉层 / Visual layer', hint: '关闭后不加载回复区样式(标题卡、表格、代码块皮肤)' },
41
+ { field: 'defaultEnabled', kind: 'switch', fallback: true, label: '新会话默认开启 / Default on', hint: '新会话默认注入结构化提示词' },
42
+ { field: 'tailReminder', kind: 'switch', fallback: true, label: '尾部提醒 / Tail reminder', hint: '在 system prompt 末尾追加一句结构化提醒' },
43
+ { field: 'includeSubagents', kind: 'switch', fallback: false, label: '子代理注入 / Subagents', hint: '子代理会话也注入(其产出不直接面向用户)' },
44
+ { field: 'genui', kind: 'segmented', fallback: 'auto', label: 'GenUI 联动 / GenUI routing', hint: 'auto:检测到 dsh-genui 才追加 dsh-ui 路由规则', options: [['auto', 'auto'], ['on', 'on'], ['off', 'off']] },
45
+ ]
46
+
47
+ const CARD_TYPES = new Map([
48
+ ['结论', 'conclusion'], ['总结', 'conclusion'], ['答案', 'conclusion'],
49
+ ['判断', 'decision'], ['决策', 'decision'],
50
+ ['指标', 'metric'], ['数据', 'metric'],
51
+ ['风险', 'risk'], ['注意', 'risk'], ['警告', 'risk'],
52
+ ['下一步', 'next'], ['行动', 'next'], ['待办', 'next'],
53
+ ['验收', 'accept'], ['通过', 'accept'], ['完成', 'accept'],
54
+ ['重点', 'key'], ['关键', 'key'],
55
+ ])
56
+ const ICONS = [
57
+ [/结论|总结|答案|判断/, '◆'],
58
+ [/方案|计划|步骤|流程/, '▸'],
59
+ [/风险|注意|警告|坑/, '⚠'],
60
+ [/验收|检查|核对/, '✓'],
61
+ [/对比|选型|取舍/, '⇄'],
62
+ [/下一步|行动|待办/, '→'],
63
+ [/背景|原因|根因/, '◎'],
64
+ [/指标|数据|结果/, '▦'],
65
+ ]
66
+
67
+ const CSS = `
68
+ [class*="_markdown_"]{
69
+ --ds-surface:#FFFFFF;--ds-surface-2:#F4F7FB;--ds-text:#0F172A;--ds-muted:#5B6B82;
70
+ --ds-border:#E2E8F0;--ds-accent:#2563EB;--ds-accent-text:#2563EB;--ds-success:#166534;--ds-warn:#92400E;--ds-error:#991B1B;
71
+ --ds-code-bg:#F6F8FA;--ds-code-text:#0F172A;
72
+ font-size:15px;line-height:1.72;letter-spacing:.01em;
73
+ }
74
+ body[data-ds-dark-theme] [class*="_markdown_"]{
75
+ --ds-surface:#111827;--ds-surface-2:#1F2937;--ds-text:#E5E7EB;--ds-muted:#9CA3AF;
76
+ --ds-border:#374151;--ds-accent:#60A5FA;--ds-accent-text:#60A5FA;--ds-success:#4ADE80;--ds-warn:#FBBF24;--ds-error:#F87171;
77
+ --ds-code-bg:#0B1220;--ds-code-text:#E5E7EB;
78
+ }
79
+ body[data-dsh-structured-theme="codex"] [class*="_markdown_"]{
80
+ --ds-surface:#FFFFFF;--ds-surface-2:#F0F7F4;--ds-text:#0D1117;--ds-muted:#4F625A;
81
+ --ds-border:#CFE3DB;--ds-accent:#10A37F;--ds-accent-text:#0B6B4F;--ds-success:#116329;--ds-warn:#7D4E00;--ds-error:#A40E26;
82
+ --ds-code-bg:#F0F7F4;--ds-code-text:#0D1117;
83
+ }
84
+ body[data-ds-dark-theme][data-dsh-structured-theme="codex"] [class*="_markdown_"]{
85
+ --ds-surface:#0B1512;--ds-surface-2:#12211B;--ds-text:#E6F2EC;--ds-muted:#9DB2A8;
86
+ --ds-border:#233A31;--ds-accent:#34D399;--ds-accent-text:#6EE7B7;--ds-success:#3FB950;--ds-warn:#D29922;--ds-error:#F85149;
87
+ --ds-code-bg:#07100D;--ds-code-text:#E6F2EC;
88
+ }
89
+ body[data-dsh-structured-theme="anthropic"] [class*="_markdown_"]{
90
+ --ds-surface:#FAF9F5;--ds-surface-2:#F5F1E8;--ds-text:#141413;--ds-muted:#6B6359;
91
+ --ds-border:#E5E0D5;--ds-accent:#CC785C;--ds-accent-text:#A14A2E;--ds-success:#266B4D;--ds-warn:#92400E;--ds-error:#8C1D18;
92
+ --ds-code-bg:#F5F1E8;--ds-code-text:#141413;
93
+ }
94
+ body[data-ds-dark-theme][data-dsh-structured-theme="anthropic"] [class*="_markdown_"]{
95
+ --ds-surface:#1F1E1D;--ds-surface-2:#2A2825;--ds-text:#F5F1E8;--ds-muted:#A8A29A;
96
+ --ds-border:#3A3733;--ds-accent:#E8A087;--ds-accent-text:#E8A087;--ds-success:#6FBF9A;--ds-warn:#E0A458;--ds-error:#F28B82;
97
+ --ds-code-bg:#171614;--ds-code-text:#F5F1E8;
98
+ }
99
+ [class*="_markdown_"] p{margin:.72em 0}
100
+ [class*="_markdown_"] h1,[class*="_markdown_"] h2,[class*="_markdown_"] h3,
101
+ [class*="_markdown_"] h4,[class*="_markdown_"] h5,[class*="_markdown_"] h6{
102
+ color:var(--ds-text);line-height:1.32;letter-spacing:-.01em;
103
+ }
104
+ [class*="_markdown_"] h1{font-size:26px;font-weight:700;margin:32px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--ds-border)}
105
+ [class*="_markdown_"] h2{font-size:21px;font-weight:700;margin:28px 0 10px;padding-left:12px;border-left:3px solid var(--ds-accent)}
106
+ [class*="_markdown_"] h3{font-size:17px;font-weight:650;margin:22px 0 8px}
107
+ [class*="_markdown_"] h4{font-size:15px;font-weight:650;margin:16px 0 6px}
108
+ [class*="_markdown_"] h5,[class*="_markdown_"] h6{font-size:13px;font-weight:700;margin:14px 0 6px;color:var(--ds-muted);text-transform:uppercase;letter-spacing:.06em}
109
+ [class*="_markdown_"] h2[${ICON_ATTR}]::before,
110
+ [class*="_markdown_"] h3[${ICON_ATTR}]::before{
111
+ content:attr(${ICON_ATTR});
112
+ display:inline-flex;align-items:center;justify-content:center;
113
+ width:22px;height:22px;margin-right:8px;border-radius:6px;
114
+ background:color-mix(in srgb,var(--ds-accent) 12%,transparent);
115
+ color:var(--ds-accent-text);font-size:12px;font-weight:700;vertical-align:2px;
116
+ }
117
+ [class*="_markdown_"] strong{font-weight:700;color:var(--ds-text)}
118
+ [class*="_markdown_"] a{color:var(--ds-accent-text);text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--ds-accent) 38%,transparent)}
119
+ [class*="_markdown_"] a:hover{border-bottom-color:var(--ds-accent)}
120
+ [class*="_markdown_"] ul,[class*="_markdown_"] ol{padding-left:1.5em;margin:.6em 0}
121
+ [class*="_markdown_"] li{margin:.28em 0}
122
+ [class*="_markdown_"] li::marker{color:var(--ds-accent-text)}
123
+ [class*="_markdown_"] hr{height:1px;border:0;margin:28px 0;background:linear-gradient(90deg,transparent,var(--ds-border),transparent)}
124
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]){
125
+ width:100%;margin:18px 0;border-collapse:separate;border-spacing:0;
126
+ border:1px solid var(--ds-border);border-radius:12px;overflow:hidden;font-size:13.5px;
127
+ box-shadow:0 1px 2px rgba(0,0,0,.03);
128
+ }
129
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) thead th{
130
+ background:var(--ds-surface-2);
131
+ font-size:11px!important;letter-spacing:.07em!important;color:var(--ds-muted);
132
+ font-weight:700!important;text-align:left;padding:12px 18px!important;
133
+ border-bottom:1px solid var(--ds-border);
134
+ }
135
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody td{
136
+ padding:13px 18px!important;line-height:1.55;vertical-align:middle;border-left:0;border-right:0;
137
+ border-bottom:1px solid color-mix(in srgb,var(--ds-border) 55%,transparent);
138
+ word-break:break-word;
139
+ }
140
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody tr:nth-child(even){background:color-mix(in srgb,var(--ds-surface-2) 45%,transparent)}
141
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody tr:hover{background:color-mix(in srgb,var(--ds-accent) 5%,transparent)}
142
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody tr:last-child td{border-bottom:0}
143
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):has(thead th:nth-child(2):last-child) th:first-child,
144
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):has(thead th:nth-child(2):last-child) td:first-child{width:30%}
145
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) th:first-child,
146
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) td:first-child{width:24%}
147
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) th:nth-child(2),
148
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) td:nth-child(2){width:22%}
149
+ [class*="_markdown_"] table:not([${SKIP_ATTR}])[${STATUS_TABLE_ATTR}] th:first-child,
150
+ [class*="_markdown_"] table:not([${SKIP_ATTR}])[${STATUS_TABLE_ATTR}] td:first-child{
151
+ width:68px;text-align:center;padding:7px 8px!important;font-size:11px;font-weight:700;
152
+ white-space:nowrap;vertical-align:middle;border-radius:7px;
153
+ }
154
+ [class*="_markdown_"] table:not([${SKIP_ATTR}])[${STATUS_TABLE_ATTR}] tbody tr[${ROW_ATTR}="success"] td:first-child{background:color-mix(in srgb,var(--ds-success) 12%,transparent);color:var(--ds-success)}
155
+ [class*="_markdown_"] table:not([${SKIP_ATTR}])[${STATUS_TABLE_ATTR}] tbody tr[${ROW_ATTR}="warn"] td:first-child{background:color-mix(in srgb,var(--ds-warn) 12%,transparent);color:var(--ds-warn)}
156
+ [class*="_markdown_"] table:not([${SKIP_ATTR}])[${STATUS_TABLE_ATTR}] tbody tr[${ROW_ATTR}="error"] td:first-child{background:color-mix(in srgb,var(--ds-error) 12%,transparent);color:var(--ds-error)}
157
+ [class*="_markdown_"] p[${LEAD_ATTR}="table"]{
158
+ border-left:2px solid var(--ds-accent);padding-left:10px;margin:10px 0 8px;
159
+ font-size:14px;color:var(--ds-text);
160
+ }
161
+ [class*="_markdown_"] p[${LEAD_ATTR}="table"] strong{color:var(--ds-accent-text)}
162
+ [class*="_markdown_"] h2+table,[class*="_markdown_"] h2+p,[class*="_markdown_"] h3+table,[class*="_markdown_"] h3+p{margin-top:8px}
163
+ [class*="_markdown_"] :not(pre)>code{
164
+ background:var(--ds-surface-2);border:1px solid var(--ds-border);border-radius:6px;
165
+ padding:.12em .38em;font-size:.9em;color:var(--ds-accent-text);overflow-wrap:anywhere;
166
+ }
167
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"]{
168
+ margin:16px 0;border:1px solid var(--ds-border);border-radius:12px;overflow:hidden;
169
+ background:var(--ds-code-bg);box-shadow:0 1px 2px rgba(0,0,0,.04);
170
+ }
171
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] > [class*="_bannerWrap"]{
172
+ margin:0;padding:0;border:0;background:none;
173
+ }
174
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] [data-code-block-banner]{
175
+ display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0;border-radius:0;
176
+ padding:6px 12px;font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
177
+ color:var(--ds-muted);border-bottom:1px solid var(--ds-border);
178
+ background:linear-gradient(90deg,color-mix(in srgb,var(--ds-code-accent,var(--ds-accent)) 14%,transparent),transparent 72%);
179
+ }
180
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] [data-code-block-banner] > [class*="_heading"]{
181
+ flex:1;min-width:0;justify-content:flex-start;text-align:left;
182
+ }
183
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] [data-code-block-banner] > [class*="_actions"]{
184
+ margin-left:auto;flex:none;
185
+ }
186
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] > [data-code-block-content]{padding:0}
187
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] [data-code-block-banner]::before{
188
+ content:"";width:8px;height:8px;border-radius:50%;flex:none;
189
+ background:var(--ds-code-accent,var(--ds-accent));
190
+ box-shadow:0 0 0 3px color-mix(in srgb,var(--ds-code-accent,var(--ds-accent)) 18%,transparent);
191
+ }
192
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] pre{margin:0;padding:14px 16px;font-size:13px;line-height:1.65;overflow:auto;color:var(--ds-code-text)}
193
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="js"]{--ds-code-accent:#eab308}
194
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="ts"],
195
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="tsx"]{--ds-code-accent:#3b82f6}
196
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="py"],
197
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="python"]{--ds-code-accent:#22c55e}
198
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="bash"],
199
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="sh"],
200
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="powershell"]{--ds-code-accent:#14b8a6}
201
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="json"]{--ds-code-accent:#a855f7}
202
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="html"],
203
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="css"]{--ds-code-accent:#f97316}
204
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="sql"]{--ds-code-accent:#06b6d4}
205
+ [class*="_markdown_"] blockquote{
206
+ margin:14px 0;padding:10px 14px;border-left:3px solid var(--ds-accent);
207
+ background:color-mix(in srgb,var(--ds-accent) 6%,transparent);border-radius:0 10px 10px 0;
208
+ }
209
+ [class*="_markdown_"] blockquote[${TONE_ATTR}="warn"]{--ds-accent:var(--ds-warn);--ds-accent-text:var(--ds-warn)}
210
+ [class*="_markdown_"] blockquote[${TONE_ATTR}="success"]{--ds-accent:var(--ds-success);--ds-accent-text:var(--ds-success)}
211
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]{
212
+ border:1px solid color-mix(in srgb,var(--ds-accent) 16%,var(--ds-border));
213
+ border-left:3px solid var(--ds-accent);
214
+ border-radius:12px;padding:14px 16px 15px;
215
+ background:linear-gradient(135deg,color-mix(in srgb,var(--ds-accent) 6%,var(--ds-surface)),var(--ds-surface) 60%);
216
+ box-shadow:0 1px 2px rgba(0,0,0,.04),0 12px 32px -24px color-mix(in srgb,var(--ds-accent) 32%,transparent);
217
+ }
218
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child{
219
+ display:flex;align-items:center;gap:9px;position:relative;
220
+ font-size:13px;line-height:1.1;letter-spacing:.06em;text-transform:uppercase;
221
+ color:var(--ds-accent-text);margin-bottom:8px;
222
+ }
223
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child::before{
224
+ content:"";display:inline-flex;flex:none;
225
+ width:26px;height:26px;border-radius:7px;
226
+ background:color-mix(in srgb,var(--ds-accent) 12%,transparent);
227
+ }
228
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child::after{
229
+ content:"";position:absolute;left:5.5px;top:5.5px;width:15px;height:15px;
230
+ background-color:var(--ds-accent);
231
+ -webkit-mask:var(--ds-card-icon) center/contain no-repeat;
232
+ mask:var(--ds-card-icon) center/contain no-repeat;
233
+ }
234
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:nth-child(2)>strong:first-child{
235
+ display:block;font-size:16px;font-weight:700;line-height:1.45;
236
+ letter-spacing:-.01em;color:var(--ds-text);margin-bottom:4px;
237
+ }
238
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p{font-size:15.5px;line-height:1.7;font-weight:450;color:var(--ds-text)}
239
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p strong{font-weight:700;color:var(--ds-text)}
240
+ [class*="_markdown_"] blockquote[${TONE_ATTR}]>p:first-child>strong:first-child{color:var(--ds-accent-text)}
241
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="conclusion"]{
242
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3l9 9-9 9-9-9 9-9z' fill='none' stroke='black' stroke-width='1.8' stroke-linejoin='round'/%3E%3C/svg%3E");
243
+ }
244
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="decision"]{
245
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 7h6l4 10h6M4 17h6l4-10h6' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
246
+ }
247
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="metric"]{
248
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 20V10M12 20V4M19 20v-7' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
249
+ }
250
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="risk"]{
251
+ --ds-accent:var(--ds-warn);--ds-accent-text:var(--ds-warn);
252
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 4l9 16H3L12 4z' fill='none' stroke='black' stroke-width='1.8' stroke-linejoin='round'/%3E%3Cpath d='M12 10v4M12 17v.5' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
253
+ }
254
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="next"]{
255
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
256
+ }
257
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="accept"]{
258
+ --ds-accent:var(--ds-success);--ds-accent-text:var(--ds-success);
259
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 12.5l5 5L20 6.5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
260
+ }
261
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="key"]{
262
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3l2.6 6.2 6.7.5-5.1 4.4 1.6 6.5L12 17.3 6.2 20.6l1.6-6.5L2.7 9.7l6.7-.5L12 3z' fill='none' stroke='black' stroke-width='1.6' stroke-linejoin='round'/%3E%3C/svg%3E");
263
+ }
264
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="statement"]{
265
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3l9 9-9 9-9-9 9-9z' fill='none' stroke='black' stroke-width='1.8' stroke-linejoin='round'/%3E%3C/svg%3E");
266
+ }
267
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="statement"]>p:first-child>strong:first-child{
268
+ font-size:16px;line-height:1.45;letter-spacing:-.01em;text-transform:none;
269
+ color:var(--ds-text);margin-bottom:4px;
270
+ }
271
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child{margin-top:0}
272
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:last-child{margin-bottom:0}
273
+ [class*="_markdown_"] p[${STATUS_ATTR}],[class*="_markdown_"] li[${STATUS_ATTR}]{
274
+ border-left:3px solid var(--ds-accent);background:color-mix(in srgb,var(--ds-accent) 7%,transparent);
275
+ border-radius:0 6px 6px 0;padding:4px 10px;margin:.4em 0;
276
+ }
277
+ [class*="_markdown_"] p[${STATUS_ATTR}="success"],[class*="_markdown_"] li[${STATUS_ATTR}="success"]{--ds-accent:var(--ds-success)}
278
+ [class*="_markdown_"] p[${STATUS_ATTR}="warn"],[class*="_markdown_"] li[${STATUS_ATTR}="warn"]{--ds-accent:var(--ds-warn)}
279
+ [class*="_markdown_"] p[${STATUS_ATTR}="error"],[class*="_markdown_"] li[${STATUS_ATTR}="error"]{--ds-accent:var(--ds-error)}
280
+ @media (max-width:720px){
281
+ [class*="_markdown_"]{font-size:14.5px}
282
+ [class*="_markdown_"] h1{font-size:22px}
283
+ [class*="_markdown_"] h2{font-size:18px}
284
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]){font-size:13px}
285
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) thead th,[class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody td{padding:7px 9px}
286
+ }
287
+ @media (prefers-reduced-motion:reduce){
288
+ [class*="_markdown_"] *{transition:none!important;animation:none!important}
289
+ }
290
+ `
291
+
292
+ function iconFor(text) {
293
+ for (const [pattern, icon] of ICONS) if (pattern.test(text)) return icon
294
+ return null
295
+ }
296
+
297
+ function firstParagraph(block) {
298
+ return [...(block.children ?? [])].find((element) => element.tagName === 'P') ?? null
299
+ }
300
+
301
+ function labelOf(block) {
302
+ const first = firstParagraph(block)
303
+ const strong = first?.firstElementChild
304
+ if (!strong || strong.tagName !== 'STRONG') return null
305
+ return (strong.textContent ?? '').replace(/[::]\s*$/u, '').trim()
306
+ }
307
+
308
+ function cardTypeFor(block) {
309
+ const label = labelOf(block)
310
+ return label === null ? null : (CARD_TYPES.get(label) ?? null)
311
+ }
312
+
313
+ function isLabelLike(text) {
314
+ return [...CARD_TYPES.keys()].some((label) => text.startsWith(label) && text !== label)
315
+ }
316
+
317
+ function inGenui(element) {
318
+ try {
319
+ return Boolean(element.closest?.('[data-genui], [class*="genui-root"]'))
320
+ } catch {
321
+ return false
322
+ }
323
+ }
324
+
325
+ function inDiff(element) {
326
+ try {
327
+ return Boolean(element.closest?.('[data-diff], .md-diff, [class*="_diff_"], [class*="diff-view"], [class*="diff-block"], [data-testid*="diff"]'))
328
+ } catch {
329
+ return false
330
+ }
331
+ }
332
+
333
+ function bannerLanguage(code) {
334
+ const banner = code.querySelector?.('[data-code-block-banner]')
335
+ const heading = banner?.querySelector?.('[class*="_heading"]')
336
+ const raw = ((heading?.textContent ?? banner?.textContent) ?? '').trim().split(/\s+/)[0] ?? ''
337
+ return raw.toLowerCase().replace(/[^a-z0-9+#]/g, '')
338
+ }
339
+
340
+ function isDiffCode(code) {
341
+ const lang = bannerLanguage(code)
342
+ return lang === 'diff' || lang === 'patch'
343
+ }
344
+
345
+ function statusFor(text) {
346
+ if (/^✅/.test(text)) return 'success'
347
+ if (/^⚠/.test(text)) return 'warn'
348
+ if (/^❌/.test(text)) return 'error'
349
+ return null
350
+ }
351
+
352
+ function isStatementCard(block) {
353
+ if (inDiff(block)) return false
354
+ const markdown = block.closest?.('[class*="_markdown_"]')
355
+ if (!markdown) return false
356
+ const first = [...markdown.querySelectorAll('blockquote')].find((element) => !inGenui(element) && !inDiff(element))
357
+ if (first !== block) return false
358
+ const paragraphs = [...block.children].filter((element) => element.tagName === 'P')
359
+ const firstParagraph = paragraphs[0]
360
+ const strong = firstParagraph?.firstElementChild
361
+ if (!strong || strong.tagName !== 'STRONG') return false
362
+ let previous = strong.previousSibling
363
+ while (previous) {
364
+ if (previous.nodeType === 1) return false
365
+ if (previous.nodeType === 3 && (previous.textContent ?? '').trim() !== '') return false
366
+ previous = previous.previousSibling
367
+ }
368
+ const title = (strong.textContent ?? '').trim()
369
+ if (title.length === 0 || isLabelLike(title)) return false
370
+ let rest = ''
371
+ for (let node = strong.nextSibling; node; node = node.nextSibling) rest += node.textContent ?? ''
372
+ const secondBody = paragraphs.slice(1).some((element) => (element.textContent ?? '').trim().length > 0)
373
+ return rest.trim().length > 0 || secondBody
374
+ }
375
+
376
+ function decorate(root) {
377
+ for (const heading of root.querySelectorAll('[class*="_markdown_"] h2, [class*="_markdown_"] h3')) {
378
+ if (inGenui(heading) || inDiff(heading)) continue
379
+ const icon = iconFor((heading.textContent ?? '').trim())
380
+ if (icon === null) heading.removeAttribute(ICON_ATTR)
381
+ else heading.setAttribute(ICON_ATTR, icon)
382
+ }
383
+
384
+ for (const block of root.querySelectorAll('blockquote')) {
385
+ if (inGenui(block) || inDiff(block)) continue
386
+ let type = cardTypeFor(block)
387
+ if (type === null && isStatementCard(block)) type = 'statement'
388
+ if (type === null) {
389
+ block.removeAttribute(CARD_ATTR)
390
+ block.removeAttribute(CARD_TYPE_ATTR)
391
+ block.removeAttribute(LEAD_ATTR)
392
+ block.removeAttribute(TONE_ATTR)
393
+ continue
394
+ }
395
+ block.setAttribute(CARD_ATTR, '1')
396
+ block.setAttribute(CARD_TYPE_ATTR, type)
397
+ block.removeAttribute(LEAD_ATTR)
398
+ block.setAttribute(TONE_ATTR, type === 'risk' ? 'warn' : type === 'accept' ? 'success' : 'info')
399
+ }
400
+
401
+ for (const element of root.querySelectorAll('[class*="_markdown_"] p, [class*="_markdown_"] li')) {
402
+ if (inGenui(element) || inDiff(element) || element.closest('blockquote, table')) {
403
+ element.removeAttribute(STATUS_ATTR)
404
+ continue
405
+ }
406
+ const status = statusFor((element.textContent ?? '').trimStart())
407
+ if (status === null) element.removeAttribute(STATUS_ATTR)
408
+ else element.setAttribute(STATUS_ATTR, status)
409
+ }
410
+
411
+ for (const row of root.querySelectorAll('[class*="_markdown_"] table tr')) {
412
+ if (inGenui(row) || inDiff(row) || row.closest('thead') || row.closest(`[${SKIP_ATTR}="table"]`)) {
413
+ row.removeAttribute(ROW_ATTR)
414
+ continue
415
+ }
416
+ const status = statusFor((row.textContent ?? '').trimStart())
417
+ if (status === null) row.removeAttribute(ROW_ATTR)
418
+ else row.setAttribute(ROW_ATTR, status)
419
+ }
420
+
421
+ for (const table of root.querySelectorAll('[class*="_markdown_"] table')) {
422
+ if (inGenui(table)) {
423
+ table.removeAttribute(SKIP_ATTR)
424
+ table.removeAttribute(STATUS_TABLE_ATTR)
425
+ continue
426
+ }
427
+ if (inDiff(table)) {
428
+ table.setAttribute(SKIP_ATTR, 'table')
429
+ table.removeAttribute(STATUS_TABLE_ATTR)
430
+ continue
431
+ }
432
+ table.removeAttribute(SKIP_ATTR)
433
+ const rows = [...table.querySelectorAll('tbody tr')]
434
+ const statusTable =
435
+ rows.length > 0 &&
436
+ rows.every((row) => statusFor((row.cells?.[0]?.textContent ?? '').trimStart()) !== null)
437
+ if (statusTable) table.setAttribute(STATUS_TABLE_ATTR, '1')
438
+ else table.removeAttribute(STATUS_TABLE_ATTR)
439
+ }
440
+
441
+ for (const paragraph of root.querySelectorAll('[class*="_markdown_"] p')) {
442
+ if (inGenui(paragraph) || inDiff(paragraph) || paragraph.closest('blockquote, table')) {
443
+ paragraph.removeAttribute(LEAD_ATTR)
444
+ continue
445
+ }
446
+ if (/^表格结论/.test((paragraph.textContent ?? '').trimStart())) paragraph.setAttribute(LEAD_ATTR, 'table')
447
+ else paragraph.removeAttribute(LEAD_ATTR)
448
+ }
449
+
450
+ for (const code of root.querySelectorAll('[class*="_markdown_"] .md-code-block')) {
451
+ if (inGenui(code)) {
452
+ code.removeAttribute(LANG_ATTR)
453
+ code.removeAttribute(CODE_ATTR)
454
+ code.removeAttribute(SKIP_ATTR)
455
+ continue
456
+ }
457
+ if (inDiff(code) || isDiffCode(code)) {
458
+ code.removeAttribute(LANG_ATTR)
459
+ code.removeAttribute(CODE_ATTR)
460
+ code.setAttribute(SKIP_ATTR, 'code')
461
+ continue
462
+ }
463
+ code.removeAttribute(SKIP_ATTR)
464
+ code.setAttribute(CODE_ATTR, '1')
465
+ const lang = bannerLanguage(code)
466
+ if (lang.length === 0) code.removeAttribute(LANG_ATTR)
467
+ else code.setAttribute(LANG_ATTR, lang)
468
+ }
469
+ }
470
+
471
+ /**
472
+ * 在插件板块的插件详情页注册配置区(plugins.bundle.config,键 = 包名)。
473
+ * 宿主不会从 Config schema 自动生成表单——配置页必须由插件自己的浏览器半侧注册;
474
+ * 宿主 describe 未服务本命名空间(whileServed 不触发)或 primitives 缺失时不出配置页,
475
+ * 且任何失败都不影响视觉层与消息渲染。
476
+ */
477
+ function setupConfigPage(child, form) {
478
+ if (typeof require !== 'function') return
479
+ if (typeof child?.configForms?.whileServed !== 'function') return
480
+ if (typeof child?.slots?.inject !== 'function' || typeof child?.slots?.register !== 'function') return
481
+ if (typeof form?.set !== 'function') return
482
+ let jsx
483
+ let jsxs
484
+ let ui
485
+ try {
486
+ const runtime = require('react/jsx-runtime')
487
+ jsx = runtime.jsx
488
+ jsxs = runtime.jsxs
489
+ ui = require('@deepseek-ai/dsh-client-ui-primitives')
490
+ if (typeof ui?.Switch !== 'function' || typeof ui?.SegmentedControl !== 'function') return
491
+ } catch {
492
+ // primitives 不可用(非 desktop 表层):不出配置页。
493
+ return
494
+ }
495
+
496
+ const labelStyle = { fontSize: '14px', lineHeight: '20px', color: 'var(--dsw-alias-label-primary, inherit)' }
497
+ const hintStyle = { fontSize: '12.5px', lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary, inherit)', marginTop: '2px' }
498
+ const rowStyle = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '16px', padding: '10px 0', borderBottom: '1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.18))' }
499
+ const noteStyle = { fontSize: '12.5px', lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary, inherit)', margin: '6px 0 2px' }
500
+
501
+ const fieldValue = (value, spec) => {
502
+ const raw = value?.[spec.field]
503
+ if (raw && typeof raw.get === 'function') return raw.get()
504
+ return raw === undefined || raw === null ? spec.fallback : raw
505
+ }
506
+
507
+ const renderRow = (state, setField, spec) => {
508
+ const writable = state.writable !== false
509
+ const control = spec.kind === 'switch'
510
+ ? jsx(ui.Switch, {
511
+ checked: fieldValue(state.value, spec) === true,
512
+ disabled: !writable,
513
+ label: spec.label,
514
+ onChange: (next) => setField(spec.field, next === true),
515
+ })
516
+ : jsx(ui.SegmentedControl, {
517
+ id: 'dsh-structured-' + spec.field,
518
+ value: String(fieldValue(state.value, spec)),
519
+ options: spec.options.map(([value, text]) => ({ value, label: text })),
520
+ disabled: !writable,
521
+ label: spec.label,
522
+ onChange: (next) => setField(spec.field, next),
523
+ })
524
+ return jsxs('div', {
525
+ style: rowStyle,
526
+ children: [
527
+ jsxs('div', { style: { minWidth: 0 }, children: [
528
+ jsx('div', { style: labelStyle, children: spec.label }),
529
+ jsx('div', { style: hintStyle, children: spec.hint }),
530
+ ] }),
531
+ control,
532
+ ],
533
+ })
534
+ }
535
+
536
+ const StructuredConfigCard = (props) => {
537
+ if (props.view !== undefined && props.view !== 'page') return null
538
+ const read = props.useStructuredConfig ?? props.structuredConfig
539
+ const state = typeof read === 'function' ? read((snapshot) => snapshot) : null
540
+ if (!state || state.status !== 'ready' || typeof state.value !== 'object' || state.value === null) {
541
+ return jsx('p', { style: noteStyle, children: '插件未加载或配置暂不可用。' })
542
+ }
543
+ const setField = typeof props.setField === 'function' ? props.setField : () => {}
544
+ return jsxs('div', {
545
+ 'data-dsh-structured-config': '1',
546
+ children: [
547
+ state.writable === false
548
+ ? jsx('p', { style: noteStyle, children: '当前配置被更高层覆盖(home patch / CLI overlay),此处只读。' })
549
+ : null,
550
+ jsx('div', { children: CONFIG_FIELDS.map((spec) => renderRow(state, setField, spec)) }),
551
+ jsx('p', { style: noteStyle, children: '会话级开关请用 /structured on | off(按会话生效,不写入这里)。' }),
552
+ ],
553
+ })
554
+ }
555
+
556
+ child.effect?.(
557
+ () =>
558
+ child.configForms.whileServed([CONFIG_NS], () =>
559
+ child.slots.inject(BUNDLE_CONFIG_SLOT, () =>
560
+ child.slots.register({
561
+ name: BUNDLE_CONFIG_SLOT,
562
+ key: 'dsh-structured',
563
+ inject: () => ({
564
+ hooks: { structuredConfig: form },
565
+ setField: (field, value) => {
566
+ try {
567
+ form.set(field, value)
568
+ } catch {
569
+ // 写入失败不打断页面;快照未变化即用户可见的反馈。
570
+ }
571
+ },
572
+ }),
573
+ }, StructuredConfigCard),
574
+ ),
575
+ ),
576
+ 'dsh-structured: bundle config page',
577
+ )
578
+ }
579
+
580
+ let active = false
581
+ let disposeLayer = null
582
+ let currentTheme = 'deepseek'
583
+
584
+ const applyTheme = (theme) => {
585
+ const safe = THEMES.includes(theme) ? theme : 'deepseek'
586
+ currentTheme = safe
587
+ if (typeof document === 'undefined') return
588
+ document.body?.setAttribute(THEME_ATTR, safe)
589
+ }
590
+
591
+ const enable = () => {
592
+ if (active) return
593
+ if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') return
594
+ if (document.body === null) return
595
+ active = true
596
+
597
+ document.getElementById(LEGACY_STYLE_ID)?.remove()
598
+ document.getElementById(STYLE_ID)?.remove()
599
+ const style = document.createElement('style')
600
+ style.id = STYLE_ID
601
+ style.textContent = CSS
602
+ document.head.appendChild(style)
603
+ document.documentElement.setAttribute(ROOT_ATTR, 'active')
604
+ document.body.setAttribute(THEME_ATTR, currentTheme)
605
+ try {
606
+ console.info('[dsh-structured] visual layer active')
607
+ } catch {
608
+ // console 不可用时忽略。
609
+ }
610
+
611
+ let disposed = false
612
+ let scheduled = false
613
+ let pending = null
614
+ const run = () => {
615
+ if (disposed) return
616
+ scheduled = false
617
+ pending = null
618
+ decorate(document)
619
+ }
620
+ const schedule = () => {
621
+ if (disposed || scheduled) return
622
+ scheduled = true
623
+ if (typeof requestAnimationFrame === 'function') pending = requestAnimationFrame(run)
624
+ else pending = setTimeout(run, 16)
625
+ }
626
+ const observer = new MutationObserver(schedule)
627
+ observer.observe(document.body, { childList: true, subtree: true, characterData: true })
628
+ run()
629
+
630
+ disposeLayer = () => {
631
+ if (disposed) return
632
+ disposed = true
633
+ active = false
634
+ disposeLayer = null
635
+ observer.disconnect()
636
+ if (pending !== null) {
637
+ if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(pending)
638
+ else clearTimeout(pending)
639
+ }
640
+ style.remove()
641
+ document.documentElement.removeAttribute(ROOT_ATTR)
642
+ document.body?.removeAttribute(THEME_ATTR)
643
+ for (const attr of [CARD_ATTR, CARD_TYPE_ATTR, ICON_ATTR, TONE_ATTR, LANG_ATTR, CODE_ATTR, SKIP_ATTR, STATUS_ATTR, STATUS_TABLE_ATTR, LEAD_ATTR, ROW_ATTR]) {
644
+ for (const element of document.querySelectorAll('[' + attr + ']')) element.removeAttribute(attr)
645
+ }
646
+ try {
647
+ console.info('[dsh-structured] visual layer disposed')
648
+ } catch {
649
+ // console 不可用时忽略。
650
+ }
651
+ }
652
+ }
653
+
654
+ const disable = () => {
655
+ const layer = disposeLayer
656
+ disposeLayer = null
657
+ if (typeof layer === 'function') layer()
658
+ if (typeof document === 'undefined') return
659
+ document.getElementById(STYLE_ID)?.remove()
660
+ document.documentElement.removeAttribute(ROOT_ATTR)
661
+ document.body?.removeAttribute(THEME_ATTR)
662
+ for (const attr of [CARD_ATTR, CARD_TYPE_ATTR, ICON_ATTR, TONE_ATTR, LANG_ATTR, CODE_ATTR, SKIP_ATTR, STATUS_ATTR, STATUS_TABLE_ATTR, LEAD_ATTR, ROW_ATTR]) {
663
+ for (const element of document.querySelectorAll('[' + attr + ']')) element.removeAttribute(attr)
664
+ }
665
+ }
666
+
667
+ const applyVisual = (on) => {
668
+ if (on) enable()
669
+ else disable()
670
+ }
671
+
672
+ return {
673
+ inject: [],
674
+ __test: { inDiff, isDiffCode, decorate, setupConfigPage },
675
+ apply(ctx) {
676
+ // 默认主题/视觉先启用;随后由 profile Config 的 theme/visual 覆盖。
677
+ enable()
678
+ try {
679
+ ctx?.inject?.(['configForms'], (child) => {
680
+ try {
681
+ const form = child.configForms?.get('dsh-structured')
682
+ if (!form || typeof form.getSnapshot !== 'function') return
683
+ const readValue = (value) => (value && typeof value.get === 'function' ? value.get() : value)
684
+ const read = () => {
685
+ try {
686
+ const value = form.getSnapshot()?.value ?? {}
687
+ applyTheme(readValue(value.theme))
688
+ applyVisual(readValue(value.visual) !== false)
689
+ } catch {
690
+ // 读取失败时保持当前状态,不打断回复。
691
+ }
692
+ }
693
+ read()
694
+ if (typeof form.subscribe === 'function') {
695
+ child.effect?.(() => form.subscribe(read), 'dsh-structured: theme/visual preferences')
696
+ }
697
+ } catch {
698
+ // configForms 不可用时保持默认开启。
699
+ }
700
+ })
701
+ } catch {
702
+ // 无 ctx.inject 的宿主保持默认开启。
703
+ }
704
+ try {
705
+ // 插件详情页配置区与视觉层分开注入:配置页失败不影响主题/视觉联动。
706
+ ctx?.inject?.(['configForms', 'slots'], (child) => {
707
+ try {
708
+ const form = child.configForms?.get('dsh-structured')
709
+ if (form) setupConfigPage(child, form)
710
+ } catch {
711
+ // 配置页不可用不影响视觉层与注入。
712
+ }
713
+ })
714
+ } catch {
715
+ // 无 ctx.inject 的宿主没有配置页,视觉层照常。
716
+ }
717
+ try {
718
+ ctx?.effect?.(() => disable, 'dsh-structured: visual layer')
719
+ } catch {
720
+ // 没有 effect 释放器时,视觉层保持到页面卸载。
721
+ }
722
+ return disable
723
+ },
724
+ }
725
+ },
726
+ })