@changfenhuang/dsh-genui 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.
Files changed (106) hide show
  1. package/CHANGELOG.md +367 -0
  2. package/LICENSE +21 -0
  3. package/README.md +261 -0
  4. package/README.zh-CN.md +261 -0
  5. package/SKILL.md +115 -0
  6. package/cordis.patch.yml +9 -0
  7. package/demo-prompts.md +118 -0
  8. package/lib/assets/echarts.js +64 -0
  9. package/lib/assets/mermaid.js +3591 -0
  10. package/lib/assets/three.js +3844 -0
  11. package/lib/client.js +5 -0
  12. package/lib/index.js +1895 -0
  13. package/lib/invariant.js +19 -0
  14. package/lib/types/client/EChartNode.d.ts +4 -0
  15. package/lib/types/client/ErrorBoundary.d.ts +30 -0
  16. package/lib/types/client/GenuiBlock.d.ts +7 -0
  17. package/lib/types/client/PlotBlock.d.ts +44 -0
  18. package/lib/types/client/action-context.d.ts +7 -0
  19. package/lib/types/client/asset-echarts.d.ts +15 -0
  20. package/lib/types/client/asset-loader.d.ts +31 -0
  21. package/lib/types/client/asset-mermaid.d.ts +1 -0
  22. package/lib/types/client/asset-three.d.ts +1 -0
  23. package/lib/types/client/blocks/advanced.d.ts +104 -0
  24. package/lib/types/client/blocks/basic.d.ts +27 -0
  25. package/lib/types/client/blocks/charts.d.ts +38 -0
  26. package/lib/types/client/blocks/diagram/geometry.d.ts +73 -0
  27. package/lib/types/client/blocks/diagram/index.d.ts +5 -0
  28. package/lib/types/client/blocks/diagram/layout.d.ts +41 -0
  29. package/lib/types/client/blocks/diagram/theme.d.ts +35 -0
  30. package/lib/types/client/blocks/forms.d.ts +83 -0
  31. package/lib/types/client/blocks/render-node.d.ts +10 -0
  32. package/lib/types/client/blocks/state.d.ts +58 -0
  33. package/lib/types/client/dom-fence.d.ts +14 -0
  34. package/lib/types/client/echarts-lazy.d.ts +17 -0
  35. package/lib/types/client/fence-render.d.ts +58 -0
  36. package/lib/types/client/gallery.d.ts +9 -0
  37. package/lib/types/client/guard.d.ts +126 -0
  38. package/lib/types/client/index.d.ts +36 -0
  39. package/lib/types/client/interaction-store.d.ts +43 -0
  40. package/lib/types/client/mermaid-core.d.ts +8 -0
  41. package/lib/types/client/mermaid-lazy.d.ts +9 -0
  42. package/lib/types/client/mermaid-safe.d.ts +52 -0
  43. package/lib/types/client/panel-command.d.ts +29 -0
  44. package/lib/types/client/panel-store.d.ts +94 -0
  45. package/lib/types/client/panel.d.ts +15 -0
  46. package/lib/types/client/parse-partial.d.ts +37 -0
  47. package/lib/types/client/safe-math.d.ts +43 -0
  48. package/lib/types/client/scene3d-core.d.ts +21 -0
  49. package/lib/types/client/scene3d-lazy.d.ts +8 -0
  50. package/lib/types/client/spec.d.ts +610 -0
  51. package/lib/types/client/toolview.d.ts +7 -0
  52. package/lib/types/index.d.ts +1 -0
  53. package/lib/types/plugin/index.d.ts +25 -0
  54. package/lib/types/plugin/invariant.d.ts +15 -0
  55. package/lib/types/plugin/tool.d.ts +27 -0
  56. package/lib/types/shared/fence-repair.d.ts +67 -0
  57. package/package.json +121 -0
  58. package/src/client/EChartNode.tsx +242 -0
  59. package/src/client/ErrorBoundary.tsx +66 -0
  60. package/src/client/GenuiBlock.module.css +1263 -0
  61. package/src/client/GenuiBlock.tsx +166 -0
  62. package/src/client/PlotBlock.module.css +155 -0
  63. package/src/client/PlotBlock.tsx +431 -0
  64. package/src/client/action-context.ts +32 -0
  65. package/src/client/asset-echarts.ts +25 -0
  66. package/src/client/asset-loader.ts +79 -0
  67. package/src/client/asset-mermaid.ts +13 -0
  68. package/src/client/asset-three.ts +12 -0
  69. package/src/client/blocks/advanced.tsx +435 -0
  70. package/src/client/blocks/basic.tsx +116 -0
  71. package/src/client/blocks/charts.tsx +319 -0
  72. package/src/client/blocks/diagram/geometry.ts +151 -0
  73. package/src/client/blocks/diagram/index.tsx +284 -0
  74. package/src/client/blocks/diagram/layout.ts +199 -0
  75. package/src/client/blocks/diagram/theme.ts +108 -0
  76. package/src/client/blocks/forms.tsx +523 -0
  77. package/src/client/blocks/render-node.tsx +260 -0
  78. package/src/client/blocks/state.ts +61 -0
  79. package/src/client/dom-fence.tsx +641 -0
  80. package/src/client/echarts-lazy.ts +35 -0
  81. package/src/client/fence-render.tsx +230 -0
  82. package/src/client/gallery.ts +128 -0
  83. package/src/client/guard.ts +1450 -0
  84. package/src/client/index.tsx +195 -0
  85. package/src/client/interaction-store.ts +122 -0
  86. package/src/client/mermaid-core.ts +121 -0
  87. package/src/client/mermaid-lazy.ts +31 -0
  88. package/src/client/mermaid-safe.ts +122 -0
  89. package/src/client/panel-command.ts +119 -0
  90. package/src/client/panel-store.ts +471 -0
  91. package/src/client/panel.tsx +185 -0
  92. package/src/client/parse-partial.ts +162 -0
  93. package/src/client/safe-math.ts +270 -0
  94. package/src/client/scene3d-core.ts +192 -0
  95. package/src/client/scene3d-lazy.ts +26 -0
  96. package/src/client/spec.ts +767 -0
  97. package/src/client/toolview.tsx +66 -0
  98. package/src/css-modules.d.ts +5 -0
  99. package/src/index.ts +12 -0
  100. package/src/plugin/index.ts +173 -0
  101. package/src/plugin/invariant.ts +30 -0
  102. package/src/plugin/node-shims.d.ts +31 -0
  103. package/src/plugin/tool.ts +330 -0
  104. package/src/shared/fence-repair.ts +244 -0
  105. package/tsconfig.json +35 -0
  106. package/tsdown.config.ts +193 -0
