dsh-genui
Verified@poder/dsh-genui · v1.0.4 · MIT · Web UI
Generative interactive UI for DeepSeek Harness: model-declared, schema-validated React components rendered inside the chat, with user interactions bridged back into the same session.
Install
dsh plugin add @poder/dsh-genui Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Readme
dsh-genui
DeepSeek Harness 的生成式交互 UI 插件。
dsh-genui 让 Agent 不再只能输出纯文本、Markdown 或代码块,而是可以在当前聊天中按需生成真正可交互的界面:练习题、排序算法演示、对照表格、表单、图表、知识树、流程图、时间线、概念卡片、代码差异和参数模拟器。模型只负责声明结构化数据,插件负责校验、渲染,并把用户的真实操作作为一条新的用户消息送回到同一个 DSH 会话,由 Agent 批改、讲解或继续下一步。
已在本机 DeepSeek Harness 0.2.1-rc.3(desktop profile,Cordis 4.0.5,React 18 Web 客户端)上通过真实
dsh web实例验证。不是“模型能输出 HTML”这种假设,而是实际跑通了:模型调用工具 → 浏览器渲染 → 用户操作 → 结果回到当前对话。

🚀 安装(把下面的 Prompt 直接发给你的 Agent)
插件已发布到 npm:@poder/dsh-genui(注意是带 scope 的名字,不是 dsh-genui)。最省事的方式是让 DSH 的 agent 帮你装——复制下面这段发给它:
帮我安装一个 DeepSeek Harness 的生成式 UI 插件:npm 包 @poder/dsh-genui(注意是带 scope 的名字,不是 dsh-genui)。
安装步骤:
1. 用 DSH 的插件命令安装(它会转发给 pnpm 并把插件注册为 DSH bundle):
dsh plugin --profile <我的profile名> add @poder/dsh-genui
不确定 profile 名就先查有哪些 profile(或用默认 desktop);也可以进到 profile 目录里执行 pnpm add @poder/dsh-genui。
2. 确认安装成功:profile 的 package.json 里 dsh.profile.bundles 应出现 @poder/dsh-genui,node_modules 里有这个包。
3. 重启 DeepSeek Harness(宿主才会加载新的 render_ui 工具)。
4. 验证:新建会话,对我说"出三道关于排序算法的单选题,让我作答",应该在聊天里渲染出可交互的选择题卡片(不是纯文字)。也试试"用动画演示快速排序"或"画一张 AJAX 请求流程图"。
装完告诉我一声。
可选:开启「学习问答」专属模式
如果你想要「只有学习模式才出 UI + 导师提示词 + 无浏览器工具」的干净教学环境,再对 agent 说:
再帮我把这个插件调成"仅学习问答模式可用":
1. 编辑 profile 的 cordis.patch.yml,把插件的 genui 那行改成 config.registerTools: false(工具不再全局注册)。
2. 把包里 node_modules/@poder/dsh-genui/presets/learn.patch.yml 的 - insert: 整段合并进 cordis.patch.yml,得到"学习问答"预设(含导师提示词 + 在学习模式里注册 render_ui + 工具过滤 + 历史压缩)。
3. 给 learn-filter 那行加 deny 列表,屏蔽内置浏览器工具(dsh_browser_* 等)和我装的 MCP 工具名(可从会话日志 request/header.tools 抄),避免动画另开浏览器窗口。
4. 重启,新建会话时选"学习问答"。
手动安装、组件清单、安全模型、开发与测试等,见下文。
⚙️ 前置要求
- DeepSeek Harness 0.2.1-rc.3 或兼容的 0.2.x(桌面端)。版本差异可能导致兼容性警告。
- 插件宿主入口是 CJS(
require/import都能加载,Node ≥ 18 即可),因此对 DSH 自带运行时的 Node 版本不敏感。 - 用
dsh plugin add @poder/dsh-genui安装(它会把插件注册为 DSH bundle 并自动插入 loader 行)。
🔧 排错 / 故障恢复
| 现象 | 原因与处理 |
|---|---|
应用启动崩溃:@poder/dsh-genui: import failed |
插件模块加载失败。崩溃弹窗点「禁用第三方插件、备份 profile patch 并重启」先恢复进应用;然后检查:① node_modules/@poder/dsh-genui/lib/index.cjs 是否存在(安装是否完整);② profile 的 cordis.patch.yml 里 loader 行的 name 是否为 @poder/dsh-genui(不能是 dsh-genui);③ DSH 版本是否满足前置要求。若仍失败,把控制台里那行 import error 发我。 |
MODULE_NOT_FOUND / name mismatch (expected "dsh-genui") |
loader 行的 name 必须等于 npm 包名 @poder/dsh-genui。早期包发错了不带 scope 的名字,1.0.2 已修。若已装旧版,直接编辑 cordis.patch.yml 把那行 name: 'dsh-genui' 改成 name: '@poder/dsh-genui'(不要用 patch 层改名,DSH 会拒绝)。 |
更新失败:{"code":"already-installed"} |
node_modules 与 pnpm-lock.yaml 不同步(通常是之前手工加过 dsh-genui 别名/链接)。先删掉手工加的 node_modules/dsh-genui,再 pnpm install 对齐,然后 pnpm add @poder/[email protected](或 pnpm up @poder/dsh-genui)。若 pnpm install 仍报错,删掉 node_modules/@poder/dsh-genui 后重装。 |
装完模型调不到 render_ui |
重启 DSH;确认插件在插件列表、package.json 的 bundle 含 @poder/dsh-genui。 |
| 卡片不显示 | 刷新页面,确认浏览器加载了插件的 client.js。 |
| 学习模式还能开浏览器/联网 | 在 learn-filter 的 deny 里补上对应工具名(可从会话日志 request/header.tools 抄)。 |
| 想卸载 | 插件页禁用/删除 @poder/dsh-genui,或从 bundle 移除。 |
贴心提示:
learn-filter对暂时没注册的名字会重试,但无进展约 1.5 秒即停、最多约 4 秒,不会因为写了一堆本机不存在的工具名就拖慢每个学习会话启动。
为什么这不是“在聊天里塞 HTML”
模型不会输出 HTML、CSS、JavaScript、iframe 或外链脚本。它调用一个带严格 schema 的工具,传入一份 JSON 组件描述。宿主和浏览器各自独立校验,只有声明过的组件类型和字段能通过;浏览器为每种组件提供固定的 React 渲染器,所有模型文本都作为 React 文本节点渲染。没有 dangerouslySetInnerHTML、没有 eval、没有 Function、没有远程脚本,也没有组件发起的外部网络请求。
已实现组件
| 组件 | 用户能做什么 | 是否纯客户端 |
|---|---|---|
quiz |
单选/多选、多题一起提交、清空、未作答提示、跳过确认、刷新后恢复草稿 | 否,提交后由 Agent 批改 |
sorting |
冒泡/选择/插入/归并/快速排序的真实逐步轨迹;上一步/下一步/播放/暂停/重新开始/打乱;键盘操作 | 是 |
table |
表头吸顶、长文本换行、横向滚动、本地列排序、徽章/代码/布尔单元格 | 是 |
form |
文本、多行文本、下拉、单选、复选、多选组、数字、滑块;必填/正则/范围校验 | 否,提交后回到 Agent |
chart |
折线/柱状/散点纯 SVG 图表、图例开关、悬停读数、无障碍数据表 | 是 |
tree |
展开/折叠、徽章、说明、键盘可操作 | 是 |
flow |
蛇形铺满布局(线性链与分支/汇合图皆可)、连线/标签、逐步走查+焦点跟随、自动播放、节点详情 | 是 |
timeline |
垂直/水平时间线、状态标记 | 是 |
cards |
多概念并列对照、标签、要点列表 | 是 |
diff |
统一/并排差异、增删统计、复制、大 diff 折叠 | 是 |
simulator |
安全公式求值(不使用 eval)、滑块调参、实时结果、扫描曲线 |
可选提交 |
html |
沙箱内运行的自包含 HTML/CSS/JS 动画,适合 AJAX/TCP/DOM 等任意过程;无网络、无宿主访问 | 是 |
模型侧注册两个工具:
render_ui:一次渲染一个组件。render_ui_help:按组件类型返回精确 schema 和完整示例,内容由同一份注册表生成,不会和校验器漂移。
动效
排序演示不再在每一步重新挂载柱子。每个元素有稳定的 item id,轨迹被重放成 order 数组,FLIP 用 spring 采样出的 linear() 缓动(140–420ms,随 speedMs 调整)移动已有的 DOM 槽位;整轮使用固定的纵向比例尺,不会因为当前窗口的 min/max 变化而让所有柱子上下跳。进度条、240ms 的步骤说明过渡和克制的完成态补足了节奏。prefers-reduced-motion 会关闭 FLIP 和所有 CSS 过渡。
这是可在聊天里拖动、暂停、逐步走的实时动画,不是视频;不引入 Remotion/Chromium/MP4 运行时。完整动效模型见 docs/MOTION.md。

