dsh-visual-card
中文 | English
可视化回答插件:AI 交一段 HTML,在对话里渲染成一个可交互的窗口——能点、能用键盘操作、能全屏、能导出。
效果
工具调用后,本轮回答下方出现一个窗口:
- 顶部工具条:标题 + 全屏 / 复制 / 下载
- 内容区是 AI 写的 HTML,脚本会真正执行(按钮、canvas、动画都能跑)
- 颜色自动跟随当前亮/暗主题
能做什么
- 图表、色板、布局示意
- 可点击的演示(切换状态、翻页、展开细节)
- 完整的小游戏(WASD 移动、canvas 逐帧动画)
- 需要键盘操作时,点进窗口才接管键盘,窗口外按键照常进输入框
键盘规则
| 操作 | 行为 |
|---|---|
| 点进窗口 | 窗口接管键盘,标题栏提示「键盘已交给窗口」 |
| 按 Esc | 释放键盘,回到输入框 |
| 点窗口外 | 自动释放 |
| 鼠标 | 始终正常(悬停、滚动、点击都不用先聚焦) |
实现上用了 iframe:AI 生成的脚本通常监听 document 的键盘事件,如果和宿主共用同一个文档,窗口外按键也会触发它。iframe 有独立的事件作用域,焦点不在里面就收不到键。
工具
render_visual({
title: '标题',
html: '<div>...</div>',
// height 可选:不给就按内容自动定高(推荐)
})
高度是自动的:宽度固定后,内容多高卡片就多高,不套固定框。内容超过设置的比例上限时,改为卡片内部滚动 —— 再大的卡片也不会逼你滚动整个对话才能看全貌。
写 HTML 时用主题变量,不要写死颜色:
--v-bg-base、--v-bg-layer-1/2、--v-border、--v-text、--v-text-dim、--v-brand、--v-success/warn/error
设置
设置 → 可视化:
| 项 | 作用 |
|---|---|
| 高度上限 | 卡片高度按内容自动决定;超过这个比例(40–100% 视口高)就改成卡片内部滚动,不逼你滚动整个对话才能看全貌 |
| 允许执行脚本 | 关掉后按钮、动画、小游戏都不运行,只显示静态内容。脚本运行在宿主页面里,这是唯一的安全风险来源 |
| 每个会话保留轮数 | 超出的旧轮次在写入时丢弃(10–2000) |
| 每轮最多卡片数 | 一轮里超过这个数量只留最新的(1–50) |
| 清空历史 | 清掉 cards.json,带二次确认 |
改动即时保存到 ~/.dsh/visual/settings.json。
存储
卡片按「会话 + 轮次」落盘到 ~/.dsh/visual/cards.json,重启后仍在。保留轮数与每轮上限由设置决定。
删掉这个目录只会丢失历史可视化,不影响功能。
安装
推荐:内置插件安装程序。 打开「设置 → 内置插件」,粘贴包名,点安装,再重启应用:
dsh-visual-card
安装程序会自动写进 dsh.profile.bundles,不用手改任何文件。
命令行方式(先完全退出应用):
cd ~/.dsh/profiles/desktop
pnpm add dsh-visual-card
然后把包名加进 dsh.profile.bundles,重启应用。
安全说明
AI 生成的脚本运行在宿主页面里,不是沙箱。这是「能点击、能跑小游戏」的必然代价。
插件只约束「不引用外部资源」,不做代码审查。自己用没问题;如果要在不信任 AI 输出的场景里用,请先自行加沙箱。
已知限制
- 卡片固定渲染在本轮回答下方,不能插入正文中间——宿主的对话节点是固定 19 种,插件无法新增节点类型
turnTail槽位只在本轮结束后渲染,所以流式输出期间看不到卡片
English | 中文
Visual answers for DeepSeek Harness: the model hands over a block of HTML and it renders as an interactive window inside the conversation — clickable, keyboard-operable, fullscreen-capable, exportable.
What it looks like
After the tool call, a window appears below the turn's answer:
- Toolbar: title plus Fullscreen / Copy / Download
- The content area is the model's HTML, and its scripts really run (buttons, canvas, animation all work)
- Colors follow the active light/dark theme automatically
What you can build
- Charts, color swatches, layout sketches
- Clickable demos (state toggles, paging, expanding detail)
- Complete small games (WASD movement, per-frame canvas animation)
- When a demo needs the keyboard, clicking into the window takes it over; keys outside keep going to the composer
Keyboard rules
| Action | Behavior |
|---|---|
| Click into the window | The window takes the keyboard; the title bar says so |
| Press Esc | Release the keyboard, back to the composer |
| Click outside | Released automatically |
| Mouse | Always works — hover, scroll and click need no focus first |
The implementation uses an iframe. Scripts generated by the model typically listen for keydown on document; if they shared the host document, keys pressed anywhere would reach them. An iframe has its own event scope, so it only receives keys while focus is inside.
Tool
render_visual({
title: 'Title',
html: '<div>...</div>',
// height is optional; omit it and the card sizes itself to its content (recommended)
})
Height is automatic: once the width is fixed, the card is as tall as its content — no fixed frame. When the content exceeds the configured cap, the card scrolls internally, so even a large card never forces you to scroll the whole conversation.
Use theme variables instead of hard-coded colors:
--v-bg-base, --v-bg-layer-1/2, --v-border, --v-text, --v-text-dim, --v-brand, --v-success/warn/error
Settings
Settings → Visual:
| Item | Effect |
|---|---|
| Height cap | Card height follows its content; above this share of the viewport (40–100%) the card scrolls internally instead of forcing a page scroll |
| Allow scripts | When off, buttons, animation and games do not run — only static content renders. Scripts run inside the host page, which is the only source of risk here |
| Turns kept per conversation | Older turns are dropped on write (10–2000) |
| Max cards per turn | Beyond this many in one turn only the newest are kept (1–50) |
| Clear history | Deletes cards.json, behind a second confirmation |
Changes save immediately to ~/.dsh/visual/settings.json.
Storage
Cards are persisted per conversation and turn to ~/.dsh/visual/cards.json, so they survive a restart. Retention is governed by the settings above.
Deleting the directory only loses visual history; functionality is unaffected.
Install
The built-in plugin installer is the recommended way. Open Settings → Built-in Plugins, paste the package name, press install, then restart the app:
dsh-visual-card
The installer writes the entry into dsh.profile.bundles for you; no manual file edits.
From the command line (quit the app completely first):
cd ~/.dsh/profiles/desktop
pnpm add dsh-visual-card
Add the package name to dsh.profile.bundles and restart the app.
Security
Scripts generated by the model run inside the host page — this is not a sandbox. That is the unavoidable cost of "clickable, can run games".
The plugin only enforces "no external resources" and performs no code review. Fine for personal use; if you point it at model output you do not trust, add a sandbox first.
Known limits
- Cards always render below the turn's answer and cannot be placed mid-text: the host supports a fixed set of 19 conversation node kinds, and a plugin cannot add one
- The
turnTailslot only renders after the turn closes, so nothing appears while the answer is still streaming
No comments yet. Be the first to write one.