@@ -0,0 +1,610 @@
1
+ /**
2
+ * GenUI spec language: the declarative component tree a model emits inside a
3
+ * ```dsh-ui fence in its reply, which GenuiBlock renders as real interactive
4
+ * UI inline in the conversation. The vocabulary is a white list — the renderer
5
+ * maps each node to DOM directly, with no arbitrary-HTML path (same
6
+ * untrusted-output stance as MarkdownText).
7
+ *
8
+ * v1 interactivity is client-side only: buttons, tabs, checkboxes, and inputs
9
+ * are operable, but events do NOT flow back to the model.
10
+ */
11
+ /** One node in the component tree. */
12
+ export type GenuiNode = GenuiText | GenuiRow | GenuiCol | GenuiGrid | GenuiCard | GenuiButton | GenuiInput | GenuiSelect | GenuiCheckbox | GenuiLink | GenuiAudio | GenuiVideo | GenuiBadge | GenuiStat | GenuiProgress | GenuiDivider | GenuiList | GenuiTable | GenuiChart | GenuiTabs | GenuiAvatar | GenuiSpacer | GenuiPlot | GenuiCallout | GenuiSteps | GenuiKeyValue | GenuiDiff | GenuiJson | GenuiCode | GenuiRadio | GenuiSubmit | GenuiSwitch | GenuiSlider | GenuiTextarea | GenuiAccordion | GenuiCopy | GenuiMermaid | GenuiScene3D | GenuiTimeline | GenuiFileTree | GenuiBreadcrumb | GenuiQuiz | GenuiDiagram | GenuiEChart;
13
+ export interface GenuiSpec {
14
+ /** Short title shown as the card banner. */
15
+ title?: string;
16
+ /** Vertical gap between root items in px. */
17
+ gap?: number;
18
+ /** Panel-only flag: `true` routes the fence to the session panel dock
19
+ * instead of the message flow (rendered by the same block, updated in
20
+ * place on every publish). */
21
+ panel?: boolean;
22
+ /** Panel APPEND flag (meaningful only with `panel: true`): instead of
23
+ * replacing the session panel, merge this spec INTO the existing one —
24
+ * same-labelled tabs get their items appended, new tabs are added, plain
25
+ * item lists are appended to the tail. Lets the model grow a panel
26
+ * incrementally without resending (or truncating) the accumulated content.
27
+ * Applies only when the fence body is fully complete, so a streaming
28
+ * partial parse never double-merges. */
29
+ append?: boolean;
30
+ /** Root component list. */
31
+ items: GenuiNode[];
32
+ }
33
+ export interface GenuiText {
34
+ type: 'text';
35
+ size?: 'h1' | 'h2' | 'h3' | 'body' | 'muted' | 'caption';
36
+ content: string;
37
+ center?: boolean;
38
+ }
39
+ export interface GenuiButton {
40
+ type: 'button';
41
+ label: string;
42
+ tone?: 'primary' | 'danger' | 'success' | 'ghost';
43
+ full?: boolean;
44
+ small?: boolean;
45
+ icon?: string;
46
+ /** v2: when set, clicking sends this action back to the model. */
47
+ action?: string;
48
+ }
49
+ export interface GenuiInput {
50
+ type: 'input';
51
+ label?: string;
52
+ placeholder?: string;
53
+ value?: string;
54
+ inputType?: 'text' | 'email' | 'password';
55
+ /** v2: when set, interaction sends this action back to the model. */
56
+ action?: string;
57
+ /**
58
+ * v2.7: stable field id — the value is persisted across refresh/re-render
59
+ * and collected by a sibling `submit` node (`fields: {id: value}`).
60
+ */
61
+ id?: string;
62
+ }
63
+ export interface GenuiSelect {
64
+ type: 'select';
65
+ label?: string;
66
+ options: string[];
67
+ /** v2: when set, interaction sends this action back to the model. */
68
+ action?: string;
69
+ /**
70
+ * v2.8: default-selected option index (a placeholder option shows instead
71
+ * when absent, so nothing is silently pre-registered).
72
+ */
73
+ selected?: number;
74
+ /**
75
+ * v2.8: stable field id — the chosen option persists across refresh and is
76
+ * collected by a sibling `submit` node (`fields: {id: value}`), like
77
+ * input/textarea ids.
78
+ */
79
+ id?: string;
80
+ }
81
+ export interface GenuiCheckbox {
82
+ type: 'checkbox';
83
+ label: string;
84
+ checked?: boolean;
85
+ /** v2: when set, interaction sends this action back to the model. */
86
+ action?: string;
87
+ }
88
+ export interface GenuiLink {
89
+ type: 'link';
90
+ label: string;
91
+ /**
92
+ * Optional http(s)/mailto target. With `href` the node renders a REAL
93
+ * anchor (target=_blank + noopener); without it the node renders as plain
94
+ * styled text — never a dead clickable-looking control.
95
+ */
96
+ href?: string;
97
+ }
98
+ /** User-controlled audio from a browser-reachable URL. */
99
+ export interface GenuiAudio {
100
+ type: 'audio';
101
+ src: string;
102
+ /** Visible caption and accessible player name. */
103
+ alt?: string;
104
+ loop?: boolean;
105
+ }
106
+ /** User-controlled video from a browser-reachable URL. */
107
+ export interface GenuiVideo {
108
+ type: 'video';
109
+ src: string;
110
+ /** Visible caption and accessible player name. */
111
+ alt?: string;
112
+ poster?: string;
113
+ loop?: boolean;
114
+ muted?: boolean;
115
+ aspectRatio?: '16:9' | '4:3' | '1:1' | '9:16';
116
+ }
117
+ export interface GenuiBadge {
118
+ type: 'badge';
119
+ label: string;
120
+ tone?: 'success' | 'warn' | 'danger' | 'accent';
121
+ icon?: string;
122
+ }
123
+ export interface GenuiStat {
124
+ type: 'stat';
125
+ label: string;
126
+ value: string;
127
+ delta?: string;
128
+ }
129
+ export interface GenuiProgress {
130
+ type: 'progress';
131
+ label?: string;
132
+ /** 0..100 */
133
+ value: number;
134
+ valueLabel?: string;
135
+ }
136
+ export interface GenuiDivider {
137
+ type: 'divider';
138
+ }
139
+ export interface GenuiAvatar {
140
+ type: 'avatar';
141
+ name: string;
142
+ color?: string;
143
+ }
144
+ export interface GenuiSpacer {
145
+ type: 'spacer';
146
+ }
147
+ export interface GenuiRow {
148
+ type: 'row';
149
+ items: GenuiNode[];
150
+ wrap?: boolean;
151
+ spacer?: boolean;
152
+ }
153
+ export interface GenuiCol {
154
+ type: 'col';
155
+ items: GenuiNode[];
156
+ gap?: number;
157
+ }
158
+ export interface GenuiGrid {
159
+ type: 'grid';
160
+ cols: number;
161
+ items: GenuiNode[];
162
+ }
163
+ export interface GenuiCard {
164
+ type: 'card';
165
+ title?: string;
166
+ items: GenuiNode[];
167
+ }
168
+ export interface GenuiList {
169
+ type: 'list';
170
+ items: Array<string | {
171
+ title: string;
172
+ desc?: string;
173
+ } | GenuiNode>;
174
+ }
175
+ export interface GenuiTable {
176
+ type: 'table';
177
+ columns: string[];
178
+ rows: Array<Array<string | number>>;
179
+ }
180
+ export interface GenuiChartDatum {
181
+ label: string;
182
+ value: number;
183
+ color?: string;
184
+ }
185
+ export interface GenuiChart {
186
+ type: 'chart';
187
+ /** Chart shape: bars (default), line (trend), donut (share). */
188
+ kind?: 'bars' | 'line' | 'donut';
189
+ data: GenuiChartDatum[];
190
+ /** Multi-series grouped bars: one series of data per entry. */
191
+ series?: Array<{
192
+ label: string;
193
+ color?: string;
194
+ data: GenuiChartDatum[];
195
+ }>;
196
+ }
197
+ export interface GenuiTab {
198
+ label: string;
199
+ items: GenuiNode[];
200
+ }
201
+ export interface GenuiTabs {
202
+ type: 'tabs';
203
+ tabs: GenuiTab[];
204
+ }
205
+ export interface GenuiPlotSeries {
206
+ /** Math expression in x (safe evaluator: sin/cos/tan/pow/sqrt/log/exp/abs/floor/ceil/round/min/max/pi/e). */
207
+ expr: string;
208
+ /** Legend label; defaults to the expression. */
209
+ label?: string;
210
+ /** Stroke color. */
211
+ color?: string;
212
+ /** v2.9 draw shape: line (default), area (fill to the baseline), scatter
213
+ * (dots only). */
214
+ kind?: 'line' | 'area' | 'scatter';
215
+ /** v2: adjustable parameters (e.g. {a: 2} in "a*sin(x)") — one slider each, live re-render. */
216
+ params?: Array<{
217
+ name: string;
218
+ value: number;
219
+ min?: number;
220
+ max?: number;
221
+ step?: number;
222
+ /** v1.5: animate this parameter (auto-play) from `value` to `animateTo`. */
223
+ animateTo?: number;
224
+ /** Animation duration in ms (default 4000). */
225
+ durationMs?: number;
226
+ /** Loop the animation (default false). */
227
+ loop?: boolean;
228
+ }>;
229
+ }
230
+ export interface GenuiPlot {
231
+ type: 'plot';
232
+ /** Functions to draw, in draw order. */
233
+ series: GenuiPlotSeries[];
234
+ xMin?: number;
235
+ xMax?: number;
236
+ yMin?: number;
237
+ yMax?: number;
238
+ title?: string;
239
+ }
240
+ export interface GenuiCallout {
241
+ type: 'callout';
242
+ tone?: 'info' | 'success' | 'warning' | 'error';
243
+ /** Short heading above the body. */
244
+ title?: string;
245
+ content: string;
246
+ }
247
+ export interface GenuiStep {
248
+ title: string;
249
+ desc?: string;
250
+ }
251
+ export interface GenuiSteps {
252
+ type: 'steps';
253
+ /** Completed steps are rendered in the accent color, the rest muted. */
254
+ current?: number;
255
+ steps: GenuiStep[];
256
+ }
257
+ export interface GenuiKeyValue {
258
+ type: 'keyvalue';
259
+ pairs: Array<{
260
+ key: string;
261
+ value: string;
262
+ }>;
263
+ }
264
+ /** 收编 dsh DiffBlock: file mutations as an inline diff. */
265
+ export interface GenuiDiff {
266
+ type: 'diff';
267
+ diffs: Array<{
268
+ path: string;
269
+ oldText: string | null;
270
+ newText: string;
271
+ }>;
272
+ }
273
+ /** 收编 dsh JsonTree: a structured JSON value for inspection. */
274
+ export interface GenuiJson {
275
+ type: 'json';
276
+ /** Any JSON value to render as a tree. */
277
+ value: unknown;
278
+ }
279
+ /** 收编 dsh CodeBlock: syntax-highlighted code with an explicit language. */
280
+ export interface GenuiCode {
281
+ type: 'code';
282
+ lang?: string;
283
+ code: string;
284
+ }
285
+ export interface GenuiRadio {
286
+ type: 'radio';
287
+ label?: string;
288
+ options: string[];
289
+ /** Default-selected option index. */
290
+ selected?: number;
291
+ /** v2: when set, interaction sends this action back to the model. */
292
+ action?: string;
293
+ /**
294
+ * v2.5: aggregation group name. When set, the selection is recorded into
295
+ * the block-wide answers registry instead of firing a per-click action — a
296
+ * sibling `submit` node then collects ALL groups ("交卷" pattern). Without
297
+ * `group`, the legacy per-click behavior applies.
298
+ */
299
+ group?: string;
300
+ /**
301
+ * v2.6: correct option for LOCAL grading — the option index (number) or
302
+ * its label (string). When the group's radio carries this, the `submit`
303
+ * button grades IN PLACE on click (score + per-question right/wrong +
304
+ * explanations, zero model round trip).
305
+ */
306
+ answer?: number | string;
307
+ /** v2.6: per-question explanation shown after local grading. */
308
+ explanation?: string;
309
+ }
310
+ /** Submit node: collects the answers of sibling `radio` groups in this block.
311
+ * LOCAL-FIRST: when the questions carry `answer` data the click grades IN
312
+ * PLACE — score, per-question right/wrong, explanations — with no model
313
+ * round trip, and locks the questions until "重新作答" resets them. Only when
314
+ * NO question carries answers does it fall back to firing ONE action
315
+ * (`{type:'submit', answers, total, answered}`). */
316
+ export interface GenuiSubmit {
317
+ type: 'submit';
318
+ label: string;
319
+ /**
320
+ * Optional action name. Local-first: when ANY question in scope carries
321
+ * `answer` data the click grades IN PLACE with zero model round trip, so
322
+ * no action is needed — the spec stays valid without one. Only when NO
323
+ * question has local answers does the submit need an action to collect
324
+ * `{type:'submit', answers, total, answered}`; without one the button
325
+ * renders disabled (honest affordance). Also fired as the reset hook if
326
+ * `resetAction` is absent.
327
+ */
328
+ action?: string;
329
+ /**
330
+ * v2.6: optional action fired when the user clicks "重新作答" after a
331
+ * local grading (e.g. to tell the model the paper was redone). Absent =
332
+ * reset stays fully local.
333
+ */
334
+ resetAction?: string;
335
+ /**
336
+ * Optional explicit group list to wait for; when absent the submit enables
337
+ * once at least one grouped radio has an answer. When present it stays
338
+ * disabled until EVERY listed group has a recorded answer (the hint shows
339
+ * the progress).
340
+ */
341
+ groups?: string[];
342
+ }
343
+ export interface GenuiSwitch {
344
+ type: 'switch';
345
+ label: string;
346
+ checked?: boolean;
347
+ /** v2: when set, interaction sends this action back to the model. */
348
+ action?: string;
349
+ }
350
+ /**
351
+ * Slider: a range input for numeric form values (v2.9). The current value
352
+ * shows next to the track; dragging fires the action (debounced by the
353
+ * block) and, with an `id`, persists across refresh and joins the sibling
354
+ * submit's `fields` collection (the value is the numeric string).
355
+ */
356
+ export interface GenuiSlider {
357
+ type: 'slider';
358
+ label?: string;
359
+ min?: number;
360
+ max?: number;
361
+ step?: number;
362
+ /** Default value; defaults to `min` (or 0). */
363
+ value?: number;
364
+ /** v2: when set, interaction sends this action back to the model. */
365
+ action?: string;
366
+ /** v2.9: stable field id — durable value + submit collection. */
367
+ id?: string;
368
+ }
369
+ export interface GenuiTextarea {
370
+ type: 'textarea';
371
+ label?: string;
372
+ placeholder?: string;
373
+ rows?: number;
374
+ value?: string;
375
+ /** v2.5: when set, blurring the field sends this action with the value. */
376
+ action?: string;
377
+ /**
378
+ * v2.7: stable field id — the value is persisted across refresh/re-render
379
+ * and collected by a sibling `submit` node (`fields: {id: value}`).
380
+ */
381
+ id?: string;
382
+ }
383
+ export interface GenuiAccordionItem {
384
+ title: string;
385
+ items: GenuiNode[];
386
+ }
387
+ export interface GenuiAccordion {
388
+ type: 'accordion';
389
+ items: GenuiAccordionItem[];
390
+ }
391
+ export interface GenuiCopy {
392
+ type: 'copy';
393
+ label?: string;
394
+ text: string;
395
+ }
396
+ /** Mermaid diagram: flowchart/sequence/class/gantt source rendered lazily. */
397
+ export interface GenuiMermaid {
398
+ type: 'mermaid';
399
+ /** Mermaid diagram source (whitelisted diagram kinds at render time). */
400
+ code: string;
401
+ }
402
+ /** One mesh in a 3D scene. */
403
+ export interface GenuiMesh {
404
+ /** Primitive geometry, white-listed. */
405
+ shape: 'box' | 'sphere' | 'cone' | 'cylinder' | 'torus';
406
+ /** Mesh color. */
407
+ color?: string;
408
+ /** [x, y, z] position. */
409
+ position?: [number, number, number];
410
+ /** [x, y, z] rotation in radians. */
411
+ rotation?: [number, number, number];
412
+ /** Uniform scale, or [sx, sy, sz]. */
413
+ scale?: number | [number, number, number];
414
+ /** Box size [w, h, d] or sphere/cone/cylinder radius. */
415
+ size?: number | [number, number, number];
416
+ }
417
+ /** 3D scene rendered with three.js (lazily imported). */
418
+ export interface GenuiScene3D {
419
+ type: 'scene3d';
420
+ title?: string;
421
+ meshes: GenuiMesh[];
422
+ /** Ambient light intensity (0..2). */
423
+ ambient?: number;
424
+ /** Background color; default transparent. */
425
+ background?: string;
426
+ }
427
+ export interface GenuiTimelineItem {
428
+ /** Title of the event. */
429
+ title: string;
430
+ /** Secondary description. */
431
+ desc?: string;
432
+ /** Optional timestamp label. */
433
+ time?: string;
434
+ }
435
+ export interface GenuiTimeline {
436
+ type: 'timeline';
437
+ items: GenuiTimelineItem[];
438
+ }
439
+ export interface GenuiFileTreeNode {
440
+ /** File or directory name. */
441
+ name: string;
442
+ /** Directory when true (collapsible children). */
443
+ type?: 'file' | 'dir';
444
+ /** Child nodes (dirs only). */
445
+ children?: GenuiFileTreeNode[];
446
+ }
447
+ export interface GenuiFileTree {
448
+ type: 'file-tree';
449
+ items: GenuiFileTreeNode[];
450
+ }
451
+ export interface GenuiBreadcrumb {
452
+ type: 'breadcrumb';
453
+ items: string[];
454
+ }
455
+ export interface GenuiQuizOption {
456
+ label: string;
457
+ /** True when this option is the correct answer. */
458
+ correct?: boolean;
459
+ /** Per-option feedback shown after selection. */
460
+ feedback?: string;
461
+ }
462
+ export interface GenuiQuiz {
463
+ type: 'quiz';
464
+ /** The question text. */
465
+ question: string;
466
+ /** Answer options; exactly one should be correct. */
467
+ options: GenuiQuizOption[];
468
+ /** Shown after answering (either way). */
469
+ explanation?: string;
470
+ /** Reset the quiz when this value changes (e.g. a question id). */
471
+ id?: string;
472
+ /** v2.5: when set, answering sends this action with the chosen answer
473
+ * (`{type:'quiz', question, answer, correct}`) so the model can collect
474
+ * or grade it. */
475
+ action?: string;
476
+ }
477
+ /** Editorial diagram kinds, ported from cathrynlavery/diagram-design (27 types). */
478
+ export declare const DIAGRAM_KINDS: readonly ["architecture", "it-state", "flowchart", "sequence", "state", "er", "timeline", "swimlane", "quadrant", "radar", "loop", "nested", "tree", "org-chart", "layers", "venn", "pyramid", "bar", "line", "gantt", "scatter", "high-level", "process", "medallion", "data-flow", "dp-integration", "dp-security-matrix"];
479
+ export type GenuiDiagramKind = typeof DIAGRAM_KINDS[number];
480
+ /** Diagram surface variant: light / dark follow the host theme; editorial is the full skin. */
481
+ export type GenuiDiagramVariant = 'light' | 'dark' | 'editorial';
482
+ /** Node treatment classes (map to the diagram-design semantic fills/strokes). */
483
+ export type GenuiDiagramNodeType = 'focal' | 'backend' | 'store' | 'external' | 'input' | 'optional' | 'security';
484
+ /** One positioned node in a coordinate-mode diagram. */
485
+ export interface GenuiDiagramNode {
486
+ /** Stable node id referenced by edges. */
487
+ id: string;
488
+ /** Human-readable label (Geist sans in the renderer). */
489
+ label: string;
490
+ /** Optional technical sublabel (Geist mono in the renderer). */
491
+ sub?: string;
492
+ /** Semantic treatment; `focal` is budgeted (max 2 per diagram). */
493
+ type?: GenuiDiagramNodeType;
494
+ /** Position / size (coordinate-mode kinds only). 4px grid is enforced at render. */
495
+ x?: number;
496
+ y?: number;
497
+ w?: number;
498
+ h?: number;
499
+ /** Optional rectangular type tag (e.g. `API`). */
500
+ tag?: string;
501
+ }
502
+ /** One connector between two nodes. */
503
+ export interface GenuiDiagramEdge {
504
+ /** Source node id. */
505
+ from: string;
506
+ /** Target node id. */
507
+ to: string;
508
+ /** Optional edge label (all-caps, <=14 chars in the renderer). */
509
+ label?: string;
510
+ /** Stroke semantics: solid (default), dashed (optional/async), accent, link (HTTP/API). */
511
+ kind?: 'solid' | 'dashed' | 'accent' | 'link';
512
+ /** Route hint; `auto` (default) lets the renderer choose orthogonal routing. */
513
+ route?: 'auto' | 'orthogonal' | 'straight';
514
+ }
515
+ /** Optional brand override; every key maps to a diagram-design semantic token. */
516
+ export interface GenuiDiagramTheme {
517
+ paper?: string;
518
+ 'paper-2'?: string;
519
+ ink?: string;
520
+ muted?: string;
521
+ soft?: string;
522
+ rule?: string;
523
+ accent?: string;
524
+ 'accent-tint'?: string;
525
+ link?: string;
526
+ }
527
+ /** Zone container: a labelled region grouping related nodes (max 3). */
528
+ export interface GenuiDiagramZone {
529
+ /** Zone label (mono uppercase eyebrow in the renderer). */
530
+ label: string;
531
+ /** Bounding box in the same canvas space as the nodes. */
532
+ x?: number;
533
+ y?: number;
534
+ w?: number;
535
+ h?: number;
536
+ }
537
+ /**
538
+ * Editorial diagram (diagram-design port): a white-listed declarative diagram
539
+ * rendered as inline SVG by the browser. `kind` selects the layout grammar
540
+ * (coordinate kinds place nodes via x/y; rule kinds auto-layout from data),
541
+ * `variant` selects the skin, and `theme` overrides semantic tokens. The
542
+ * renderer enforces the editorial constraints — orthogonal connectors, 4px
543
+ * grid, complexity budget, focal accent budget — so the model cannot emit
544
+ * "AI slop" schematics.
545
+ */
546
+ export interface GenuiDiagram {
547
+ type: 'diagram';
548
+ /** The 27-type editorial vocabulary. */
549
+ kind: GenuiDiagramKind;
550
+ /** Surface skin; default follows the host theme. */
551
+ variant?: GenuiDiagramVariant;
552
+ /** Optional diagram title (Instrument Serif in the renderer). */
553
+ title?: string;
554
+ /** Nodes (coordinate kinds use x/y/w/h; rule kinds may omit positions). */
555
+ nodes: GenuiDiagramNode[];
556
+ /** Connectors. */
557
+ edges?: GenuiDiagramEdge[];
558
+ /** Optional labelled regions grouping nodes (max 3). */
559
+ zones?: GenuiDiagramZone[];
560
+ /** Optional semantic-token overrides. */
561
+ theme?: GenuiDiagramTheme;
562
+ }
563
+ /** Preset chart kinds the `echart` node can build from `data`/`series` without
564
+ * a full ECharts option. Each maps to a themed option template. */
565
+ export type EChartPreset = 'bar' | 'line' | 'area' | 'pie' | 'scatter';
566
+ /** ECharts node: renders a full ECharts chart. Two modes:
567
+ *
568
+ * 1. **Full option** (`option` set): the model provides a standard ECharts
569
+ * `EChartsCoreOption` object directly. This is the escape hatch for
570
+ * custom chart types, complex series, or advanced features (dataZoom,
571
+ * visualMap, etc.).
572
+ * 2. **Preset shorthand** (`preset` + `data`/`series`): the model provides
573
+ * the same simple `data`/`series` shape as the `chart` node, and the
574
+ * component builds a themed ECharts option automatically. This is the
575
+ * easy upgrade path: change `type: 'chart'` to `type: 'echart'` and add
576
+ * `preset`.
577
+ *
578
+ * The echarts engine is lazy-loaded (lib/assets/echarts.js) only when an
579
+ * `echart` node appears in a spec. */
580
+ export interface GenuiEChart {
581
+ type: 'echart';
582
+ /** Optional title shown above the chart. */
583
+ title?: string;
584
+ /** Chart height in pixels (default 300). */
585
+ height?: number;
586
+ /** Preset: builds the ECharts option from `data`/`series` when `option`
587
+ * is absent. */
588
+ preset?: EChartPreset;
589
+ /** Simple data for preset mode (same shape as `chart.data`). */
590
+ data?: GenuiChartDatum[];
591
+ /** Multi-series for preset mode (same shape as `chart.series`). */
592
+ series?: Array<{
593
+ label: string;
594
+ color?: string;
595
+ data: GenuiChartDatum[];
596
+ }>;
597
+ /** Full ECharts option object. When present, `preset`/`data`/`series` are
598
+ * ignored. This is a pass-through to `echarts.setOption`. */
599
+ option?: Record<string, unknown>;
600
+ }
601
+ /** Parse the raw fence body as a GenuiSpec, or null when it is not one. */
602
+ export declare function parseGenuiSpec(raw: string): GenuiSpec | null;
603
+ /**
604
+ * Wrap a bare component object into a col root. Returns null when `value` is
605
+ * not component-shaped (no usable `type`). `panel`/`append` live on the root
606
+ * spec, so they are hoisted onto the wrapper.
607
+ */
608
+ export declare function wrapSingleComponentRoot(value: unknown): GenuiSpec | null;
609
+ /** Basic structural guard: is this object a valid GenuiSpec? */
610
+ export declare function isGenuiSpec(value: unknown): value is GenuiSpec;
@@ -0,0 +1,7 @@
1
+ import type { ToolCallViewProps } from '@deepseek-ai/dsh-client-ui-tool/client';
2
+ /**
3
+ * Keyed toolview for the `render_ui` tool. `block` is the settled result
4
+ * node once the call completes; while it runs (or on replay without meta)
5
+ * the summary fallback is shown.
6
+ */
7
+ export declare function GenuiToolView({ toolName, block, sessionId }: ToolCallViewProps): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from './plugin/index';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * GenUI plugin: teaches the model the ```dsh-ui fence syntax for emitting
3
+ * declarative UI components inline in its reply. The browser half renders the
4
+ * fence through GenuiBlock (ui-primitives); this host half only tells the
5
+ * model the language exists, so a session without the plugin simply never
6
+ * emits fences and nothing changes.
7
+ *
8
+ * The section is a convention section (order 100-199), placed after the bash
9
+ * guidance so the model sees it among its output-format rules.
10
+ * @module @changfenhuang/dsh-genui
11
+ */
12
+ import { Context } from '@deepseek-ai/cordis';
13
+ /** Convention: tool guidance uses 100–199; bash's section is 104. */
14
+ export declare const GENUI_SECTION_ORDER = 105;
15
+ /** The fence language description injected into every assembled system prompt.
16
+ * Deliberately slim: the `genui` skill carries the full component→field
17
+ * mapping; this section keeps only the contract that must always be
18
+ * present (fence syntax, type whitelist, and critical behavioral rules). */
19
+ export declare const GENUI_SECTION_TEXT = "You can render interactive UI components INSIDE your reply \u2014 between paragraphs \u2014 by emitting a fenced block with the language tag `dsh-ui` containing a JSON spec:\n\n```dsh-ui\n{\"title\":\"\u53EF\u9009\u6807\u9898\",\"gap\":14,\"items\":[...]}\n```\n\nThe spec is a white-listed component tree rendered inline where the fence sits. Only these `type` values; the `genui` skill, when available, carries the full content\u2192component mapping and per-component field details:\n\n- \u5E03\u5C40: text \u00B7 row \u00B7 col \u00B7 grid \u00B7 card \u00B7 divider \u00B7 spacer\n- \u5C55\u793A: badge \u00B7 stat \u00B7 progress \u00B7 list \u00B7 table \u00B7 keyvalue \u00B7 avatar \u00B7 audio \u00B7 video \u00B7 timeline \u00B7 file-tree \u00B7 breadcrumb \u00B7 callout \u00B7 steps \u00B7 diff \u00B7 json \u00B7 code \u00B7 copy\n- \u56FE\u8868: chart (bars|line|donut) \u00B7 echart (preset|option) \u00B7 plot (\u51FD\u6570\u56FE)\n- \u4EA4\u4E92: button \u00B7 input \u00B7 textarea \u00B7 select \u00B7 checkbox \u00B7 switch \u00B7 slider \u00B7 radio \u00B7 submit \u00B7 quiz \u00B7 link \u00B7 tabs \u00B7 accordion\n- \u9AD8\u7EA7: mermaid (flowchart/sequence/class/gantt/pie/er/state/journey) \u00B7 diagram (\u7F16\u8F91\u7EA7\u67B6\u6784/\u6D41\u7A0B\u56FE\uFF0C27 \u79CD kind) \u00B7 scene3d (3D WebGL)\n\nRules:\n- \u89E6\u53D1: \u7ED3\u6784\u5316\u8868\u8FBE\u4F18\u4E8E\u7EAF\u6587\u672C\u65F6\u4E3B\u52A8\u7528\uFF08\u8981\u70B9\u3001\u5F3A\u8C03\u3001\u5BF9\u6BD4\u3001\u6D41\u7A0B\u3001\u6B65\u9AA4\u3001\u72B6\u6001\u3001\u6570\u636E\u3001\u6F14\u793A\uFF09\uFF0C\u7EAF\u95EE\u7B54\u4E0E\u4E00\u53E5\u8BDD\u4E0D\u5957 UI\uFF1B\u4E00\u4E2A\u4E3B\u9898\u4E00\u4E2A\u4E3B\u7EC4\u4EF6\uFF0C\u6BCF\u6B21 3\u20138 \u4E2A\u7EC4\u4EF6\uFF0C\u540C\u4E00\u6570\u636E\u4E0D\u91CD\u590D\u51FA\u73B0\u3002\n- JSON \u4E25\u683C: \u574F\u56F4\u680F\u964D\u7EA7\u4E3A\u4EE3\u7801\u5757\uFF1B\u22653 \u8282\u70B9\u6216\u542B table \u7684\u56F4\u680F\u53D1\u51FA\u524D\u8C03\u7528 validate_dsh_ui\uFF0C\u274C \u4FEE\u597D\u518D\u53D1\uFF08\u82E5\u9644\u300C\u5DF2\u81EA\u52A8\u4FEE\u590D\u300DJSON \u7167\u6284\u5373\u53EF\uFF09\u3002\n- \u89C4\u6A21: \u2264200 \u8282\u70B9\u3001\u5D4C\u5957\u22648 \u5C42\uFF08\u8D85\u51FA\u88AB\u622A\u65AD\uFF09\uFF1B3D mesh 1\u20135\uFF1Bplot \u7ED9\u5408\u7406 xMin/xMax\u3002\n- LOCAL-FIRST + actions: UI \u80FD\u81EA\u5DF1\u505A\u7684\u72B6\u6001\u53D8\u5316\uFF08\u5224\u5377\u3001\u5224\u9898\u3001\u91CD\u7F6E\u3001\u5C55\u5F00\u3001\u9009\u4E2D\uFF09\u5C31\u5730\u5B8C\u6210\uFF0C\u96F6\u5F80\u8FD4\uFF1Baction \u53EA\u7528\u4E8E\u5FC5\u987B\u6A21\u578B\u53C2\u4E0E\u7684\u4E8B\u3002\u4EA4\u4E92\u7EC4\u4EF6\u5E26 \"action\":\"name\"\uFF0C\u4EA4\u4E92\u4EE5 [genui-action] name + \u7EC4\u4EF6\u6570\u636E\u56DE\u4F20\uFF0C\u5C4A\u65F6\u91CD\u6E32\u67D3\u66F4\u65B0 UI\uFF1B\u65E0 action \u7684\u6309\u94AE\u7981\u7528\u3002\n- Durable state: \u4EA4\u4E92\u72B6\u6001\u6309\u300C\u4F1A\u8BDD+\u5185\u5BB9\u6307\u7EB9\u300D\u6301\u4E45\u5316\u2014\u2014\u5237\u65B0/\u91CD\u653E\u6062\u590D\uFF1B\u91CD\u6E32\u67D3\u76F8\u540C\u5185\u5BB9\u4FDD\u7559\uFF0C\u65B0\u5185\u5BB9\u91CD\u7F6E\u3002\n- \u5377\u5B50\u6A21\u5F0F: \u6BCF\u9898\u4E00\u4E2A radio\uFF08group+answer+explanation\uFF09+ \u4E00\u4E2A submit\uFF08groups \u5168\u5217\uFF09\uFF0C\u672C\u5730\u5224\u5206\u3002\n- Secrets ban: \u4E0D\u7D22\u53D6\u5BC6\u7801\u3001API Key\u3001Token\u3001\u6062\u590D\u7801\uFF1B\u9700\u8981\u65F6\u62D2\u7EDD\u5E76\u89E3\u91CA\u3002\n- Tool channel: render_ui \u5DE5\u5177\u628A\u540C\u4E00 spec \u6E32\u67D3\u4E3A\u5DE5\u5177\u884C\u5361\u7247\uFF08\u4EA4\u4ED8\u7269\u578B\u754C\u9762\u7528\uFF09\uFF1B\u56F4\u680F\u7528\u4E8E\u56DE\u7B54\u5185\u8054 UI\u3002\n- Panel: \"panel\":true \u53EA\u6E32\u67D3\u8FDB\u4F1A\u8BDD\u9762\u677F dock \u5E76\u539F\u5730\u66F4\u65B0\uFF1B\"append\":true \u8FFD\u52A0\u5408\u5E76\uFF08\u540C\u6807\u7B7E tabs \u8FFD\u52A0/\u65B0\u6807\u7B7E\u52A0\u5165/\u5C3E\u90E8\u8FFD\u52A0\uFF09\uFF1B\u4E0A\u9650 200 \u8282\u70B9/200 \u6B21\u8FFD\u52A0\uFF0C\u6EE1\u4E86\u53D1 replace \u91CD\u5EFA\u3002\u9762\u677F\u7EC4\u4EF6\u6765\u7684 [genui-action] \u53EA\u56DE\u4E00\u4E2A panel:true \u56F4\u680F + \u81F3\u591A\u4E00\u884C 10 \u5B57\u5185\u786E\u8BA4\uFF0C\u4E0D\u89E3\u91CA\u3001\u4E0D\u7528\u666E\u901A\u56F4\u680F\u3002";
20
+ /**
21
+ * Register the GenUI output-language section and the render_ui tool.
22
+ * @param ctx - cordis context.
23
+ */
24
+ export declare const inject: string[];
25
+ export declare function apply(ctx: Context): void;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Package-owned invariant companion for `@changfenhuang/dsh-genui`.
3
+ * @module @changfenhuang/dsh-genui/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "genui-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;