dsh-structured 0.8.2 → 0.9.1

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/src/client.js CHANGED
@@ -1,508 +1,588 @@
1
- /**
2
- * dsh-structured client
3
- *
4
- * 两层,都是纯 DOM 增强,不改消息内容、不替换 app root:
5
- * 1) 对话 markdown 视觉层:标题字号/层级、表格、代码块、引用块、列表、链接的排版与配色。
6
- * 2) 语义装饰:结论卡片、风险/验收等 callout 色调、标题关键词图标、代码块语言色。
7
- *
8
- * 只给宿主已有节点加 class/attribute,不搬 React 节点;markdown 重渲染后由 MutationObserver
9
- * 重新扫描。主题色全部走 --dsw-alias-*,自动跟随明暗主题。
10
- */
11
- window.__ModuleLoader__.load({
12
- id: 'dsh-structured',
13
- factory() {
14
- const CARD_ATTR = 'data-dsh-structured-card'
15
- const CARD_TYPE_ATTR = 'data-dsh-structured-card-type'
16
- const ICON_ATTR = 'data-dsh-structured-icon'
17
- const TONE_ATTR = 'data-dsh-structured-tone'
18
- const LANG_ATTR = 'data-dsh-structured-lang'
19
- const STATUS_ATTR = 'data-dsh-structured-status'
20
- const STATUS_TABLE_ATTR = 'data-dsh-structured-status-table'
21
- const LEAD_ATTR = 'data-dsh-structured-lead'
22
- const ROW_ATTR = 'data-dsh-structured-row'
23
- const ROOT_ATTR = 'data-dsh-structured'
24
- const STYLE_ID = 'dsh-structured-skin-style'
25
- const LEGACY_STYLE_ID = 'dsh-structured-card-style'
26
-
27
- const CARD_TYPES = new Map([
28
- ['结论', 'conclusion'], ['总结', 'conclusion'], ['答案', 'conclusion'],
29
- ['判断', 'decision'], ['决策', 'decision'],
30
- ['指标', 'metric'], ['数据', 'metric'],
31
- ['风险', 'risk'], ['注意', 'risk'], ['警告', 'risk'],
32
- ['下一步', 'next'], ['行动', 'next'], ['待办', 'next'],
33
- ['验收', 'accept'], ['通过', 'accept'], ['完成', 'accept'],
34
- ['重点', 'key'], ['关键', 'key'],
35
- ])
36
- const ICONS = [
37
- [/结论|总结|答案|判断/, '◆'],
38
- [/方案|计划|步骤|流程/, '▸'],
39
- [/风险|注意|警告|坑/, '⚠'],
40
- [/验收|检查|核对/, '✓'],
41
- [/对比|选型|取舍/, '⇄'],
42
- [/下一步|行动|待办/, '→'],
43
- [/背景|原因|根因/, '◎'],
44
- [/指标|数据|结果/, '▦'],
45
- ]
46
-
47
- const CSS = `
48
- [class*="_markdown_"]{
49
- --ds-accent:#2563eb;
50
- --ds-text:var(--dsw-alias-label-primary,#111827);
51
- --ds-muted:var(--dsw-alias-label-secondary,#6b7280);
52
- --ds-border:var(--dsw-alias-border-l2,rgba(127,127,127,.22));
53
- --ds-surface:var(--dsw-alias-bg-layer-1,#fff);
54
- --ds-surface-2:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.06));
55
- font-size:15px;line-height:1.72;letter-spacing:.01em;
56
- }
57
- [data-theme="dark"] [class*="_markdown_"]{--ds-accent:#60a5fa}
58
- [class*="_markdown_"] p{margin:.72em 0}
59
- [class*="_markdown_"] h1,[class*="_markdown_"] h2,[class*="_markdown_"] h3,
60
- [class*="_markdown_"] h4,[class*="_markdown_"] h5,[class*="_markdown_"] h6{
61
- color:var(--ds-text);line-height:1.32;letter-spacing:-.01em;
62
- }
63
- [class*="_markdown_"] h1{font-size:26px;font-weight:700;margin:32px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--ds-border)}
64
- [class*="_markdown_"] h2{font-size:21px;font-weight:700;margin:28px 0 10px;padding-left:12px;border-left:3px solid var(--ds-accent)}
65
- [class*="_markdown_"] h3{font-size:17px;font-weight:650;margin:22px 0 8px}
66
- [class*="_markdown_"] h4{font-size:15px;font-weight:650;margin:16px 0 6px}
67
- [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}
68
- [class*="_markdown_"] h2[${ICON_ATTR}]::before,
69
- [class*="_markdown_"] h3[${ICON_ATTR}]::before{
70
- content:attr(${ICON_ATTR});
71
- display:inline-flex;align-items:center;justify-content:center;
72
- width:22px;height:22px;margin-right:8px;border-radius:6px;
73
- background:color-mix(in srgb,var(--ds-accent) 12%,transparent);
74
- color:var(--ds-accent);font-size:12px;font-weight:700;vertical-align:2px;
75
- }
76
- [class*="_markdown_"] strong{font-weight:700;color:var(--ds-text)}
77
- [class*="_markdown_"] a{color:var(--ds-accent);text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--ds-accent) 38%,transparent)}
78
- [class*="_markdown_"] a:hover{border-bottom-color:var(--ds-accent)}
79
- [class*="_markdown_"] ul,[class*="_markdown_"] ol{padding-left:1.5em;margin:.6em 0}
80
- [class*="_markdown_"] li{margin:.28em 0}
81
- [class*="_markdown_"] li::marker{color:var(--ds-accent)}
82
- [class*="_markdown_"] hr{height:1px;border:0;margin:28px 0;background:linear-gradient(90deg,transparent,var(--ds-border),transparent)}
83
- [class*="_markdown_"] table{
84
- width:100%;margin:18px 0;border-collapse:separate;border-spacing:0;
85
- border:1px solid var(--ds-border);border-radius:12px;overflow:hidden;font-size:13.5px;
86
- box-shadow:0 1px 2px rgba(0,0,0,.03);
87
- }
88
- [class*="_markdown_"] thead th{
89
- background:var(--ds-surface-2);
90
- font-size:11px!important;letter-spacing:.07em!important;color:var(--ds-muted);
91
- font-weight:700!important;text-align:left;padding:12px 18px!important;
92
- border-bottom:1px solid var(--ds-border);
93
- }
94
- [class*="_markdown_"] tbody td{
95
- padding:13px 18px!important;line-height:1.55;vertical-align:middle;border-left:0;border-right:0;
96
- border-bottom:1px solid color-mix(in srgb,var(--ds-border) 55%,transparent);
97
- word-break:break-word;
98
- }
99
- [class*="_markdown_"] tbody tr:nth-child(even){background:color-mix(in srgb,var(--ds-surface-2) 45%,transparent)}
100
- [class*="_markdown_"] tbody tr:hover{background:color-mix(in srgb,var(--ds-accent) 5%,transparent)}
101
- [class*="_markdown_"] tbody tr:last-child td{border-bottom:0}
102
- [class*="_markdown_"] table:has(thead th:nth-child(2):last-child) th:first-child,
103
- [class*="_markdown_"] table:has(thead th:nth-child(2):last-child) td:first-child{width:30%}
104
- [class*="_markdown_"] table:not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) th:first-child,
105
- [class*="_markdown_"] table:not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) td:first-child{width:24%}
106
- [class*="_markdown_"] table:not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) th:nth-child(2),
107
- [class*="_markdown_"] table:not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) td:nth-child(2){width:22%}
108
- [class*="_markdown_"] table[${STATUS_TABLE_ATTR}] th:first-child,
109
- [class*="_markdown_"] table[${STATUS_TABLE_ATTR}] td:first-child{
110
- width:68px;text-align:center;padding:7px 8px!important;font-size:11px;font-weight:700;
111
- white-space:nowrap;vertical-align:middle;border-radius:7px;
112
- }
113
- [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)}
114
- [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)}
115
- [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)}
116
- [class*="_markdown_"] p[${LEAD_ATTR}="table"]{
117
- border-left:2px solid var(--ds-accent);padding-left:10px;margin:10px 0 8px;
118
- font-size:14px;color:var(--ds-text);
119
- }
120
- [class*="_markdown_"] p[${LEAD_ATTR}="table"] strong{color:var(--ds-accent)}
121
- [class*="_markdown_"] h2+table,[class*="_markdown_"] h2+p,[class*="_markdown_"] h3+table,[class*="_markdown_"] h3+p{margin-top:8px}
122
- [class*="_markdown_"] :not(pre)>code{
123
- background:var(--ds-surface-2);border:1px solid var(--ds-border);border-radius:6px;
124
- padding:.12em .38em;font-size:.9em;color:var(--ds-accent);overflow-wrap:anywhere;
125
- }
126
- [class*="_markdown_"] .md-code-block{
127
- margin:16px 0;border:1px solid var(--ds-border);border-radius:12px;overflow:hidden;
128
- background:var(--ds-surface-2);box-shadow:0 1px 2px rgba(0,0,0,.04);
129
- }
130
- [class*="_markdown_"] .md-code-block > [class*="_bannerWrap"]{
131
- margin:0;padding:0;border:0;background:none;
132
- }
133
- [class*="_markdown_"] .md-code-block [data-code-block-banner]{
134
- display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0;border-radius:0;
135
- padding:6px 12px;font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
136
- color:var(--ds-muted);border-bottom:1px solid var(--ds-border);
137
- background:linear-gradient(90deg,color-mix(in srgb,var(--ds-code-accent,var(--ds-accent)) 14%,transparent),transparent 72%);
138
- }
139
- [class*="_markdown_"] .md-code-block [data-code-block-banner] > [class*="_heading"]{
140
- flex:1;min-width:0;justify-content:flex-start;text-align:left;
141
- }
142
- [class*="_markdown_"] .md-code-block [data-code-block-banner] > [class*="_actions"]{
143
- margin-left:auto;flex:none;
144
- }
145
- [class*="_markdown_"] .md-code-block > [data-code-block-content]{padding:0}
146
- [class*="_markdown_"] .md-code-block [data-code-block-banner]::before{
147
- content:"";width:8px;height:8px;border-radius:50%;flex:none;
148
- background:var(--ds-code-accent,var(--ds-accent));
149
- box-shadow:0 0 0 3px color-mix(in srgb,var(--ds-code-accent,var(--ds-accent)) 18%,transparent);
150
- }
151
- [class*="_markdown_"] .md-code-block pre{margin:0;padding:14px 16px;font-size:13px;line-height:1.65;overflow:auto}
152
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="js"]{--ds-code-accent:#eab308}
153
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="ts"],
154
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="tsx"]{--ds-code-accent:#3b82f6}
155
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="py"],
156
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="python"]{--ds-code-accent:#22c55e}
157
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="bash"],
158
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="sh"],
159
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="powershell"]{--ds-code-accent:#14b8a6}
160
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="json"]{--ds-code-accent:#a855f7}
161
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="html"],
162
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="css"]{--ds-code-accent:#f97316}
163
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="sql"]{--ds-code-accent:#06b6d4}
164
- [class*="_markdown_"] blockquote{
165
- margin:14px 0;padding:10px 14px;border-left:3px solid var(--ds-accent);
166
- background:color-mix(in srgb,var(--ds-accent) 6%,transparent);border-radius:0 10px 10px 0;
167
- }
168
- [class*="_markdown_"] blockquote[${TONE_ATTR}="warn"]{--ds-accent:var(--dsw-alias-state-warn-primary,#d97706)}
169
- [class*="_markdown_"] blockquote[${TONE_ATTR}="success"]{--ds-accent:var(--dsw-alias-state-success-primary,#16a34a)}
170
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]{
171
- border:1px solid color-mix(in srgb,var(--ds-accent) 16%,var(--ds-border));
172
- border-left:3px solid var(--ds-accent);
173
- border-radius:12px;padding:14px 16px 15px;
174
- background:linear-gradient(135deg,color-mix(in srgb,var(--ds-accent) 6%,var(--ds-surface)),var(--ds-surface) 60%);
175
- box-shadow:0 1px 2px rgba(0,0,0,.04),0 12px 32px -24px color-mix(in srgb,var(--ds-accent) 32%,transparent);
176
- }
177
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child{
178
- display:flex;align-items:center;gap:9px;position:relative;
179
- font-size:13px;line-height:1.1;letter-spacing:.06em;text-transform:uppercase;
180
- color:var(--ds-accent);margin-bottom:8px;
181
- }
182
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child::before{
183
- content:"";display:inline-flex;flex:none;
184
- width:26px;height:26px;border-radius:7px;
185
- background:color-mix(in srgb,var(--ds-accent) 12%,transparent);
186
- }
187
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child::after{
188
- content:"";position:absolute;left:5.5px;top:5.5px;width:15px;height:15px;
189
- background-color:var(--ds-accent);
190
- -webkit-mask:var(--ds-card-icon) center/contain no-repeat;
191
- mask:var(--ds-card-icon) center/contain no-repeat;
192
- }
193
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:nth-child(2)>strong:first-child{
194
- display:block;font-size:16px;font-weight:700;line-height:1.45;
195
- letter-spacing:-.01em;color:var(--ds-text);margin-bottom:4px;
196
- }
197
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p{font-size:15.5px;line-height:1.7;font-weight:450;color:var(--ds-text)}
198
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p strong{font-weight:700;color:var(--ds-text)}
199
- [class*="_markdown_"] blockquote[${TONE_ATTR}]>p:first-child>strong:first-child{color:var(--ds-accent)}
200
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="conclusion"]{
201
- --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");
202
- }
203
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="decision"]{
204
- --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");
205
- }
206
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="metric"]{
207
- --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");
208
- }
209
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="risk"]{
210
- --ds-accent:var(--dsw-alias-state-warn-primary,#d97706);
211
- --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");
212
- }
213
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="next"]{
214
- --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");
215
- }
216
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="accept"]{
217
- --ds-accent:var(--dsw-alias-state-success-primary,#16a34a);
218
- --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");
219
- }
220
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="key"]{
221
- --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");
222
- }
223
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="statement"]{
224
- --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");
225
- }
226
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="statement"]>p:first-child>strong:first-child{
227
- font-size:16px;line-height:1.45;letter-spacing:-.01em;text-transform:none;
228
- color:var(--ds-text);margin-bottom:4px;
229
- }
230
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child{margin-top:0}
231
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:last-child{margin-bottom:0}
232
- [class*="_markdown_"] p[${STATUS_ATTR}],[class*="_markdown_"] li[${STATUS_ATTR}]{
233
- border-left:3px solid var(--ds-accent);background:color-mix(in srgb,var(--ds-accent) 7%,transparent);
234
- border-radius:0 6px 6px 0;padding:4px 10px;margin:.4em 0;
235
- }
236
- [class*="_markdown_"] p[${STATUS_ATTR}="success"],[class*="_markdown_"] li[${STATUS_ATTR}="success"]{--ds-accent:var(--dsw-alias-state-success-primary,#16a34a)}
237
- [class*="_markdown_"] p[${STATUS_ATTR}="warn"],[class*="_markdown_"] li[${STATUS_ATTR}="warn"]{--ds-accent:var(--dsw-alias-state-warn-primary,#d97706)}
238
- [class*="_markdown_"] p[${STATUS_ATTR}="error"],[class*="_markdown_"] li[${STATUS_ATTR}="error"]{--ds-accent:var(--dsw-alias-state-error-primary,#dc2626)}
239
- @media (max-width:720px){
240
- [class*="_markdown_"]{font-size:14.5px}
241
- [class*="_markdown_"] h1{font-size:22px}
242
- [class*="_markdown_"] h2{font-size:18px}
243
- [class*="_markdown_"] table{font-size:13px}
244
- [class*="_markdown_"] thead th,[class*="_markdown_"] tbody td{padding:7px 9px}
245
- }
246
- @media (prefers-reduced-motion:reduce){
247
- [class*="_markdown_"] *{transition:none!important;animation:none!important}
248
- }
249
- `
250
-
251
- function iconFor(text) {
252
- for (const [pattern, icon] of ICONS) if (pattern.test(text)) return icon
253
- return null
254
- }
255
-
256
- function firstParagraph(block) {
257
- return [...(block.children ?? [])].find((element) => element.tagName === 'P') ?? null
258
- }
259
-
260
- function labelOf(block) {
261
- const first = firstParagraph(block)
262
- const strong = first?.firstElementChild
263
- if (!strong || strong.tagName !== 'STRONG') return null
264
- return (strong.textContent ?? '').replace(/[::]\s*$/u, '').trim()
265
- }
266
-
267
- function cardTypeFor(block) {
268
- const label = labelOf(block)
269
- return label === null ? null : (CARD_TYPES.get(label) ?? null)
270
- }
271
-
272
- function isLabelLike(text) {
273
- return [...CARD_TYPES.keys()].some((label) => text.startsWith(label) && text !== label)
274
- }
275
-
276
- function inGenui(element) {
277
- try {
278
- return Boolean(element.closest?.('[data-genui], [class*="genui-root"]'))
279
- } catch {
280
- return false
281
- }
282
- }
283
-
284
- function statusFor(text) {
285
- if (/^✅/.test(text)) return 'success'
286
- if (/^⚠/.test(text)) return 'warn'
287
- if (/^❌/.test(text)) return 'error'
288
- return null
289
- }
290
-
291
- function isStatementCard(block) {
292
- const markdown = block.closest?.('[class*="_markdown_"]')
293
- if (!markdown) return false
294
- const first = [...markdown.querySelectorAll('blockquote')].find((element) => !inGenui(element))
295
- if (first !== block) return false
296
- const paragraphs = [...block.children].filter((element) => element.tagName === 'P')
297
- const firstParagraph = paragraphs[0]
298
- const strong = firstParagraph?.firstElementChild
299
- if (!strong || strong.tagName !== 'STRONG') return false
300
- let previous = strong.previousSibling
301
- while (previous) {
302
- if (previous.nodeType === 1) return false
303
- if (previous.nodeType === 3 && (previous.textContent ?? '').trim() !== '') return false
304
- previous = previous.previousSibling
305
- }
306
- const title = (strong.textContent ?? '').trim()
307
- if (title.length === 0 || isLabelLike(title)) return false
308
- let rest = ''
309
- for (let node = strong.nextSibling; node; node = node.nextSibling) rest += node.textContent ?? ''
310
- const secondBody = paragraphs.slice(1).some((element) => (element.textContent ?? '').trim().length > 0)
311
- return rest.trim().length > 0 || secondBody
312
- }
313
-
314
- function decorate(root) {
315
- for (const heading of root.querySelectorAll('[class*="_markdown_"] h2, [class*="_markdown_"] h3')) {
316
- if (inGenui(heading)) continue
317
- const icon = iconFor((heading.textContent ?? '').trim())
318
- if (icon === null) heading.removeAttribute(ICON_ATTR)
319
- else heading.setAttribute(ICON_ATTR, icon)
320
- }
321
-
322
- for (const block of root.querySelectorAll('blockquote')) {
323
- if (inGenui(block)) continue
324
- let type = cardTypeFor(block)
325
- if (type === null && isStatementCard(block)) type = 'statement'
326
- if (type === null) {
327
- block.removeAttribute(CARD_ATTR)
328
- block.removeAttribute(CARD_TYPE_ATTR)
329
- block.removeAttribute(LEAD_ATTR)
330
- block.removeAttribute(TONE_ATTR)
331
- continue
332
- }
333
- block.setAttribute(CARD_ATTR, '1')
334
- block.setAttribute(CARD_TYPE_ATTR, type)
335
- block.removeAttribute(LEAD_ATTR)
336
- block.setAttribute(TONE_ATTR, type === 'risk' ? 'warn' : type === 'accept' ? 'success' : 'info')
337
- }
338
-
339
- for (const element of root.querySelectorAll('[class*="_markdown_"] p, [class*="_markdown_"] li')) {
340
- if (inGenui(element) || element.closest('blockquote, table')) {
341
- element.removeAttribute(STATUS_ATTR)
342
- continue
343
- }
344
- const status = statusFor((element.textContent ?? '').trimStart())
345
- if (status === null) element.removeAttribute(STATUS_ATTR)
346
- else element.setAttribute(STATUS_ATTR, status)
347
- }
348
-
349
- for (const row of root.querySelectorAll('[class*="_markdown_"] table tr')) {
350
- if (inGenui(row) || row.closest('thead')) {
351
- row.removeAttribute(ROW_ATTR)
352
- continue
353
- }
354
- const status = statusFor((row.textContent ?? '').trimStart())
355
- if (status === null) row.removeAttribute(ROW_ATTR)
356
- else row.setAttribute(ROW_ATTR, status)
357
- }
358
-
359
- for (const table of root.querySelectorAll('[class*="_markdown_"] table')) {
360
- if (inGenui(table)) continue
361
- const rows = [...table.querySelectorAll('tbody tr')]
362
- const statusTable =
363
- rows.length > 0 &&
364
- rows.every((row) => statusFor((row.cells?.[0]?.textContent ?? '').trimStart()) !== null)
365
- if (statusTable) table.setAttribute(STATUS_TABLE_ATTR, '1')
366
- else table.removeAttribute(STATUS_TABLE_ATTR)
367
- }
368
-
369
- for (const paragraph of root.querySelectorAll('[class*="_markdown_"] p')) {
370
- if (inGenui(paragraph) || paragraph.closest('blockquote, table')) {
371
- paragraph.removeAttribute(LEAD_ATTR)
372
- continue
373
- }
374
- if (/^表格结论/.test((paragraph.textContent ?? '').trimStart())) paragraph.setAttribute(LEAD_ATTR, 'table')
375
- else paragraph.removeAttribute(LEAD_ATTR)
376
- }
377
-
378
- for (const code of root.querySelectorAll('[class*="_markdown_"] .md-code-block')) {
379
- if (inGenui(code)) continue
380
- const banner = code.querySelector('[data-code-block-banner]')
381
- const raw = (banner?.textContent ?? '').trim().split(/\s+/)[0] ?? ''
382
- const lang = raw.toLowerCase().replace(/[^a-z0-9+#]/g, '')
383
- if (lang.length === 0) code.removeAttribute(LANG_ATTR)
384
- else code.setAttribute(LANG_ATTR, lang)
385
- }
386
- }
387
-
388
- let active = false
389
- let disposeLayer = null
390
-
391
- const enable = () => {
392
- if (active) return
393
- if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') return
394
- if (document.body === null) return
395
- active = true
396
-
397
- document.getElementById(LEGACY_STYLE_ID)?.remove()
398
- document.getElementById(STYLE_ID)?.remove()
399
- const style = document.createElement('style')
400
- style.id = STYLE_ID
401
- style.textContent = CSS
402
- document.head.appendChild(style)
403
- document.documentElement.setAttribute(ROOT_ATTR, 'active')
404
- try {
405
- console.info('[dsh-structured] visual layer active')
406
- } catch {
407
- // console 不可用时忽略。
408
- }
409
-
410
- let disposed = false
411
- let scheduled = false
412
- let pending = null
413
- const run = () => {
414
- if (disposed) return
415
- scheduled = false
416
- pending = null
417
- decorate(document)
418
- }
419
- const schedule = () => {
420
- if (disposed || scheduled) return
421
- scheduled = true
422
- if (typeof requestAnimationFrame === 'function') pending = requestAnimationFrame(run)
423
- else pending = setTimeout(run, 16)
424
- }
425
- const observer = new MutationObserver(schedule)
426
- observer.observe(document.body, { childList: true, subtree: true, characterData: true })
427
- run()
428
-
429
- disposeLayer = () => {
430
- if (disposed) return
431
- disposed = true
432
- active = false
433
- disposeLayer = null
434
- observer.disconnect()
435
- if (pending !== null) {
436
- if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(pending)
437
- else clearTimeout(pending)
438
- }
439
- style.remove()
440
- document.documentElement.removeAttribute(ROOT_ATTR)
441
- for (const attr of [CARD_ATTR, CARD_TYPE_ATTR, ICON_ATTR, TONE_ATTR, LANG_ATTR, STATUS_ATTR, STATUS_TABLE_ATTR, LEAD_ATTR, ROW_ATTR]) {
442
- for (const element of document.querySelectorAll('[' + attr + ']')) element.removeAttribute(attr)
443
- }
444
- try {
445
- console.info('[dsh-structured] visual layer disposed')
446
- } catch {
447
- // console 不可用时忽略。
448
- }
449
- }
450
- }
451
-
452
- const disable = () => {
453
- const layer = disposeLayer
454
- disposeLayer = null
455
- if (typeof layer === 'function') layer()
456
- if (typeof document === 'undefined') return
457
- document.getElementById(STYLE_ID)?.remove()
458
- document.documentElement.removeAttribute(ROOT_ATTR)
459
- for (const attr of [CARD_ATTR, CARD_TYPE_ATTR, ICON_ATTR, TONE_ATTR, LANG_ATTR, STATUS_ATTR, STATUS_TABLE_ATTR, LEAD_ATTR, ROW_ATTR]) {
460
- for (const element of document.querySelectorAll('[' + attr + ']')) element.removeAttribute(attr)
461
- }
462
- }
463
-
464
- const applyVisual = (on) => {
465
- if (on) enable()
466
- else disable()
467
- }
468
-
469
- return {
470
- inject: [],
471
- apply(ctx) {
472
- // 默认开启,保持 0.8.1 的现有观感;随后由插件设置里的 visual 覆盖。
473
- enable()
474
- try {
475
- ctx?.inject?.(['configForms'], (child) => {
476
- try {
477
- const form = child.configForms?.get('dsh-structured')
478
- if (!form || typeof form.getSnapshot !== 'function') return
479
- const read = () => {
480
- try {
481
- const raw = form.getSnapshot()?.value?.visual
482
- const visual = raw && typeof raw.get === 'function' ? raw.get() : raw
483
- applyVisual(visual !== false)
484
- } catch {
485
- // 读取失败时保持当前状态,不打断回复。
486
- }
487
- }
488
- read()
489
- if (typeof form.subscribe === 'function') {
490
- child.effect?.(() => form.subscribe(read), 'dsh-structured: visual preference')
491
- }
492
- } catch {
493
- // configForms 不可用时保持默认开启。
494
- }
495
- })
496
- } catch {
497
- // 无 ctx.inject 的宿主保持默认开启。
498
- }
499
- try {
500
- ctx?.effect?.(() => disable, 'dsh-structured: visual layer')
501
- } catch {
502
- // 没有 effect 释放器时,视觉层保持到页面卸载。
503
- }
504
- return disable
505
- },
506
- }
507
- },
508
- })
1
+ /**
2
+ * dsh-structured client
3
+ *
4
+ * 两层,都是纯 DOM 增强,不改消息内容、不替换 app root:
5
+ * 1) 对话 markdown 视觉层:标题字号/层级、表格、代码块、引用块、列表、链接的排版与配色。
6
+ * 2) 语义装饰:结论卡片、风险/验收等 callout 色调、标题关键词图标、代码块语言色。
7
+ *
8
+ * 只给宿主已有节点加 class/attribute,不搬 React 节点;markdown 重渲染后由 MutationObserver
9
+ * 重新扫描。主题色全部走 --dsw-alias-*,自动跟随明暗主题。
10
+ */
11
+ window.__ModuleLoader__.load({
12
+ id: 'dsh-structured',
13
+ factory() {
14
+ const CARD_ATTR = 'data-dsh-structured-card'
15
+ const CARD_TYPE_ATTR = 'data-dsh-structured-card-type'
16
+ const ICON_ATTR = 'data-dsh-structured-icon'
17
+ const TONE_ATTR = 'data-dsh-structured-tone'
18
+ const LANG_ATTR = 'data-dsh-structured-lang'
19
+ const CODE_ATTR = 'data-dsh-structured-code'
20
+ const SKIP_ATTR = 'data-dsh-structured-skip'
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 THEME_ATTR = 'data-dsh-structured-theme'
27
+ const THEMES = ['deepseek', 'codex', 'anthropic']
28
+ const STYLE_ID = 'dsh-structured-skin-style'
29
+ const LEGACY_STYLE_ID = 'dsh-structured-card-style'
30
+
31
+ const CARD_TYPES = new Map([
32
+ ['结论', 'conclusion'], ['总结', 'conclusion'], ['答案', 'conclusion'],
33
+ ['判断', 'decision'], ['决策', 'decision'],
34
+ ['指标', 'metric'], ['数据', 'metric'],
35
+ ['风险', 'risk'], ['注意', 'risk'], ['警告', 'risk'],
36
+ ['下一步', 'next'], ['行动', 'next'], ['待办', 'next'],
37
+ ['验收', 'accept'], ['通过', 'accept'], ['完成', 'accept'],
38
+ ['重点', 'key'], ['关键', 'key'],
39
+ ])
40
+ const ICONS = [
41
+ [/结论|总结|答案|判断/, '◆'],
42
+ [/方案|计划|步骤|流程/, '▸'],
43
+ [/风险|注意|警告|坑/, '⚠'],
44
+ [/验收|检查|核对/, '✓'],
45
+ [/对比|选型|取舍/, '⇄'],
46
+ [/下一步|行动|待办/, '→'],
47
+ [/背景|原因|根因/, '◎'],
48
+ [/指标|数据|结果/, '▦'],
49
+ ]
50
+
51
+ const CSS = `
52
+ [class*="_markdown_"]{
53
+ --ds-surface:#FFFFFF;--ds-surface-2:#F4F7FB;--ds-text:#0F172A;--ds-muted:#5B6B82;
54
+ --ds-border:#E2E8F0;--ds-accent:#2563EB;--ds-accent-text:#2563EB;--ds-success:#166534;--ds-warn:#92400E;--ds-error:#991B1B;
55
+ --ds-code-bg:#F6F8FA;--ds-code-text:#0F172A;
56
+ font-size:15px;line-height:1.72;letter-spacing:.01em;
57
+ }
58
+ body[data-ds-dark-theme] [class*="_markdown_"]{
59
+ --ds-surface:#111827;--ds-surface-2:#1F2937;--ds-text:#E5E7EB;--ds-muted:#9CA3AF;
60
+ --ds-border:#374151;--ds-accent:#60A5FA;--ds-accent-text:#60A5FA;--ds-success:#4ADE80;--ds-warn:#FBBF24;--ds-error:#F87171;
61
+ --ds-code-bg:#0B1220;--ds-code-text:#E5E7EB;
62
+ }
63
+ body[data-dsh-structured-theme="codex"] [class*="_markdown_"]{
64
+ --ds-surface:#FFFFFF;--ds-surface-2:#F0F7F4;--ds-text:#0D1117;--ds-muted:#4F625A;
65
+ --ds-border:#CFE3DB;--ds-accent:#10A37F;--ds-accent-text:#0B6B4F;--ds-success:#116329;--ds-warn:#7D4E00;--ds-error:#A40E26;
66
+ --ds-code-bg:#F0F7F4;--ds-code-text:#0D1117;
67
+ }
68
+ body[data-ds-dark-theme][data-dsh-structured-theme="codex"] [class*="_markdown_"]{
69
+ --ds-surface:#0B1512;--ds-surface-2:#12211B;--ds-text:#E6F2EC;--ds-muted:#9DB2A8;
70
+ --ds-border:#233A31;--ds-accent:#34D399;--ds-accent-text:#6EE7B7;--ds-success:#3FB950;--ds-warn:#D29922;--ds-error:#F85149;
71
+ --ds-code-bg:#07100D;--ds-code-text:#E6F2EC;
72
+ }
73
+ body[data-dsh-structured-theme="anthropic"] [class*="_markdown_"]{
74
+ --ds-surface:#FAF9F5;--ds-surface-2:#F5F1E8;--ds-text:#141413;--ds-muted:#6B6359;
75
+ --ds-border:#E5E0D5;--ds-accent:#CC785C;--ds-accent-text:#A14A2E;--ds-success:#266B4D;--ds-warn:#92400E;--ds-error:#8C1D18;
76
+ --ds-code-bg:#F5F1E8;--ds-code-text:#141413;
77
+ }
78
+ body[data-ds-dark-theme][data-dsh-structured-theme="anthropic"] [class*="_markdown_"]{
79
+ --ds-surface:#1F1E1D;--ds-surface-2:#2A2825;--ds-text:#F5F1E8;--ds-muted:#A8A29A;
80
+ --ds-border:#3A3733;--ds-accent:#E8A087;--ds-accent-text:#E8A087;--ds-success:#6FBF9A;--ds-warn:#E0A458;--ds-error:#F28B82;
81
+ --ds-code-bg:#171614;--ds-code-text:#F5F1E8;
82
+ }
83
+ [class*="_markdown_"] p{margin:.72em 0}
84
+ [class*="_markdown_"] h1,[class*="_markdown_"] h2,[class*="_markdown_"] h3,
85
+ [class*="_markdown_"] h4,[class*="_markdown_"] h5,[class*="_markdown_"] h6{
86
+ color:var(--ds-text);line-height:1.32;letter-spacing:-.01em;
87
+ }
88
+ [class*="_markdown_"] h1{font-size:26px;font-weight:700;margin:32px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--ds-border)}
89
+ [class*="_markdown_"] h2{font-size:21px;font-weight:700;margin:28px 0 10px;padding-left:12px;border-left:3px solid var(--ds-accent)}
90
+ [class*="_markdown_"] h3{font-size:17px;font-weight:650;margin:22px 0 8px}
91
+ [class*="_markdown_"] h4{font-size:15px;font-weight:650;margin:16px 0 6px}
92
+ [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}
93
+ [class*="_markdown_"] h2[${ICON_ATTR}]::before,
94
+ [class*="_markdown_"] h3[${ICON_ATTR}]::before{
95
+ content:attr(${ICON_ATTR});
96
+ display:inline-flex;align-items:center;justify-content:center;
97
+ width:22px;height:22px;margin-right:8px;border-radius:6px;
98
+ background:color-mix(in srgb,var(--ds-accent) 12%,transparent);
99
+ color:var(--ds-accent-text);font-size:12px;font-weight:700;vertical-align:2px;
100
+ }
101
+ [class*="_markdown_"] strong{font-weight:700;color:var(--ds-text)}
102
+ [class*="_markdown_"] a{color:var(--ds-accent-text);text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--ds-accent) 38%,transparent)}
103
+ [class*="_markdown_"] a:hover{border-bottom-color:var(--ds-accent)}
104
+ [class*="_markdown_"] ul,[class*="_markdown_"] ol{padding-left:1.5em;margin:.6em 0}
105
+ [class*="_markdown_"] li{margin:.28em 0}
106
+ [class*="_markdown_"] li::marker{color:var(--ds-accent-text)}
107
+ [class*="_markdown_"] hr{height:1px;border:0;margin:28px 0;background:linear-gradient(90deg,transparent,var(--ds-border),transparent)}
108
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]){
109
+ width:100%;margin:18px 0;border-collapse:separate;border-spacing:0;
110
+ border:1px solid var(--ds-border);border-radius:12px;overflow:hidden;font-size:13.5px;
111
+ box-shadow:0 1px 2px rgba(0,0,0,.03);
112
+ }
113
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) thead th{
114
+ background:var(--ds-surface-2);
115
+ font-size:11px!important;letter-spacing:.07em!important;color:var(--ds-muted);
116
+ font-weight:700!important;text-align:left;padding:12px 18px!important;
117
+ border-bottom:1px solid var(--ds-border);
118
+ }
119
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody td{
120
+ padding:13px 18px!important;line-height:1.55;vertical-align:middle;border-left:0;border-right:0;
121
+ border-bottom:1px solid color-mix(in srgb,var(--ds-border) 55%,transparent);
122
+ word-break:break-word;
123
+ }
124
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody tr:nth-child(even){background:color-mix(in srgb,var(--ds-surface-2) 45%,transparent)}
125
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody tr:hover{background:color-mix(in srgb,var(--ds-accent) 5%,transparent)}
126
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody tr:last-child td{border-bottom:0}
127
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):has(thead th:nth-child(2):last-child) th:first-child,
128
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):has(thead th:nth-child(2):last-child) td:first-child{width:30%}
129
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) th:first-child,
130
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) td:first-child{width:24%}
131
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) th:nth-child(2),
132
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) td:nth-child(2){width:22%}
133
+ [class*="_markdown_"] table:not([${SKIP_ATTR}])[${STATUS_TABLE_ATTR}] th:first-child,
134
+ [class*="_markdown_"] table:not([${SKIP_ATTR}])[${STATUS_TABLE_ATTR}] td:first-child{
135
+ width:68px;text-align:center;padding:7px 8px!important;font-size:11px;font-weight:700;
136
+ white-space:nowrap;vertical-align:middle;border-radius:7px;
137
+ }
138
+ [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)}
139
+ [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)}
140
+ [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)}
141
+ [class*="_markdown_"] p[${LEAD_ATTR}="table"]{
142
+ border-left:2px solid var(--ds-accent);padding-left:10px;margin:10px 0 8px;
143
+ font-size:14px;color:var(--ds-text);
144
+ }
145
+ [class*="_markdown_"] p[${LEAD_ATTR}="table"] strong{color:var(--ds-accent-text)}
146
+ [class*="_markdown_"] h2+table,[class*="_markdown_"] h2+p,[class*="_markdown_"] h3+table,[class*="_markdown_"] h3+p{margin-top:8px}
147
+ [class*="_markdown_"] :not(pre)>code{
148
+ background:var(--ds-surface-2);border:1px solid var(--ds-border);border-radius:6px;
149
+ padding:.12em .38em;font-size:.9em;color:var(--ds-accent-text);overflow-wrap:anywhere;
150
+ }
151
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"]{
152
+ margin:16px 0;border:1px solid var(--ds-border);border-radius:12px;overflow:hidden;
153
+ background:var(--ds-code-bg);box-shadow:0 1px 2px rgba(0,0,0,.04);
154
+ }
155
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] > [class*="_bannerWrap"]{
156
+ margin:0;padding:0;border:0;background:none;
157
+ }
158
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] [data-code-block-banner]{
159
+ display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0;border-radius:0;
160
+ padding:6px 12px;font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
161
+ color:var(--ds-muted);border-bottom:1px solid var(--ds-border);
162
+ background:linear-gradient(90deg,color-mix(in srgb,var(--ds-code-accent,var(--ds-accent)) 14%,transparent),transparent 72%);
163
+ }
164
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] [data-code-block-banner] > [class*="_heading"]{
165
+ flex:1;min-width:0;justify-content:flex-start;text-align:left;
166
+ }
167
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] [data-code-block-banner] > [class*="_actions"]{
168
+ margin-left:auto;flex:none;
169
+ }
170
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] > [data-code-block-content]{padding:0}
171
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] [data-code-block-banner]::before{
172
+ content:"";width:8px;height:8px;border-radius:50%;flex:none;
173
+ background:var(--ds-code-accent,var(--ds-accent));
174
+ box-shadow:0 0 0 3px color-mix(in srgb,var(--ds-code-accent,var(--ds-accent)) 18%,transparent);
175
+ }
176
+ [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)}
177
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="js"]{--ds-code-accent:#eab308}
178
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="ts"],
179
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="tsx"]{--ds-code-accent:#3b82f6}
180
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="py"],
181
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="python"]{--ds-code-accent:#22c55e}
182
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="bash"],
183
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="sh"],
184
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="powershell"]{--ds-code-accent:#14b8a6}
185
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="json"]{--ds-code-accent:#a855f7}
186
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="html"],
187
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="css"]{--ds-code-accent:#f97316}
188
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="sql"]{--ds-code-accent:#06b6d4}
189
+ [class*="_markdown_"] blockquote{
190
+ margin:14px 0;padding:10px 14px;border-left:3px solid var(--ds-accent);
191
+ background:color-mix(in srgb,var(--ds-accent) 6%,transparent);border-radius:0 10px 10px 0;
192
+ }
193
+ [class*="_markdown_"] blockquote[${TONE_ATTR}="warn"]{--ds-accent:var(--ds-warn);--ds-accent-text:var(--ds-warn)}
194
+ [class*="_markdown_"] blockquote[${TONE_ATTR}="success"]{--ds-accent:var(--ds-success);--ds-accent-text:var(--ds-success)}
195
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]{
196
+ border:1px solid color-mix(in srgb,var(--ds-accent) 16%,var(--ds-border));
197
+ border-left:3px solid var(--ds-accent);
198
+ border-radius:12px;padding:14px 16px 15px;
199
+ background:linear-gradient(135deg,color-mix(in srgb,var(--ds-accent) 6%,var(--ds-surface)),var(--ds-surface) 60%);
200
+ box-shadow:0 1px 2px rgba(0,0,0,.04),0 12px 32px -24px color-mix(in srgb,var(--ds-accent) 32%,transparent);
201
+ }
202
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child{
203
+ display:flex;align-items:center;gap:9px;position:relative;
204
+ font-size:13px;line-height:1.1;letter-spacing:.06em;text-transform:uppercase;
205
+ color:var(--ds-accent-text);margin-bottom:8px;
206
+ }
207
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child::before{
208
+ content:"";display:inline-flex;flex:none;
209
+ width:26px;height:26px;border-radius:7px;
210
+ background:color-mix(in srgb,var(--ds-accent) 12%,transparent);
211
+ }
212
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child::after{
213
+ content:"";position:absolute;left:5.5px;top:5.5px;width:15px;height:15px;
214
+ background-color:var(--ds-accent);
215
+ -webkit-mask:var(--ds-card-icon) center/contain no-repeat;
216
+ mask:var(--ds-card-icon) center/contain no-repeat;
217
+ }
218
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:nth-child(2)>strong:first-child{
219
+ display:block;font-size:16px;font-weight:700;line-height:1.45;
220
+ letter-spacing:-.01em;color:var(--ds-text);margin-bottom:4px;
221
+ }
222
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p{font-size:15.5px;line-height:1.7;font-weight:450;color:var(--ds-text)}
223
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p strong{font-weight:700;color:var(--ds-text)}
224
+ [class*="_markdown_"] blockquote[${TONE_ATTR}]>p:first-child>strong:first-child{color:var(--ds-accent-text)}
225
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="conclusion"]{
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}="decision"]{
229
+ --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");
230
+ }
231
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="metric"]{
232
+ --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");
233
+ }
234
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="risk"]{
235
+ --ds-accent:var(--ds-warn);--ds-accent-text:var(--ds-warn);
236
+ --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");
237
+ }
238
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="next"]{
239
+ --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");
240
+ }
241
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="accept"]{
242
+ --ds-accent:var(--ds-success);--ds-accent-text:var(--ds-success);
243
+ --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");
244
+ }
245
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="key"]{
246
+ --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");
247
+ }
248
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="statement"]{
249
+ --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");
250
+ }
251
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="statement"]>p:first-child>strong:first-child{
252
+ font-size:16px;line-height:1.45;letter-spacing:-.01em;text-transform:none;
253
+ color:var(--ds-text);margin-bottom:4px;
254
+ }
255
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child{margin-top:0}
256
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:last-child{margin-bottom:0}
257
+ [class*="_markdown_"] p[${STATUS_ATTR}],[class*="_markdown_"] li[${STATUS_ATTR}]{
258
+ border-left:3px solid var(--ds-accent);background:color-mix(in srgb,var(--ds-accent) 7%,transparent);
259
+ border-radius:0 6px 6px 0;padding:4px 10px;margin:.4em 0;
260
+ }
261
+ [class*="_markdown_"] p[${STATUS_ATTR}="success"],[class*="_markdown_"] li[${STATUS_ATTR}="success"]{--ds-accent:var(--ds-success)}
262
+ [class*="_markdown_"] p[${STATUS_ATTR}="warn"],[class*="_markdown_"] li[${STATUS_ATTR}="warn"]{--ds-accent:var(--ds-warn)}
263
+ [class*="_markdown_"] p[${STATUS_ATTR}="error"],[class*="_markdown_"] li[${STATUS_ATTR}="error"]{--ds-accent:var(--ds-error)}
264
+ @media (max-width:720px){
265
+ [class*="_markdown_"]{font-size:14.5px}
266
+ [class*="_markdown_"] h1{font-size:22px}
267
+ [class*="_markdown_"] h2{font-size:18px}
268
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]){font-size:13px}
269
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) thead th,[class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody td{padding:7px 9px}
270
+ }
271
+ @media (prefers-reduced-motion:reduce){
272
+ [class*="_markdown_"] *{transition:none!important;animation:none!important}
273
+ }
274
+ `
275
+
276
+ function iconFor(text) {
277
+ for (const [pattern, icon] of ICONS) if (pattern.test(text)) return icon
278
+ return null
279
+ }
280
+
281
+ function firstParagraph(block) {
282
+ return [...(block.children ?? [])].find((element) => element.tagName === 'P') ?? null
283
+ }
284
+
285
+ function labelOf(block) {
286
+ const first = firstParagraph(block)
287
+ const strong = first?.firstElementChild
288
+ if (!strong || strong.tagName !== 'STRONG') return null
289
+ return (strong.textContent ?? '').replace(/[::]\s*$/u, '').trim()
290
+ }
291
+
292
+ function cardTypeFor(block) {
293
+ const label = labelOf(block)
294
+ return label === null ? null : (CARD_TYPES.get(label) ?? null)
295
+ }
296
+
297
+ function isLabelLike(text) {
298
+ return [...CARD_TYPES.keys()].some((label) => text.startsWith(label) && text !== label)
299
+ }
300
+
301
+ function inGenui(element) {
302
+ try {
303
+ return Boolean(element.closest?.('[data-genui], [class*="genui-root"]'))
304
+ } catch {
305
+ return false
306
+ }
307
+ }
308
+
309
+ function inDiff(element) {
310
+ try {
311
+ return Boolean(element.closest?.('[data-diff], .md-diff, [class*="_diff_"], [class*="diff-view"], [class*="diff-block"], [data-testid*="diff"]'))
312
+ } catch {
313
+ return false
314
+ }
315
+ }
316
+
317
+ function bannerLanguage(code) {
318
+ const banner = code.querySelector?.('[data-code-block-banner]')
319
+ const heading = banner?.querySelector?.('[class*="_heading"]')
320
+ const raw = ((heading?.textContent ?? banner?.textContent) ?? '').trim().split(/\s+/)[0] ?? ''
321
+ return raw.toLowerCase().replace(/[^a-z0-9+#]/g, '')
322
+ }
323
+
324
+ function isDiffCode(code) {
325
+ const lang = bannerLanguage(code)
326
+ return lang === 'diff' || lang === 'patch'
327
+ }
328
+
329
+ function statusFor(text) {
330
+ if (/^✅/.test(text)) return 'success'
331
+ if (/^⚠/.test(text)) return 'warn'
332
+ if (/^❌/.test(text)) return 'error'
333
+ return null
334
+ }
335
+
336
+ function isStatementCard(block) {
337
+ if (inDiff(block)) return false
338
+ const markdown = block.closest?.('[class*="_markdown_"]')
339
+ if (!markdown) return false
340
+ const first = [...markdown.querySelectorAll('blockquote')].find((element) => !inGenui(element) && !inDiff(element))
341
+ if (first !== block) return false
342
+ const paragraphs = [...block.children].filter((element) => element.tagName === 'P')
343
+ const firstParagraph = paragraphs[0]
344
+ const strong = firstParagraph?.firstElementChild
345
+ if (!strong || strong.tagName !== 'STRONG') return false
346
+ let previous = strong.previousSibling
347
+ while (previous) {
348
+ if (previous.nodeType === 1) return false
349
+ if (previous.nodeType === 3 && (previous.textContent ?? '').trim() !== '') return false
350
+ previous = previous.previousSibling
351
+ }
352
+ const title = (strong.textContent ?? '').trim()
353
+ if (title.length === 0 || isLabelLike(title)) return false
354
+ let rest = ''
355
+ for (let node = strong.nextSibling; node; node = node.nextSibling) rest += node.textContent ?? ''
356
+ const secondBody = paragraphs.slice(1).some((element) => (element.textContent ?? '').trim().length > 0)
357
+ return rest.trim().length > 0 || secondBody
358
+ }
359
+
360
+ function decorate(root) {
361
+ for (const heading of root.querySelectorAll('[class*="_markdown_"] h2, [class*="_markdown_"] h3')) {
362
+ if (inGenui(heading) || inDiff(heading)) continue
363
+ const icon = iconFor((heading.textContent ?? '').trim())
364
+ if (icon === null) heading.removeAttribute(ICON_ATTR)
365
+ else heading.setAttribute(ICON_ATTR, icon)
366
+ }
367
+
368
+ for (const block of root.querySelectorAll('blockquote')) {
369
+ if (inGenui(block) || inDiff(block)) continue
370
+ let type = cardTypeFor(block)
371
+ if (type === null && isStatementCard(block)) type = 'statement'
372
+ if (type === null) {
373
+ block.removeAttribute(CARD_ATTR)
374
+ block.removeAttribute(CARD_TYPE_ATTR)
375
+ block.removeAttribute(LEAD_ATTR)
376
+ block.removeAttribute(TONE_ATTR)
377
+ continue
378
+ }
379
+ block.setAttribute(CARD_ATTR, '1')
380
+ block.setAttribute(CARD_TYPE_ATTR, type)
381
+ block.removeAttribute(LEAD_ATTR)
382
+ block.setAttribute(TONE_ATTR, type === 'risk' ? 'warn' : type === 'accept' ? 'success' : 'info')
383
+ }
384
+
385
+ for (const element of root.querySelectorAll('[class*="_markdown_"] p, [class*="_markdown_"] li')) {
386
+ if (inGenui(element) || inDiff(element) || element.closest('blockquote, table')) {
387
+ element.removeAttribute(STATUS_ATTR)
388
+ continue
389
+ }
390
+ const status = statusFor((element.textContent ?? '').trimStart())
391
+ if (status === null) element.removeAttribute(STATUS_ATTR)
392
+ else element.setAttribute(STATUS_ATTR, status)
393
+ }
394
+
395
+ for (const row of root.querySelectorAll('[class*="_markdown_"] table tr')) {
396
+ if (inGenui(row) || inDiff(row) || row.closest('thead') || row.closest(`[${SKIP_ATTR}="table"]`)) {
397
+ row.removeAttribute(ROW_ATTR)
398
+ continue
399
+ }
400
+ const status = statusFor((row.textContent ?? '').trimStart())
401
+ if (status === null) row.removeAttribute(ROW_ATTR)
402
+ else row.setAttribute(ROW_ATTR, status)
403
+ }
404
+
405
+ for (const table of root.querySelectorAll('[class*="_markdown_"] table')) {
406
+ if (inGenui(table)) {
407
+ table.removeAttribute(SKIP_ATTR)
408
+ table.removeAttribute(STATUS_TABLE_ATTR)
409
+ continue
410
+ }
411
+ if (inDiff(table)) {
412
+ table.setAttribute(SKIP_ATTR, 'table')
413
+ table.removeAttribute(STATUS_TABLE_ATTR)
414
+ continue
415
+ }
416
+ table.removeAttribute(SKIP_ATTR)
417
+ const rows = [...table.querySelectorAll('tbody tr')]
418
+ const statusTable =
419
+ rows.length > 0 &&
420
+ rows.every((row) => statusFor((row.cells?.[0]?.textContent ?? '').trimStart()) !== null)
421
+ if (statusTable) table.setAttribute(STATUS_TABLE_ATTR, '1')
422
+ else table.removeAttribute(STATUS_TABLE_ATTR)
423
+ }
424
+
425
+ for (const paragraph of root.querySelectorAll('[class*="_markdown_"] p')) {
426
+ if (inGenui(paragraph) || inDiff(paragraph) || paragraph.closest('blockquote, table')) {
427
+ paragraph.removeAttribute(LEAD_ATTR)
428
+ continue
429
+ }
430
+ if (/^表格结论/.test((paragraph.textContent ?? '').trimStart())) paragraph.setAttribute(LEAD_ATTR, 'table')
431
+ else paragraph.removeAttribute(LEAD_ATTR)
432
+ }
433
+
434
+ for (const code of root.querySelectorAll('[class*="_markdown_"] .md-code-block')) {
435
+ if (inGenui(code)) {
436
+ code.removeAttribute(LANG_ATTR)
437
+ code.removeAttribute(CODE_ATTR)
438
+ code.removeAttribute(SKIP_ATTR)
439
+ continue
440
+ }
441
+ if (inDiff(code) || isDiffCode(code)) {
442
+ code.removeAttribute(LANG_ATTR)
443
+ code.removeAttribute(CODE_ATTR)
444
+ code.setAttribute(SKIP_ATTR, 'code')
445
+ continue
446
+ }
447
+ code.removeAttribute(SKIP_ATTR)
448
+ code.setAttribute(CODE_ATTR, '1')
449
+ const lang = bannerLanguage(code)
450
+ if (lang.length === 0) code.removeAttribute(LANG_ATTR)
451
+ else code.setAttribute(LANG_ATTR, lang)
452
+ }
453
+ }
454
+
455
+ let active = false
456
+ let disposeLayer = null
457
+ let currentTheme = 'deepseek'
458
+
459
+ const applyTheme = (theme) => {
460
+ const safe = THEMES.includes(theme) ? theme : 'deepseek'
461
+ currentTheme = safe
462
+ if (typeof document === 'undefined') return
463
+ document.body?.setAttribute(THEME_ATTR, safe)
464
+ }
465
+
466
+ const enable = () => {
467
+ if (active) return
468
+ if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') return
469
+ if (document.body === null) return
470
+ active = true
471
+
472
+ document.getElementById(LEGACY_STYLE_ID)?.remove()
473
+ document.getElementById(STYLE_ID)?.remove()
474
+ const style = document.createElement('style')
475
+ style.id = STYLE_ID
476
+ style.textContent = CSS
477
+ document.head.appendChild(style)
478
+ document.documentElement.setAttribute(ROOT_ATTR, 'active')
479
+ document.body.setAttribute(THEME_ATTR, currentTheme)
480
+ try {
481
+ console.info('[dsh-structured] visual layer active')
482
+ } catch {
483
+ // console 不可用时忽略。
484
+ }
485
+
486
+ let disposed = false
487
+ let scheduled = false
488
+ let pending = null
489
+ const run = () => {
490
+ if (disposed) return
491
+ scheduled = false
492
+ pending = null
493
+ decorate(document)
494
+ }
495
+ const schedule = () => {
496
+ if (disposed || scheduled) return
497
+ scheduled = true
498
+ if (typeof requestAnimationFrame === 'function') pending = requestAnimationFrame(run)
499
+ else pending = setTimeout(run, 16)
500
+ }
501
+ const observer = new MutationObserver(schedule)
502
+ observer.observe(document.body, { childList: true, subtree: true, characterData: true })
503
+ run()
504
+
505
+ disposeLayer = () => {
506
+ if (disposed) return
507
+ disposed = true
508
+ active = false
509
+ disposeLayer = null
510
+ observer.disconnect()
511
+ if (pending !== null) {
512
+ if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(pending)
513
+ else clearTimeout(pending)
514
+ }
515
+ style.remove()
516
+ document.documentElement.removeAttribute(ROOT_ATTR)
517
+ document.body?.removeAttribute(THEME_ATTR)
518
+ 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]) {
519
+ for (const element of document.querySelectorAll('[' + attr + ']')) element.removeAttribute(attr)
520
+ }
521
+ try {
522
+ console.info('[dsh-structured] visual layer disposed')
523
+ } catch {
524
+ // console 不可用时忽略。
525
+ }
526
+ }
527
+ }
528
+
529
+ const disable = () => {
530
+ const layer = disposeLayer
531
+ disposeLayer = null
532
+ if (typeof layer === 'function') layer()
533
+ if (typeof document === 'undefined') return
534
+ document.getElementById(STYLE_ID)?.remove()
535
+ document.documentElement.removeAttribute(ROOT_ATTR)
536
+ document.body?.removeAttribute(THEME_ATTR)
537
+ 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]) {
538
+ for (const element of document.querySelectorAll('[' + attr + ']')) element.removeAttribute(attr)
539
+ }
540
+ }
541
+
542
+ const applyVisual = (on) => {
543
+ if (on) enable()
544
+ else disable()
545
+ }
546
+
547
+ return {
548
+ inject: [],
549
+ __test: { inDiff, isDiffCode, decorate },
550
+ apply(ctx) {
551
+ // 默认主题/视觉先启用;随后由 profile Config 的 theme/visual 覆盖。
552
+ enable()
553
+ try {
554
+ ctx?.inject?.(['configForms'], (child) => {
555
+ try {
556
+ const form = child.configForms?.get('dsh-structured')
557
+ if (!form || typeof form.getSnapshot !== 'function') return
558
+ const readValue = (value) => (value && typeof value.get === 'function' ? value.get() : value)
559
+ const read = () => {
560
+ try {
561
+ const value = form.getSnapshot()?.value ?? {}
562
+ applyTheme(readValue(value.theme))
563
+ applyVisual(readValue(value.visual) !== false)
564
+ } catch {
565
+ // 读取失败时保持当前状态,不打断回复。
566
+ }
567
+ }
568
+ read()
569
+ if (typeof form.subscribe === 'function') {
570
+ child.effect?.(() => form.subscribe(read), 'dsh-structured: theme/visual preferences')
571
+ }
572
+ } catch {
573
+ // configForms 不可用时保持默认开启。
574
+ }
575
+ })
576
+ } catch {
577
+ // 无 ctx.inject 的宿主保持默认开启。
578
+ }
579
+ try {
580
+ ctx?.effect?.(() => disable, 'dsh-structured: visual layer')
581
+ } catch {
582
+ // 没有 effect 释放器时,视觉层保持到页面卸载。
583
+ }
584
+ return disable
585
+ },
586
+ }
587
+ },
588
+ })