这是什么
@changfenhuang/dsh-genui 是 DeepSeek Harness 的一个插件,版本 0.10.0,以 MIT 许可发布在公共 npm 上。它的仓库地址是 github.com/omdsh-dev/dsh-genui,而包名用的是另一个 scope——当你把配置里的条目和包名对照时,这一点值得留意。package.json 声明了 dsh、deepseek-harness、generative-ui、plugin、ui 五个关键词,并且直白地写明了用途:通过 dsh-ui 围栏在助手回复中内联渲染交互式 UI 组件;同时附带教授围栏用法的主机插件、浏览器端渲染器(也就是客户端部分)和一个内置的 genui 技能。
安装会带来三部分东西。主机部分是 lib/index.js,其不变量在 lib/invariant.js;浏览器部分是 lib/client.js;按需加载的引擎是 lib/assets/mermaid.js、lib/assets/three.js、lib/assets/echarts-core.js 和 lib/assets/echarts-full.js。发布包里还包含 SKILL.md(组件与字段目录)、cordis.patch.yml、两份 README、变更日志和演示提示词。dsh 字段声明了一个 bundle 补丁,以及一个 platform 为 web、inject 为空的客户端部分。
机制究竟是怎样运作的
模型把界面描述写成 JSON,放进一个标记为 dsh-ui 的围栏代码块里。围栏正文就是一份 JSON 文档——README 给出的例子是一个带 title 和 items 数组的对象,数组中每一项带一个 type,例如 stat、table、chart、progress、echart、plot 或 quiz。浏览器端渲染器认领这门语言,把它画出来,而不是显示成一个代码块。同一份规范也可以通过 render_ui 工具送达,那样会作为一张卡片渲染在工具行里;README 自己划的界线是:交付物式的 UI 走工具通道,回答式的 UI 走围栏通道。
渲染有两条通道,在主机激活浏览器模块后自动选择。在较新的、暴露 fence-registry 扩展点的 harness 构建上,围栏通过主机自身的流式渲染管线注册;在没有该扩展点的构建上(包括官方支持的默认构建),插件改为观察会话 DOM 并挂载自己的渲染树,它会匹配若干种代码块表面,并且作为结构性兜底,匹配任何横幅标注为 dsh-ui 且内部含有 <pre> 的元素。README 声称两条通道下组件、交互、面板和持久化的行为完全一致,并且 DOM 通道同样支持流式渲染,因此第一个写好的组件在整条回复完成之前就会显示出来。
交互回传给模型的方式是动作事件,而不是工具调用,也不是你手动发的一条聊天消息。按钮、开关、输入框、下拉框、复选框、单选按钮、多行文本框和测验都带 action 字段;点击或失焦会把该动作发回去,模型随后用新的围栏更新界面。README 把这称为事件回路,并说明同名动作会以 300 毫秒的尾部延迟去抖,因此连点会合并为一次、以最后一个值为准。README 对边界说得很清楚:界面自己就能完成的状态变化——判分、测验校验、重置、展开折叠、选中——都在本地即时发生,不产生任何模型往返;动作只保留给真正需要模型的事情。
内置的组件
README 声称有 30 多个组件:卡片、表格、图表、表单、标签页、手风琴、文件树、时间线和 diff 等等。它按使用场景给了一张能力表——数据类(stat、table、chart、progress)、媒体类(可从浏览器访问的 http(s) 或同源相对地址的内联音视频,带封面图、宽高比和可见的失败状态)、探索类(plot 曲线,拖动参数滑块在本地重绘)、反馈类(quiz 在本地判分并给出解释)、工作区类(/panel 或 panel: true,输入框上方的常驻可缩放面板,模型就地更新它,也可以用 append: true 增量合并)。此外还有专门的 echart 节点用于完整 ECharts 图表,以及带自动修复重试的 mermaid 集成——修复失败才降级为显示源码。所有规格在校验前会先经过一个守卫,确定性地归一化原生字段别名。
安全性:来源声称了什么,又没有说什么
这是读者一定会问的问题,所以这里给出源码的答案以及答案的边界。README 声称组件是白名单的——模型无法夹带 HTML 或脚本,并称函数表达式经过一个独立解析器处理,绝不使用 eval。它还写明了机密信息禁令:GenUI 绝不应索取密码、API key、访问令牌或恢复码;即使出现密码输入框,它也会保持掩码、不被持久化、也不进入表单收集。
在这几句话之外,源码没有描述任何沙箱、CSP、iframe 或源隔离、网络白名单,也没有任何安全审计或威胁模型。白名单那句话是对组件集合的声明,不是已发布的保证,也没有引用任何第三方审查。还有一个 README 只是暗示而没有论证的后果:媒体组件会从「浏览器可达」的地址拉取内容,因此渲染面并不会让页面变成网络惰性的;同时各个引擎是从插件自己注册的 HTTP 路由加载的。至于动作载荷、表单字段或持久化状态是否会离开浏览器,源码没有说明——诚实的读法是:它的安全属性建立在白名单、解析器和文档之上,你应当像不信任模型输出的一切那样,去不信任模型生成的界面。包自身的元数据把安全性标为 unverified。
状态、持久化与重新加载
README 说答案、提交锁定和输入值按「会话 + 内容指纹」保存:刷新或重新打开会恢复,重新渲染完全相同的内容会保留你的状态,新内容则从空白开始,并且有 200 个块的 LRU 上限。测验在判分后锁定,「重做」在本地重置。会话面板是一块常驻浮坞,在模型持续更新它的过程中原地留存。
有两点需要和上面区分开。这里的持久化指的是你已经拥有的那个组件的界面状态,而不是一件被保存下来的产物:源码从未说过组件可以从回复中导出、当作模板复用、存成文件或分享,也从未描述一个可以浏览的组件库。而跨会话持久化是被刻意不做的——路线图明确否掉了「回放时恢复标签页和开关」,理由是回放即重置才是更正确的默认行为,因为模型已经用新的围栏更新过界面了。
环境要求
包的 engines 字段要求 Node ^22.19.0 || >=24.0.0,以及 pnpm >=11.7.0 <12;packageManager 字段锁定 pnpm@11.7.0。pnpm 必须在 PATH 上,因为 harness 的插件命令依赖它;README 建议用 corepack enable 或全局安装,然后新开一个终端。harness 下限是 ^0.1.2-rc.1 || ^0.1.5-alpha.1——这条以 peer dependency 的形式写在各个 @deepseek-ai/dsh-* 和 @deepseek-ai/cordis 包上,并在 README 中重复了一遍:0.10.0 被声明为已在 DSH 0.1.5-rc.2 和 0.1.2-rc.1 这个最低版本上验证,而仍在 DSH <=0.1.1-rc.x 上的用户被建议使用 dsh-genui 0.9.8。还需要一个 Web 前端,因为主机负责客户端激活,并且必须提供 slots 和 sessions 两个服务。React 是直接依赖(^18.3.1)也是 peer 依赖(^18.0.0 || ^19.0.0)。
安装方式是在公共 npm 上向 web profile 添加该包,随后重启 Web 端并硬刷新浏览器。README 特别强调:下载到 client.js、或者加载器缓存里有条目,只能证明字节到位了;激活成功一定会打印 [genui] client active; fence-channel=registry|dom。如果看不到这一行,应当先去修包或 profile 的身份信息,而不是去排查 DOM 属性。它还警告不要在刚克隆下来的目录上使用 link:,因为 link: 不会安装渲染器自身的依赖。
已知限制与版本注意事项
守卫的上限有明确文档:一棵组件树最多 200 个节点、8 层嵌套,坏节点被静默丢弃、数字被截断到区间内、字符串被截短,因此病态的规范也绝不会让界面崩溃。会话面板的上限是 200 个节点 / 200 次追加,超过之后模型应当发送 replace 重建。路线图明确列出了项目决定不做的事:增量补丁(一段围栏只花 200–800 token,重发的代价几乎为零)、跨会话状态持久化、MCP 适配器、独立的组件画廊页面,以及国际化——内置字符串只有 6 条。chart 被有意保持为紧凑版,只支持 kind: 'bars' | 'line' | 'donut' 且数值必须有限,render_ui 会直接拒绝非法数据而不是悄悄回退。引擎不再内联进 client.js,而是首次使用时从插件自己的路由按需加载;没有这些资源路由的主机会降级为显示源码或加载失败提示。在缺少注册表扩展点的主机上,围栏发现依赖匹配主机渲染出的 DOM,一旦该 DOM 漂移就会打印一次性的控制台警告。源码没有说明操作系统要求、浏览器支持矩阵、遥测行为,也没有给出任何 star 数或下载量。
什么时候该用,什么时候不该用
当答案「展示」比「描述」更好时,就该用它:一张可排序的表格、一个小仪表盘、一张可调参数的曲线、一张图表、一幅示意图、一道小测验,或者一块随着对话被模型持续刷新的面板。它最有力的论据是「本地优先」的设计——判分、重置、展开折叠都不花往返,只有真正需要问模型的时刻才发出动作。如果你本来就接受模型输出进入你的会话,GenUI 能把其中一部分输出变成可以就地操作的东西,而不只是读过去。
但不要把它当成通用应用框架。它渲染的是一份白名单规范,不是任意 HTML、CSS 或 JavaScript——你自己写的自定义控件不会在围栏里跑起来,README 也没有给出让你新增组件类型的插件 API。如果你需要界面被归档或复用,它同样不合适:没有导出、没有画廊,也没有交互状态的跨会话回放。如果你的 harness 低于声明的最低版本,或者机器上没有 pnpm,插件根本不会激活——而这种失败表现为一段普通的代码块,而不是一个报错弹窗。最后,如果你的 harness 客户端包较旧,就要预期按需加载的引擎会降级为源码提示,直到你更新它。
FAQ
回复只渲染成一个普通代码块,我该先查什么?
打开浏览器控制台,找 [genui] client active; fence-channel=registry|dom 这一行。如果没有,说明客户端包根本没有激活,即使它的 URL 返回 200 也一样——README 说这时要对齐 profile 依赖、package.json.name、cordis.patch.yml、加载器 id 和配置的 bundle 名。如果有这一行,就去检查围栏的标签和正文;缺少注册表扩展点的主机会自动退回 DOM 通道。
模型生成的界面能否执行脚本或渲染任意 HTML?
README 说不能:组件是白名单的,函数表达式走独立解析器而不是 eval。但要准确理解这句话的范围——源码没有提到沙箱、CSP、源隔离、网络白名单或任何审计,而且媒体组件确实会从浏览器可达的地址拉取内容。请把它当作一份有文档的白名单,而不是安全保证。
组件在刷新后还在吗?我能保存或复用它吗?
状态会留存:答案、提交锁定和输入值以「会话 + 内容指纹」为键保存,LRU 上限 200 个块,因此刷新可以恢复。但「保存」是另一回事——README 没有描述导出、组件画廊、收藏库或分享途径,并且明确拒绝了跨会话状态持久化。
支持哪些 harness 和 Node 版本?
按 engines,需要 Node ^22.19.0 || >=24.0.0,以及 pnpm >=11.7.0 <12,并且 pnpm 要在 PATH 上。harness 下限为 ^0.1.2-rc.1 || ^0.1.5-alpha.1;0.10.0 被声明为已在 DSH 0.1.5-rc.2 和 0.1.2-rc.1 最低版本上验证,DSH <=0.1.1-rc.x 应继续使用 dsh-genui 0.9.8。
我需要用特殊提示词才能让模型输出围栏吗?
通常不需要,但有时需要。README 说新会话在重启后会自动获得内置的 genui 技能,并且在带公共技能注册表的主机上插件会自动注册它。如果模型仍然只回文字,文档给出的兜底办法是直接说「用 dsh-ui 输出」——把请求表述为一块 dsh-ui 仪表盘或面板,也正是官方演示的做法。
