Skip to content

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

新增组件:

  1. 在 src/shared/protocol/components/<type>.ts 增加 TypeScript 类型和 JSON Schema 分支。
  2. 在 src/shared/protocol/registry.ts 注册定义;模型 schema、帮助、文档、示例会自动更新。
  3. 在 src/client/components/<Type>.tsx 增加渲染器,并在 src/client/renderers.tsx 增加一个 switch 分支。
  4. 如果需要提交,增加数据 payload 和 validateSubmission 分支。
  5. 在定义的 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,它复制了凭据存储但有独立的会话和工作区,不会干扰正在运行的桌面会话。

文档

已知限制

  • 卡片是 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。