Community编程与开发github.com

AlbertZJ/dsh-genui-stateful

通过 dsh-ui 围栏在回答中直接渲染结构化的交互式 UI 组件。适用于在结构化展示比纯文字更清晰时,用于关键点、强调、对比、流程、步骤、状态、数据、演示和交互场景。所有用户可见文本保持对话语言。

dsh-genui-stateful 是什么?

该技能允许 AI 助手在对话响应中直接内嵌并渲染交互式 UI 组件,而无需依赖外部工具调用。它定义了一个标准的 'dsh-ui' 围栏标记,AI 在其中以 JSON 格式描述组件,渲染器会将其转换为真实的布局、展示、图表、交互及高级组件(如 SVG、Mermaid 图、3D 场景等)。该技能支持 182+ 组件类型,包括布局容器(卡片、网格、轮播)、数据展示(表格、图表、差异对比)、交互控件(按钮、输入框、选择器)以及教育组件(测验)。所有用户可见文本会自适应对话语言,数学公式支持 KaTeX 语法,图表引擎支持 ECharts 全功能配置。组件原生支持本地状态管理(如表格排序、表单校验、判卷),仅在有模型参与需求(如生成新内容、执行工具调用)时才发送 'action' 请求,从而最小化不必要的模型往返。该技能适用于 Claude Code、Coder 等支持 dsh-ui 渲染平台的 AI 编程助手,能显著提升复杂技术信息的可视化表达与交互体验。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/AlbertZJ/dsh-genui-stateful/tree/main

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

GenUI — 生成式 UI 输出规范

语言: 匹配用户请求的语言(否则匹配消息语言),包括周围散文和所有用户可见的 UI 文本:标题、标签、内容、选项和解释。以下中文示例仅说明模式;加载此技能后,不要将对话切换到中文。保持 JSON 键、组件类型、ID 和操作不变。英文请求得到英文散文和 UI 文本;中文请求得到中文散文和 UI 文本。<user-language ...> 标记仅表示元占位符。将其全部替换为对话语言中的实际内容;切勿逐字发出这些占位符。

你可以在回答正文中间输出可交互 UI 组件:写一个 dsh-ui 围栏(带有语言标签 dsh-ui 的围栏块),内含 JSON 规格,渲染器会把这一整块画成真实组件,文字照常穿插在前后。组件就是回答的一部分,不是工具调用。

{"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 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 tabs accordion copy link submit 高级:svg mermaid diagram scene3d quiz

布局

  • 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":[...]}
  • 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 不内嵌图片字节,图片仍依赖原地址。完整用法与验收。
  • 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,点开在整行下方展开明细,适合「主表 + 明细」
  • 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
  • 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: {"type":"radio","label":"<用户语言标签>","options":["<用户语言选项>","<用户语言选项>"],"selected":n?,"action":"pick"?} — 单选;加 "group":"group-id" 进入聚合模式:选择只本地记录、不发往返;可设置 "answer":正确下标或标签 和 "explanation":"<用户语言解释>",在纯 radio 且没有其他待发送表单状态时本地判卷
  • link: {"type":"link","label":"...","href":"https://..."?} — 仅 http(s)/mailto 协议被接受;无 href 时渲染为纯文本样式(不会假装可点)
  • submit: {"type":"submit","label":"<用户语言操作>","action":"grade","groups":["q1","styles"],"resetAction":"redo"?} — 聚合按钮:groups 引用当前 block 中的 submission member key,来源为 radio.group、checkbox.group、input.id、textarea.id、select.id、slider.id;radio 需已选择,checkbox 组需至少选择一项,普通 field 的值需在 trim 后非空。groups 控制提交所需成员与完成进度;未设置时,至少有一个已完成成员即可提交。纯 radio 范围含 answer 且本次没有范围外 payload 时可本地判卷;其余场景一次发送 [genui-action],payload 继续收集当前 block 中已填写的表单状态,字段...

相关技能