学习问答模式(可选 preset)
本包附带一个聚焦的 学习问答 Agent Preset:presets/learn.patch.yml。它不是第二个插件,而是一个 DSH 原生 preset:只组合一段完整的导师 persona(dsh-persona,complete: true)和历史压缩,不挂载任何编码或联网工具。模型可见工具只有 render_ui、render_ui_help,以及 DSH 运行时的只读 working_directory 上下文工具。
desktop profile 的主 genui 行配置为 registerTools: false;render_ui / render_ui_help 由学习 preset 里的 dsh-genui/tools 子插件注册。因此标准模式完全看不到这两个工具;历史卡片仍由全局浏览器渲染器重放,但标准会话不能再新建智能 UI。
id: learn
name: 学习问答
description: 以交互练习为主的学习导师,只保留教学提示词与 render_ui 组件。
导师提示词要求:先诊断需求,用最小例子讲清概念;适合自测时才生成 quiz/可视化;把 <genui_result> 当作权威提交记录;逐题批改并给一个下一步;不替用户写作业最终答案。它刻意移除了通用编码 Agent 身份、Shell/文件/浏览器/子 Agent/Todo/Skill/Plan/Workflow 工具,以及冗长的通用系统提示词。
preset 还挂了 dsh-genui/learn-filter,在 agent scope 内按可选的精确 deny 列表移除全局工具。这一步在真实 profile 里很关键:DSH 的 Playwright/Context7 MCP 客户端注册的是全局工具,不过滤就会漏进学习模式。desktop profile 已列出自己的 29 个 MCP 工具名;这个 filter 不会碰 preset 自己注册的 scoped render_ui / render_ui_help。
已在隔离 profile 和真实 desktop 组合副本中验证:会话日志 request/header.tools 只有这三个名字;唯一一条 system/message 就是约 1,135 字符的导师提示词。模型生成了二分查找边界单选题,用户提交 B 后,Agent 用最小例子解释了死循环,并逐项说明干扰项错在哪。

