--- name: genui description: "Render structured interactive UI inline through the dsh-ui fence. Use for key points, emphasis, comparisons, flows, steps, status, data, demos, and interactions whenever structured presentation would be clearer than prose. Preserve conversation language for all user-visible text." --- # GenUI — 生成式 UI 输出规范 **Language:** Match the user's requested language (otherwise the language of their message) in both surrounding prose and all user-visible UI text: titles, labels, content, options, and explanations. Chinese examples below illustrate the schema only; do not switch the conversation to Chinese after loading this skill. Keep JSON keys, component types, IDs, and actions unchanged. An English request gets English prose and UI text; a Chinese request gets Chinese prose and UI text. `` tokens are meta-placeholders only. Replace every one with actual content in the conversation language; never emit these placeholders literally. 你可以**在回答正文中间**输出可交互 UI 组件:写一个 `dsh-ui` 围栏(fenced block with language tag `dsh-ui`),内含 JSON 规格,渲染器会把这一整块画成真实组件,文字照常穿插在前后。组件**就是回答的一部分**,不是工具调用。 ```dsh-ui {"title":"","gap":14,"items":[...]} ``` ## 围栏书写铁律(先读这一节) 围栏开头那一行的语言标记**统一写 `dsh-ui`**,不要添加标点、引号或注释。渲染器会容错大小写、首尾空格和部分尾部标点;其他语言标记不会作为 GenUI 接管,用户会看到普通代码块。 | 写法 | 结果 | | --- | --- | | ` ```dsh-ui ` | ✅ 渲染 | | ` ```dsh-ui> ` | ✅ 容错渲染;生成时仍应去掉 `>` | | ` ```dsh-ui ` 带行尾空格 | ✅ 渲染(自动 trim) | | ` ``` DSH-UI ` | ✅ 渲染(忽略大小写) | | ` ```dshui ` / ` ```ds-ui ` | ❌ 不是合法标记 | 此前同一模型曾在同一会话里写出 **182** 个 ` ```dsh-ui> ` 围栏并导致静默失效;现在已对这类尾部标点统一容错。写围栏时**只写 `dsh-ui` 这六个字符**,不要加任何标点、引号、注释;收尾必须是单独一行的 ` ``` `。 围栏位置:只写在**回答正文**里。写进 reasoning / 思考块不会渲染,用户看不到。 公式:`$...$` / `\(...\)` 为行内公式,`$$...$$` / `\[...\]` 为独立公式;支持矩阵、分段函数、多行对齐推导,以及加粗/高亮内的公式。正文、列表、键值、表头/单元格、卡片/步骤/时间线/标签页标题、题目与说明、按钮与表单标签、指标、媒体说明和图表外层标题共用此能力。JSON 字符串中的反斜杠必须双写,如 `"content": "\\(\\frac{a}{b}\\)"`。代码源码、输入值/占位符、原生下拉选项和图表引擎内部绘图文本仍遵循各自的原生格式,不解析富文本。数学语法以 KaTeX 为准,不执行 HTML、外部资源或脚本;不支持 LaTeX 文档编译、加载任意宏包。 ## 组件词汇(只允许这些 type) 布局:`text` `row` `col` `grid` `card` `carousel` `divider` `spacer` `arrow` `hero` 展示:`stat` `badge` `progress` `list` `table` `keyvalue` `avatar` `image` `image-group` `compare` `audio` `video` `timeline` `file-tree` `breadcrumb` `diff` `json` `code` `callout` `steps` `icon` `blocks` 图表:`chart`(bars/line/donut,可多序列)`plot`(数学函数图)`echart`(ECharts 全功能图表) 交互:`button` `input` `select` `checkbox` `radio` `switch` `slider` `textarea` `editor` `date-picker` `segmented-control` `tabs` `accordion` `copy` `link` `submit` `popover` 高级:`svg` `mermaid` `diagram` `scene3d` `quiz` `entity-card` ### 布局 - text: `{"type":"text","size":"display|h1|h2|h3|body|muted|caption","content":"...","align":"left|center|right"?}`(`tone:"danger|success|warning|info|muted"` 给整行文字上语义色,不用套 badge;`mono:true` 走等宽字体,用于十六进制/二进制/标识符,不带行内代码底色)(`size:"display"` 比标题大一档,作引言/开篇点题句,**一条回答 ≤1**;`center:true` 是旧写法,等价 `align:"center"`,新写一律用 `align`) - row / col: `{"type":"row"|"col","items":[...],"wrap":true?,"spacer":true?,"gap":n?}`;`row.justify:"center"` 主轴居中(图标+标题那种图头);`col.width:n`(1-100)给这一列占行宽的百分比,不写就按内容宽 - grid: `{"type":"grid","cols":n,"items":[...]}`;`columns` 可作为 `cols` 的别名,校验会提示使用规范字段。 - carousel: `{"type":"carousel","title":"","items":[{"type":"card","title":"...","items":[...]}],"itemWidth":240?,"gap":3?}` — 原生节点横向滚动;最多 24 项,宽度 80–640px 且不超过轨道宽度的 85%;默认宽度 240px。`gap` 数值乘以 4px,省略时为 12px,负数按 0 处理。导航按钮每次滚动轨道宽度的 85%,仅在内容溢出时显示,触摸设备直接滑动轨道。不自动播放。轨道聚焦时支持左右键 / Home / End,卡片内表单按原提交规则工作。浏览与滑动不发 action;子组件自身声明的 action 按既有交互规则触发。流式输出只在尾部追加卡片以保留已有输入路径。 - hero: `{"type":"hero","title":"","subtitle":"","value":"99.96%","label":"","delta":"+0.02%","spark":[...],"tone":"accent|success|warning|danger"}` — **封面块**:eyebrow + 超大数字(52px,带入场计数)+ 标题 + 副标题 + tone 渐变底色。**一条回答最多用一个**,放在最前面当视觉锚点 - span: 任意节点都可加 `"span":2`(grid 子节点占几列)——bento 排版的唯一原语:一张 `span:2` 宽卡配一张窄卡,比一列方块堆下去好看得多 - card: `{"type":"card","title":"...","items":[...]}`;`"tone":"info|success|warning|danger"` 给卡片语义底色(用于结论卡/风险卡);`"accent":"#f59e0b"` 指定强调色(边框 + 标题 + 极淡底色);`"flat":true` 去掉阴影、换成极淡中性底——**图里的面板必须用 flat**,否则每层都像浮起的卡片,叠两层就脏了;`"eyebrow":""` 在标题上方加一行 meta 小字(把多张卡分章时用——**连续同章的卡片不要每张都加**,一段回答 ≤3 个) - palette: `chart` / `echart` 都支持 `"palette":["#ff8800","#3ecf8e"]` 覆盖分类色板(默认跟随宿主主题)。**只有语义上需要指定颜色时才写**(成本=红、收益=绿),否则跟随主题更稳 - divider: `{"type":"divider"}`;**带章节名** `{"type":"divider","label":""}`(线 + 小标签居中,作章节切换:报告从摘要切入数据、图表册开新章时用一段;卡片/列表内部、连续同主题内容之间**不要用**,一条回答 ≤3 个); spacer: `{"type":"spacer"}` ### 展示 - stat: `{"type":"stat","label":"...","value":"...","delta":"+12.4%|-3%"}`(`-` 开头自动红、`+` 绿);可选 `"spark":[3,5,4,8,6]` 画一条微趋势线(2–60 个有限数值);`"size":"hero"` 渲染超大数字(一条回答最多用一次,作为视觉锚点) - badge: `{"type":"badge","label":"...","tone":"success|warn|danger|accent","icon":"emoji?"}` - progress: `{"type":"progress","label":"...","value":0-100,"valueLabel":"70%"}`;`"variant":"ring"` 画环形进度,`"target":70` 在轨道上标出目标刻度 - avatar: `{"type":"avatar","name":"...","color":"#hex?"}` - image: `{"type":"image","src":"/mmx-files/result.png","alt":""}` — 展示浏览器可访问的 http(s) 或同源相对图片地址;懒加载;不支持 `file:`/`data:` 等本地或主动协议;`"caption":""` 在图**下方**加说明行(来源/解读/署名;`alt` 是图**上方**的标签行,两者分工不同) - image-group: `{"type":"image-group","title":"","items":[{"src":"/mmx-files/a.png","alt":"","caption":""},{"src":"/mmx-files/b.png","alt":""}]}` — 最多 24 张图片按长宽比自适应换行;点击原生 lightbox 放大,左右键 / Home / End 切图,Esc / 关闭按钮返回并恢复焦点。沿用 image 的 http(s) / 相对地址限制;不接受 file/data/javascript。只做本地浏览,不发 action;需要模型动作请在旁边放独立 button。追加 / 重排保留当前 src;当前图片移除后关闭查看器;相同 src 按出现次序区分。空列表与图片加载失败都有明确状态。导出 HTML 不内嵌图片字节,图片仍依赖原地址。[完整用法与验收](docs/gallery.md)。 - compare: `{"type":"compare","title":"","before":{"src":"/shot-before.png","label":"","caption":""},"after":{"src":"/shot-after.png","label":""},"mode":"side|stack|slider","height":260}` — **两张图成对呈现**:改动前后、方案 A/B、原图与重绘。`before`/`after` 都必填,缺一侧整节点丢弃(不是降级成单图)。`mode`:`side`(默认,容器够宽时并排,窄于 560px 自动竖排)、`stack`(始终竖排,适合长截图)、`slider`(单框中间一条可拖动分割线,指针拖动 / 触摸 / 方向键 / Home / End 都能用)。`height` 是框高上限 120–640(默认 260),图片按 contain 装进框内、不裁切。标签 `label` 常显,不靠悬停;点击图片原地开原生对话框放大,Esc / 关闭返回并恢复焦点。图片地址沿用 image 的 http(s) / 相对地址限制;只做本地浏览不发 action。**汇报改动(修了什么、前后差异)时默认用它**,而不是表格或纯文字清单——读者要的是「变成什么样了」,不是字段名。只在两张图必须被对照阅读时才用;同一批无关截图用 image-group。 **本机截图怎么进回答**:`image` / `image-group` / `compare` 的 `src` 只接受浏览器能拿到的地址。本机文件走宿主已有的鉴权文件路由,直接写 URL,不用搬文件、不用配目录: `/api/file?path=<绝对路径,percent-encoded>`(例:`/api/file?path=%2FUsers%2Fme%2Fproj%2F.a.png`)。宿主用同一个路由渲染 Markdown 里的本地图片,支持 png/jpeg/webp/gif/svg,带大小上限与沙箱策略。典型用法:同一处改动前后各截一张,用 `compare` 摆出来,而不是用文字描述"改了什么"。 - audio: `{"type":"audio","src":"/mmx-files/result.mp3","alt":"","loop":true?}` — 原生控制条;用户主动播放,不自动播放;仅 http(s) 或同源相对地址 - video: `{"type":"video","src":"/mmx-files/result.mp4","alt":"","poster":"/mmx-files/poster.jpg"?,"loop":true?,"muted":true?,"aspectRatio":"16:9|4:3|1:1|9:16"?}` — 原生播放/音量/全屏控制;不自动播放 - list: `{"type":"list","items":["..."] 或 [{"title":"...","desc":"..."}] 或嵌套节点(如 {"type":"badge","label":"TS"})}` — 行内可嵌节点(计入节点预算);记录中的 `label`/`name` 归一化为 `title`,`description`/`content`/`text`/`body`/`detail` 归一化为 `desc`;只有正文时将正文用作标题,未知字段会收到警告 - table: `{"type":"table","columns":["..."],"rows":[["...","..."]],"types":["text|num|delta|bar|badge"]?,"details":[[...]]?,"total":true?}` — 表头点击本地排序(升/降/还原,零往返);数值感知:千分位(`1,234`)、`k/m/b`、`万/亿`、`%`、货币符号都能按真实数值比较,纯数值列自动右对齐;**带符号单元格自动着色**(`+12.4%` 绿、`-3` 红,无需额外字段);`types` 可按列指定 `bar`(0-100 内联进度条)、`ring`(0-100 小环)、`spark`(单元格写 `"3,5,4,8"` 画微趋势线)、`badge`(胶囊标签)、`delta`(强制涨跌色)、`num`(强制右对齐)、`index`(行号)、`group`(首列当分组标题:该行只有第一格有内容时渲染成跨列小标题);`"total":true` 追加合计行(数值列自动求和);**`"export":true`**:表格上方出现「复制 Markdown / 复制 CSV」两个小按钮(纯本地剪贴板,不发请求);**`"filter":"输入框id"`**:把表格和某个 input/select 绑定,读者输入即时过滤(`filterColumn` 可限定列)——数据多时**默认就该配一个**;**`"sortField":"下拉id"`** 用下拉的值(列名)排序;**`"details"` 与 rows 同序**,第 i 项是该行展开后的内容(可放任意组件,`null` = 该行不可展开)——首列出现 chevron,点开在整行下方展开明细,适合「主表 + 明细」;**`"wrap":true`** 开启长文本排版:单元格、长标识符和行内代码自动折行,保留显式换行;列有可读宽度下限,窄容器仍可横向滚动,嵌套表格独立选择模式(默认关闭,数据表保持单行紧凑;当某列是整句描述时开启) - keyvalue: `{"type":"keyvalue","pairs":[{"key":"...","value":"..."}]}` - timeline: `{"type":"timeline","items":[{"title":"...","desc":"...","time":"..."}]}` - file-tree: `{"type":"file-tree","items":[{"name":"...","type":"file|dir","children":[...]?}]}` — 目录行可点击折叠/展开(本地,零往返) - breadcrumb: `{"type":"breadcrumb","items":["","",""]}` - diff: `{"type":"diff","diffs":[{"path":"...","oldText":"..."|null,"newText":"..."}]}` - json: `{"type":"json","value":...}`(JSON 树查看器) - code: `{"type":"code","lang":"ts","code":"..."}` - callout: `{"type":"callout","tone":"info|success|warning|error","title":"...","content":"..."}` — 用于单条强调、提醒或风险摘要;`content` 是文本字符串,可用 JSON `"\n"` 分行。多条独立事项用 `list` 的 `{title,desc}` 保留逐项层次 - steps: `{"type":"steps","current":n,"steps":[{"title":"...","desc":"..."}]}` - arrow: `{"type":"arrow","dir":"down|right","length":16-96,"label":"..."}` — 图里两个块之间的**细线连接件**(1.4px 线宽 + V 形箭头,固定长度)。上下流程图一律用它,**不要用文字「↓」**:字符箭头跟着文字基线走,撑不开间距 - icon: `{"type":"icon","name":"Nodes","tone":"info|success|warning|danger|muted|default","size":16|20|24|32,"tile":true,"label":""}` — 内置图标集(749 个可忠实渲染;64 个内联秒开,其余首次使用时载入,完整名单见 ICON-NAMES.md)。`tile:true` 画成**淡色圆角方底**,用于阶段卡/语义标记;不带 `tile` 是跟随文字色的行内图标。`label` 可省略(纯装饰),成对出现时只给一个加 label - blocks: `{"type":"blocks","title":"...","rows":[{"label":"初始","cells":["0","1","2"],"color":"#64748b"}],"columns":n?,"caption":"..."}` — **色块图解**:按行分组的格子。三种形态同一个结构:① 阶梯图(每行格子数不同,各行自己等分宽度,如 8→4→2→1)② 等分块(一行 4 块)③ 泳道图(`columns` 固定列数,`cells` 里写 `"—"` 或留空)。`color` 省略时用强调色淡底;文字颜色按亮度自动反转(深底白字/浅底黑字)。**有「分成几份、逐轮推进、分组对照」的结构就用它,不要手写 svg 坐标。** `caption` 必写:一句话说明每格代表什么 - 行级 `"variant":"outline"`:细边框不填充,用于**矩阵**(每格都是一个框);`"variant":"plain"`:连框都不画,用于**位串**(`1 0 1 0 0 0 0 0` 读起来是数字不是表格)——只有带 `tone`/`color` 的格子会画出底色 - 格子可以写成对象:`{"text":"1","tone":"danger","sub":"SF"}` —— `tone`(accent/success/warning/danger/muted)给**单个格子**上语义色,`sub` 在格子下方挂一行小字(位名、序号、角色)。`color` 可显式指定十六进制,优先于 tone - 例:`{"columns":8,"rows":[{"variant":"outline","cells":[{"text":"1","tone":"danger","sub":"SF"},"0","1","1","0","0","1","0","0"]}],"caption":"最高位为 1,所以 SF=1。"}` 内置图标名:以下 **64 个内联在包里、立即渲染**,优先从这里面选: Check CheckCircle Info InfoCircle Warning Error X Plus Minus Circle ArrowDown ArrowUp ArrowRight ArrowLeft ArrowCurvedRight Function Functions Operator Atom Nodes Connect Share Branch Grid GridAlt Stack Cube Storage Chart Trending Pulse Speed Timer Clock TableFilled Code SquareCode Terminal File FileCode Folder Link Paperclip Lightbulb Book BookOpen GraduationCap Flask Lab Brain Robot Sparkles ShieldCheck Lock Globe WebsiteNetwork Search MagnifyingGlassSearch PluginPuzzle Settings Bolt Compass 其余 **685 个按需载入**(首次用到时拉取约 1 MB,此后本页全部可用)。完整名单在本 SKILL 同目录的 `ICON-NAMES.md`——需要猜名字时读它或 grep 它,不要在对话里穷举。 ### 图表 - chart: `{"type":"chart","kind":"bars|line|donut","data":[{"label":"...","value":n,"color":"#hex?"}],"series":[{"label":"...","data":[...]}]?,"horizontal":true?}` — bars 默认;line 趋势;donut 占比;**series:bars 是分组柱,line 是多序列折线;每个 `series[].data` 使用 `[{"label":"Q1","value":30},{"label":"Q2","value":45}]` 这样的对象数组,不是 `[30,45]` 这样的纯数字数组**;**`horizontal:true` 画横向柱**(排行/长标签首选);**`stacked:true` 把 series 堆叠**(构成/占比随时间);堆叠段够高时数值直接印在段内,鼠标悬停任意柱/段/点/扇区都会弹出即时 tooltip(堆叠显示该段数值 + 合计)。v3 渲染:宽度自适应、Y 轴 1/2/5 刻度、单序列负值在零线以下真实绘制、line 带面积渐变与抽稀 X 标签、donut 图例显示数值与百分比。**≤8 个点的快速对比用 chart;多序列、需要缩放/交互或数据量大时用 echart** - plot: `{"type":"plot","series":[{"expr":"a*sin(b*x)","label":"...","color":"#hex?","params":[{"name":"a","value":1,"min":0,"max":5,"animateTo":3,"durationMs":4000,"loop":true},{"name":"b","value":1,"min":0.5,"max":5}]}],"xMin":-6.28,"xMax":6.28,"title":"..."}` — SVG 函数图;**series 可带 `"kind":"line|area|scatter"`**(缺省 line;area 填色到基线;scatter 散点);**params 渲染成实时滑块**(拖动即时重绘,**y 轴锁定**=只变曲线不变数轴);**animateTo 参数会显示播放按钮**(自动动画演示);SVG 可拖拽平移、滚轮缩放;表达式支持 sin/cos/tan/asin/acos/atan/sqrt/cbrt/exp/log/ln/abs/floor/ceil/round/min/max/pow,常量 pi/e/tau,变量 x(其他字母=参数) - echart: `{"type":"echart","title":"...","height":300,"preset":"bar|line|area|pie|scatter","data":[{"label":"...","value":n}],"series":[...]?}` — **ECharts 全功能图表**,视觉效果远超 `chart`(渐变、tooltip、动画、图例交互);**preset 模式**:用和 `chart` 一样的 `data`/`series` 格式,自动构建主题化的 ECharts 配置(颜色跟随宿主主题);**preset 一览**(只写 preset + data/series/links,主题自动跟随): `bar` · `line` · `area` · `pie` · `scatter` · **`radar`**(每 series 一个多边形,指标取第一条 series 的 label)· **`gauge`**(每个 datum 一个仪表,适合单 KPI)· **`funnel`**(漏斗/转化)· **`treemap`**(体积/层级占比)· **`sankey`**(流向,用 `links:[{from,to,value}]`)· **`graph`**(关系图,`links` 驱动,节点大小随连接数)· **`heatmap`**(`series` 当行、第一条 series 的 label 当列)· **`bigline`**(长序列 + 内置缩放)· **`wordCloud`**(词云,`data:[{label,value}]` 的 value 表示权重;颜色跟随 `palette` 或主题调色板) **full option 模式**:传 `"option":{...}` 直接写 ECharts 原生配置(支持 ECharts 内置图表及已注册的 `wordCloud` 词云扩展;其他第三方扩展不保证可用),option 中的函数会被过滤(只接受数据)。选择原则:**chart 轻量(无额外下载)适合 ≤8 点的快速对比;echart 视觉更丰富(渐变、tooltip、图例交互、dataZoom),但会按需下载约 1MB 引擎,多序列/大屏/交互场景优先** ### 交互 **本地优先(v2.6)**:UI 自己能做的状态变化——判卷、判题、重置、展开、选中——一律本地即时完成,**零模型往返**。action 只用于必须模型参与的事(生成新内容、执行工具、下一步建议)。**交互组件必须带 action:不带 action 的按钮渲染为禁用态,用户点不了;带 action 的按钮点击后有「已触发」本地反馈。** - button: `{"type":"button","label":"...","tone":"primary|danger|success|ghost","full":true?,"small":true?,"icon":"emoji?","action":"refresh"?}` - **秘密禁令**:不得索取或生成密码、API Key、访问令牌、恢复码等秘密输入;遇到此类需求直接拒绝并解释 - input: `{"type":"input","label":"...","placeholder":"...","inputType":"text|email|color|number","value":"...","action":"name"?,"id":"field-id"?}` — `color` 使用浏览器原生取色器,值使用 `#RRGGBB`;`number` 可作为 Computed 数值输入;action 在失焦**和回车**时触发(回车带 `submit:true`);**blur 仅值有变化才发送**(聚焦又离开不产生空往返);payload 带 `id` 帮模型定位字段;带 `id` 的值刷新后保留、并被 submit 收集进 `fields` - select: `{"type":"select","label":"...","options":["...","..."],"selected":下标?,"action":"pick"?,"id":"field-id"?}` — `selected` 预选某选项(缺省显示「请选择…」占位,不静默预选第一项);带 `id` 的选择跨刷新保留并进 submit 的 `fields` - date-picker: `{"type":"date-picker","label":"...","mode":"single|range","value":"YYYY-MM-DD 或 YYYY-MM-DD/YYYY-MM-DD","min":"YYYY-MM-DD","max":"YYYY-MM-DD","id":"field-id"}` — 使用浏览器原生日期控件;日期必须是真实日历日期,范围模式的值是开始和结束日期组成的 ISO 区间;范围输入完整且有效后,带 `id` 的值进入共享状态与 submit 的 `fields` - segmented-control: `{"type":"segmented-control","label":"...","options":["...","..."],"value":"...","id":"field-id","action":"pick"}` — 胶囊式单选控件;带 `id` 时选中值进入共享状态,可用于 `when` 更新同级组件,也会进入 submit 的 `fields`;`action` 可选,设置后每次选择都会发送该 action - checkbox: `{"type":"checkbox","label":"","checked":true?,"action":"toggle"?,"group":"group-id"?}` — 默认保持逐次 `action` 行为;**加 `group` 进入多选聚合模式**:同组 checkbox 可反复勾选/取消,变化只在本地记录、不发逐次 action,兄弟 `submit` 一次性把该组已选 label 作为字符串数组放进 `answers`(例如 `{"styles":["",""]}`) - slider: `{"type":"slider","label":"...","min":0,"max":100,"step":1,"value":n?,"action":"name"?,"id":"field-id"?}` — 数值表单滑块:实时显示数值;带 `id` 跨刷新保留并进 submit 的 `fields`(拖拽经防抖合并成一次 action) - radio(**legacy,仅用于兼容老围栏**:试卷与决策都改用 `choices`。`choices` 的 `group` + `answer` + `explanation` + 兄弟 `submit` 已覆盖本地判分,且每个选项还能带代价与证据。新回答不要再用 radio): `{"type":"radio","label":"","options":["",""],"selected":n?,"action":"pick"?}` — 单选;**加 `"group":"group-id"` 进入聚合模式**:选择只本地记录、不发往返;可设置 `"answer":正确下标或标签` 和 `"explanation":""`,在纯 radio 且没有其他待发送表单状态时本地判卷 - choices: `{"type":"choices","title":"","prompt":"","mode":"single|multi","max":2,"submit":"