安装:把 presets/learn.patch.yml 追加到 profile 的 cordis.patch.yml,重启 DSH,在新建会话的模式选择器里选 学习问答(或在“设置 → 通用 → Agent 预设”中设为默认)。完整提示词策略、安装/卸载和限制见 docs/LEARNING-MODE.md。
已验证的 DSH 集成路径
插件是一个标准的 DSH profile 插件,分宿主端和浏览器端两半。
宿主端(lib/index.js)不直接 import 任何 @deepseek-ai/dsh-* 包,而是通过 Cordis 服务名拿能力:
ctx.tools.register(...)注册工具;ctx.sessionQuery.readSession()/ 当前 Session 快照校验“这个callId确实是本会话里真实发生过的render_ui调用”;ctx.sessionController.prompt(request, signal)追加持久化用户消息并让 Agent 继续;ctx.connection.admit(request)用 DSH 自己的浏览器 Cookie 鉴权自定义 POST 路由;ctx.webServer.register({ kind: 'exact', path: '/dsh-genui/submit', ... })挂载回传桥。
浏览器端(lib/client.js)由 package.json 的 dsh.client 声明,被 @deepseek-ai/dsh-client-modules 以 /plugins/dsh-genui/client.js 提供。它注册:
tool.call.toolview的render_ui键:工具过程区里的紧凑记录行;conversation.chat.node的genui键:完整的交互卡片,并设置groupTree: 'turn',让卡片位于可折叠的“思考/工具过程”之外,始终可见;
这个位置细节很关键:只注册 tool.call.toolview 时,DSH 会把工具过程折叠起来,卡片也跟着被隐藏。dsh-genui 把真正的卡片作为 turn 级 Chat node 渲染,工具行只保留一条紧凑记录。
数据流
sequenceDiagram
participant M as 模型
participant H as 宿主工具
participant L as Session 日志
participant C as 浏览器插件
participant U as 用户
participant R as 提交路由
M->>H: render_ui({ version, component })
H->>H: 严格校验(schema + 语义 + 大小)
H->>L: 持久化 tool/call
H-->>M: 简短确认
L-->>C: tool/call 重放
C->>C: 再次校验并渲染 React 卡片
C->>U: 可交互界面
U->>C: 选择 / 拖拽 / 输入
C->>R: POST /dsh-genui/submit(同源 Cookie)
R->>R: 鉴权 + 校验 call 属于本会话 + 语义校验 + 去重
R->>L: 用户消息,内含 <genui_result>{...}</genui_result>
L-->>M: 下一轮:批改 / 讲解 / 继续
排序播放、树展开、图表悬停、diff 视图切换、模拟器滑块等纯客户端操作不会调用模型,不消耗 token。只有显式提交才走桥接。
快速开始(本地开发)
要求:DSH 0.2.1-rc.3(或兼容的 0.2.x)、Node 24、profile 可用的 pnpm/npm。
cd D:\TempWorker\dsh-genui
npm install --ignore-scripts # 沙箱友好;esbuild 的平台二进制包已足够
npm run check # typecheck + 单元测试 + 文档生成 + 构建
安装到实际 profile(以 desktop 为例):
# 1. 先备份 profile 文件
Copy-Item "$env:USERPROFILE\.dsh\profiles\desktop\cordis.patch.yml" `
"$env:USERPROFILE\.dsh\profiles\desktop\cordis.patch.yml.bak-before-genui"
Copy-Item "$env:USERPROFILE\.dsh\profiles\desktop\package.json" `
"$env:USERPROFILE\.dsh\profiles\desktop\package.json.bak-before-genui"
# 2. 把本包加入 profile(在 profile 目录运行 pnpm,生成 node_modules/dsh-genui)
dsh plugin --profile desktop add "file:D:/TempWorker/dsh-genui"
# 3. 确认 package.json 的 dsh.profile.bundles 里出现 "dsh-genui"。
# 本包自带的 cordis.patch.yml 会插入 loader 行,通常不需要手动加。
# 如果你的 DSH 版本没有自动加入 bundle,再追加:
# - insert:
# - id: genui
# name: 'dsh-genui'
# 4. 重启 DSH(桌面端下次启动时生效)
本机执行 dsh plugin add 时会先打印一个既有插件 @michengai/[email protected] 的兼容性警告并返回退出码 1,但 dsh-genui 实际上已经安装成功;检查 package.json 和 node_modules/dsh-genui 即可。该警告与本插件无关。
打开 Web UI,新建会话,然后可以这样问:
- “出三道关于排序算法的单选题,用交互组件让我作答。”
- “用交互演示器展示冒泡排序或快速排序的完整过程。”
- “用表格对比四种排序算法。”
- “用 HTML 动画演示浏览器发出 AJAX 请求到数据库响应的全过程。”
- 在学习问答会话中直接要求出题、对比或逐步演示即可。
配置
插件没有必填配置。以下行为由组件描述本身控制:
submit.label/submit.hint/submit.instruction/submit.confirm控制提交按钮和确认;speedMs控制排序自动播放速度;reportProgress: true给排序演示增加“把进度告诉 AI”按钮。
插件本身通过 profile 行启用/禁用。禁用后会移除工具、路由、客户端 slot 注册、注入样式和本地监听;卸载与回滚见 docs/INSTALL.md。
安全模型(摘要)
- 只接受结构化组件;不支持任意 HTML/CSS/JS、iframe 或远程执行。
- 宿主严格校验:未知字段、类型错误、重复 id、非法选项引用、超大数据、非法组件类型都会被拒绝,并返回可修正的工具错误。
- 浏览器渲染前再次校验;未知类型只显示错误卡片,绝不渲染原始 HTML。
- 提交路由同源,并使用 DSH 自己的浏览器 Cookie 鉴权;检查 Host/Origin 信任边界和 Peer admission。
- 提交的
callId必须指向该 Session 中真实存在的render_ui调用;组件 id 和类型也要与记录参数一致。 - 请求体上限 128 KiB,并按记录的组件做语义校验(题目 id、选项 id、表单字段 id、数值范围、数量等)。
- 幂等:重试复用同一个
submissionId;宿主检查持久化rpcId和 in-flight 集合,重复点击不会产生第二条消息。 - 草稿状态存于
dsh-genui:v1:draft:<sessionId>:<callId>,提交状态存于dsh-genui:v1:submission:<sessionId>:<callId>,天然按会话和调用隔离。 - 组件不能访问文件系统、进程、Shell 或宿主 API,也不会发起网络请求。
- 发给模型的内容只有结构化的
<genui_result>和一小段人类可读摘要。
完整威胁模型与残余风险见 docs/SECURITY.md。
开发流程
src/
shared/
protocol/ 类型、限制、校验器、组件注册表、提交校验、消息格式
algorithms/ 排序轨迹、行差异、安全表达式求值
host/ 工具定义、提交路由、Session 校验
client/
components/ 每种组件一个渲染器 + 共享原语
node.tsx conversation.chat.node 定义与 keyed 渲染器
bridge.ts 同源 fetch 桥
drafts.ts 按会话/调用隔离的草稿状态
submission.tsx 提交生命周期 + 共享底部
index.tsx 插件入口:样式、slot
styles.css 作用域化、跟随宿主主题的 CSS
新增组件:
- 在
src/shared/protocol/components/<type>.ts增加 TypeScript 类型和 JSON Schema 分支。 - 在
src/shared/protocol/registry.ts注册定义;模型 schema、帮助、文档、示例会自动更新。 - 在
src/client/components/<Type>.tsx增加渲染器,并在src/client/renderers.tsx增加一个 switch 分支。 - 如果需要提交,增加数据 payload 和
validateSubmission分支。 - 在定义的
examples里放一个完整示例(tests/protocol.test.ts会校验),补充渲染器测试或浏览器场景,运行npm run docs生成文档。
不需要改卡片外壳、桥、宿主工具或提交路由。
测试
npm run test # 纯单元测试(排序、diff、表达式、协议、示例)
npm run typecheck
npm run build
npm run docs
真实验收证据(浏览器截图、Agent 原话、操作步骤)在 docs/TEST-REPORT.md。验收使用的隔离 home 位于 D:\TempWorker\dsh-test-home,它复制了凭据存储但有独立的会话和工作区,不会干扰正在运行的桌面会话。
文档
- 架构说明:扩展点、数据流、位置、重放与失败处理。
- 协议:信封、组件目录、提交格式、限制、错误码。
- 组件参考:自动生成的 schema 和示例。
- 安装、升级、卸载、回滚。
- 安全。
- 测试报告。
- English README。
已知限制
- 卡片是 turn 级 Chat node,不是 assistant Markdown 段落里的内联节点;它出现在 turn 边界(当前实测是在最终回答上方),不是某一段文字中间。
- 交互 UI 在模型调用工具后出现;刷新可恢复,但提交会产生新的用户 turn,而不是在同一 assistant step 内伪造 tool result。这是 DSH 提供的真实数据通路。
- 宿主路由需要
sessionQuery或当前 Session 快照来校验调用归属;没有历史读取能力的组合会明确返回“无法校验”,而不是接受提交。 - 没有复用
ask_user_question的阻塞语义:dsh-genui是“渲染后立即返回 + 用户消息桥”的模式,这样才能承载任意表单/排序/图表,而不被问题表单的窄词汇限制。 - PTC 模式只在
tool/call/tool/ptc-dispatch暴露同样render_ui参数时可用;native 模式是主要实测路径。 - 草稿/提交状态是浏览器本地状态;不会跨浏览器同步,跨标签页也不会实时同步,持久真相是那条用户消息。
- 模型仍可能生成内容错误的 UI(例如题目答案本身错)。插件是展示与传输层,不负责事实正确性。
- 组件不支持任意文件上传、外链、图片或 LaTeX;这是刻意的安全取舍。
许可证
MIT,见 LICENSE。