- Inicio
- Plugins
- Mejoras de UI
- dsh-plugin-middle-panel
dsh-plugin-middle-panel
darkhe55/dsh-plugin-middle-panel
Middle canvas panel for DeepSeek Harness: shows deliverables between the workspace list and the conversation, and sends numbered annotations back to the chat.
Instalar
dsh plugin --profile web add github:darkhe55/dsh-plugin-middle-panelREADME
画布面板 / Canvas panel
在 DeepSeek Harness Web GUI 里把中栏变成 [画布 | 对话] 两栏:左边工作区列表不动,中间是交付物画布,右边是原生对话。画布支持文本划选、HTML 元素选取、图片区域框选与 SVG 图层选取,标注按编号回传到输入框。
┌──────────┬──────────────────────────────────────┬──────────────────┐
│ 工作区 │ 画布 │ 对话 │
│ │ ┌────────────────────────────────┐ │ │
│ ▸ 目录 │ │ 编辑 渲染 | 原文 A− A+ 🔍 │ │ 用户 / 模型 │
│ ▸ 文件 │ ├────────────────────────────────┤ │ 消息 │
│ │ │ 交付物(文本 / 网页 / 图 / 3D)│ │ │
│ │ ├────────────────────────────────┤ │ ┌────────────┐ │
│ │ │ 3. notes.md · 第 12-14 行 ✓ ↗ × │ │ │ 输入框 │ │
│ │ │ > 这段话需要改写 │ │ └────────────┘ │
│ │ └────────────────────────────────┘ │ │
└──────────┴──────────────────────────────────────┴──────────────────┘
包名 dsh-plugin-middle-panel,以 bundle 形式装在当前 profile(desktop)里。
发布流程
分支推送只跑测试,不动 registry;发布只由打标签触发。
# 1) 改版本 + 写 CHANGELOG,提交并推送
git commit -am "release: 0.5.6" && git push
# 2) 打标签并推送,这一步才会发布
git tag v0.5.6 && git push origin v0.5.6
.github/workflows/publish.yml 会依次:校验标签与 package.json 一致 → 若该版本已存在则跳过 → 跑全部测试 → 校验 client.js 与 src/parts 同源 → npm publish --provenance。
两种凭据方式,二选一:
| 方式 | 配置 | 是否需要 token |
|---|---|---|
| 可信发布(推荐) | npm 包页面 → Trusted Publisher 绑定 Darkhe55/dsh-plugin-middle-panel + publish.yml | 不需要,走 OIDC |
| 仓库 secret | Settings → Secrets → NPM_TOKEN(Granular,不要开 bypass 2FA) | 需要 |
手动触发也可以在 Actions → Release → Run workflow 里选 stage(只暂存,需人工 2FA 审批)或 publish。
本地等价命令:npm run guard -- --tag v0.5.6(只检查是否该发)、npm run release:stage、npm run release:approve -- <stage-id>、npm run release(直接发布)。
安装
已发布到 npm:dsh-plugin-middle-panel(以 npm view dsh-plugin-middle-panel version 为准)。
- 按包名安装(官方 npm 源,或任意镜像源,二者等价):
安装后重启 DSH;首次打开若界面无变化,dsh plugin --profile desktop add dsh-plugin-middle-panel # 镜像源 dsh plugin --profile desktop add dsh-plugin-middle-panel --registry https://registry.npmmirror.comCtrl+Shift+R硬刷新一次。 - 从市场(Discover)安装:在 Plugins → Discover 添加索引源
https://raw.githubusercontent.com/Darkhe55/dsh-plugin-middle-panel/main/market-index.json, 搜索「画布面板」。该索引条目指向 npm 源,安装参数为dsh-plugin-middle-panel@0.4.0。 - 从 Git 直接安装(不依赖 npm):
plugin_manager install_bundle target: git+https://github.com/Darkhe55/dsh-plugin-middle-panel.git#<commit> - 本地开发:
install_bundle指向本目录。
兼容 engines.dsh: >=0.2.2-rc.1 <0.3.0,无运行时依赖、无 postinstall。已在 0.2.2-rc.3 上实测:宿主路由、main slot 占用、canvas 工具注册与保存接口冒烟均通过。
用法
- 会话头部右侧的 画布 按钮,或让模型调用
canvas工具(模型调用会自动展开面板):{ "path": "demo/hero.html", "view": "auto", "note": "配色按设计稿改了" } - 面板打开交付物后:
- 文本:划选 → 浮出的「标记第 N 行」;
- HTML:悬停高亮、点一下选中元素(选择器 + 文本);
- 图片:先点「框选区域」再拖矩形,可复制或保存为
<文件名>.crop.png;Shift悬停取色,点「标尺」量两点像素距离;SVG 还会列出可点选的图层; - 三维:点三角面生成标注,标注自带顶点、法线、面积与方位;「测距 / 测角」量尺寸。
- 每条标注可写一句意见,右上角 发送标注 (N) 会把下面这种文本填进输入框,用户补一句话即可发送:
以下是我在画布面板中的标注(共 2 条),请按编号逐条处理:
### `demo/notes.md`
1. `demo/notes.md` — 第 5-6 行
> - 第一条:这句话写得太平。
意见:这里改成更具体的说法
### `demo/sample.dae`
2. `demo/sample.dae` — 三角面 #0(共 2 面)
> 0.888, 0.601, 0.000
> 顶点 (0.000, 0.000, 0.000) (1.000, 0.000, 0.000) (1.000, 1.000, 0.000)
> 法线 (0.000, 0.000, 1.000) · 面积 0.500 · 位于模型 中·中·前 · 该模型共 2 个三角面
意见:这个面要加厚
处理完的条目标为 ✓:编号与意见都留着,但不再参与下一次发送,方便对齐「哪些还没改」。
实现要点
- 面板注册为全局
mainkeycanvas(ctx.layout.selectPanel('canvas'))。 - 右半是 原生对话的嵌入式出现:
props.renderFactorySlot('conversation.content', {})。框架没有第四列,root槽明确禁止注册,这是唯一不 fork 框架的中间布局。 - 会话身份与输入框 API 由一个零高度桥接组件从
conversation.composer.dock拿:sessionId+inputActions(captureInsertion/insertText,失败回退setDraft,再回退剪贴板)。 - 文件读取走
remote.workspaceFiles,注意客户端 Remote 返回的是{ ok, value }信封,必须解包;remote.workspaceFiles也必须写进客户端inject。 - HTML 预览:读文本 → 注入 picker 脚本 → Blob iframe(
sandbox="allow-scripts",不给 same-origin),选取结果经postMessage回传。 - 标注只存客户端(
localStorage,按 session 分键);发送后才进入 session log,符合“log 是唯一真相”。
检视能力
- 定格聚光:
Alt+S暂停后画面定格,此时鼠标在同一元素上停 0.5 秒,其余元素淡到 45%,被悬停元素及其子树保持清晰;移开或换元素立即恢复,过渡180ms cubic-bezier(.33,0,.2,1)。播放状态下不做任何淡化,页面按自己的交互跑。 - 动画冻结 + 时间轴:
暂停 / 继续(Alt+S,单手)、时间轴滑轨、±1 帧(16ms,Shift 100ms)。HTML 走document.getAnimations()(CSS 动画 / 过渡 / WAAPI)+ SVG SMIL,并接管requestAnimationFrame让 JS 帧循环也能停住、+可逐帧推进。 - 快捷键归属:只有鼠标悬停在面板上、或焦点在面板内时才响应;在会话输入框、任意
input/textarea/select/contenteditable或输入法组合期间一律不拦截。 - SVG 内联渲染:不再用
<img>,因此可悬停聚光、点元素生成图层标注、暂停/拖动 SMIL 时间线。插入前剥掉<script>、on*事件与javascript:链接。 - 标注 ↔ 视图联动:鼠标停在标注栏的编号或意见框上,视图里对应的目标立即高亮、其余内容淡到 40%;移出即恢复。覆盖文本行、HTML 元素、SVG 图层、图片区域、docx 段落、xlsx/csv 单元格、pptx 页、DXF 图元、三维三角面、PSD 像素与 sketch 结构行。
已知边界:JS 帧无法回退(requestAnimationFrame 没有历史);页面若在启动时缓存了原生 requestAnimationFrame 引用则不吃接管;时间轴范围取运行中动画的最长有限 endTime,无限循环动画用随播放时间增长的窗口。
文件格式支持
| 类型 | 格式 | 渲染方式 |
|---|---|---|
| 网页 | .html .htm | 沙箱 iframe(注入元素选取与动效控制) |
| 文档 | .md .txt | Markdown 渲染 / 纯文本,可编辑保存 |
| 图片 | .png .jpg .webp .gif .bmp .ico .avif | 位图缩放 + 框选区域标注 |
| 矢量 | .svg | 沙箱 iframe 内渲染(sandbox="allow-scripts",不透明来源),可悬停聚光、点选图层、暂停 SMIL |
.pdf | 运行时内置阅读器(不可用时提示路径与大小) | |
| Office | .docx .xlsx .pptx | 解 OOXML:段落 / 表格(≤3 表 × 300 行)/ 分页文本 |
| 表格 | .csv .tsv | 自解析渲染表格(支持引号内逗号换行) |
| 三维 | .stl .obj .ply .off .3mf .dae .glb .gltf | WebGL 三角面渲染:Blinn-Phong 双光源 + 雾、面/边可独立开关(边可切消隐或透视)、坐标轴、包围盒统计;左键旋转 / 右键平移 / 滚轮缩放 / F 复位取景;标注带面片信息,重新导出后按重心自动找回 |
| 视频 | .mp4 .m4v .webm .mov .ogv .ogg | 时间区间标注:拖进度条选段生成 00:12.4–00:18.7,暂停后可框选画面区域;播放/暂停、逐帧(← →)、倍速、点标注时间码跳转 |
| 图纸 | .dxf | 解析 LINE/CIRCLE/ARC/LWPOLYLINE/TEXT/POINT 渲染 SVG,可缩放 |
| 其他 | .doc .xls .ppt .step .iges .dwg .fbx 等 | 明确提示需转换的格式(不显示二进制乱码) |
| 视频(不可播) | .mkv .avi .wmv .flv .mpg .mpeg .3gp .ts | 明确提示「浏览器无法直接播放 .mkv,可播 mp4/webm/mov/m4v/ogv,请先转码」 |
视频标注
视频在画布面板里可以直接看、直接标。
| 操作 | 结果 |
|---|---|
| 点进度条 | 跳转到该时刻 |
| 输入后点「跳到 xx」 | 输入与当前位置不同时右侧出现,点击直接定位 |
| 在时间框输入后回车 | 跳转,支持 12.4 / 01:23.4 / 01:02:03.5;格式不对或超出片长会提示 |
| 未播放时悬停进度条 | 画面预览该帧,移出后回到原位置 |
| 拖动选段 | 画面跟随指针,浮出「起–止 · 时长」提示,便于确认范围 |
Alt + 滚轮 | 时间轴缩放,以指针为锚点;最深 1 秒窗口并显示帧级刻度,反向滚回全部 |
Shift + 滚轮 | 放大后左右平移时间轴窗口 |
| 右键时间轴 | 只弹出菜单,不移动播放头(左键才会定位);菜单含插入入点 / 插入出点 / 清除入出点(也可用 I、O) |
| 标记按钮 | 随入出点变化:标记此刻 / 标记入点 / 标记出点 / 标记区间 |
| 单击进度条 | 只跳转;移开鼠标不会回退到预览前的位置 |
| 拖动进度条 | 选中一段时间 → 生成一条区间标注 00:12.4–00:18.7 |
| 单击不拖 | 只打一个时间点 |
| 暂停后点「框选画面」 | 在画面上框一块区域,附在该时刻(显示为「· 画面区域」) |
空格 | 播放 / 暂停 |
← → | 逐帧步进(1/30 秒);按住 Shift 为 1 秒 |
| 点标注栏里的时间码 | 跳回该时刻 |
同一段时间重复标注不会产生新编号——起止时间都在 0.2 秒内视为同一处,只刷新原有那条的引用。
发送时时间码随编号一起进入输入框,形如:
1. `demo/clip.mp4` — 00:12.4–00:18.7
意见:这段转场太慢
并排对比
画布面板可以拆成两个窗格,把两个交付物放在一起看。
| 操作 | 结果 |
|---|---|
标签栏的 ⧉ / Alt+\ | 进入并排,第二个窗格填入另一个已打开的交付物 |
| 拖动中间分隔条 | 调比例,松手吸附到 30/40/50/60/70,双击复位 50/50 |
点击窗格 / Alt+1 Alt+2 | 切换焦点(激活窗格有 1px 蓝色内描边) |
| 点击另一个标签 | 替换非激活窗格,不会越开越多 |
| 标签栏的 ↔ / ↕ | 手动切换左右 / 上下排列(宽度足够时才生效,窄于 860px 一律上下) |
Esc | 退出并排,保留当前窗格 |
排列方式按内容与宽度自动决定:
| 面板宽度 | 行为 |
|---|---|
| ≥ 860px | 左右并排(默认),可用 Alt+方向键 在分隔条上微调 |
| 600–860px | 自动改为上下排列,避免文字被挤成窄条 |
| < 600px 或高度 < 500px | 放弃并排,回到单栏 |
不会形变:HTML 与 PDF 在窄窗格里按固定逻辑宽度(1280 / 900)整体等比缩放,页面自身的版式不变;图片用 contain;SVG 等比居中;三维每帧按当前宽高比重算投影;表格保持字号并允许横向滚动;只有文本会正常重排。
标注仍然属于文件而不是窗格:并排时两侧的标注一起发送,工具栏会提示「另有 N 条在另一侧」。
安全模型
| 面 | 现状 |
|---|---|
| 外部网络 | 零:插件不访问任何第三方域名,唯一请求是本机路由(/dsh-canvas/save、/dsh-canvas/file) |
| 文件读取 | 走 DSH 进程内通道 remote.workspaceFiles;HTTP 路由只返回元数据(大小、mtime),不返回任何字节 |
| 文件写入 | 仅限该会话工作目录的真实路径内:先 realpath 最近的已存在祖先、再拼接缺失尾段,用 path.relative 判断,符号链接与 Windows junction 一律拒绝;显式 workspace-write 策略;文本 ≤ 4 MiB(按 UTF-8 字节) |
| 请求体 | 读取时边读边计字节,超过 6 MiB 立即中断并 destroy,不把超大请求读进内存;文本按 Buffer.byteLength 判断,base64 先解码再判实际字节 |
| 路由来源 | 仅接受 dsh-app://app、桌面端的不透明来源,以及与本服务同端口的 loopback 页面;其它本机端口的页面一律拒绝 |
| 预览隔离 | HTML 与 SVG 预览都运行在 sandbox="allow-scripts" 的 iframe(不透明来源,无 allow-same-origin),注入脚本只能读到该预览自身,拿不到本页的标注与保存路由 |
| 本地留存 | localStorage 保存打开的路径、标注(含 ≤240 字符原文引用)与面板尺寸;不上传,可随时「清空」 |
两级上限:JSON 请求体 6 MiB、最终文件 4 MiB。分开是因为 base64 会把文件撑大约 4/3,用同一个数字会误伤合法文件;而只看 text.length 又会把 400 万个中文字符(12 MB)当成 4 MB 放行。
已知残余风险:桌面端页面以不透明来源发起请求,因此无法用来源区分"同一台机器上的恶意页面";两条路由仍以 sessionId 作为凭据(UUID 级)。若需要更强的隔离,可再加"进程内下发的一次性令牌",但那需要宿主暴露客户端↔主进程的服务通道。
性能取舍
- Markdown 只解析一次:解析结果按文本 memo,悬停只对已解析的块套一次 class,移动鼠标不再重跑解析器。
- 悬停有独立订阅通道:
hoverId不再触发全量notify,只有标注栏和当前视图会重渲染。 - 文本行缓存:行元素按「内容 / 搜索词 / 高亮区间」memo;搜索输入走
useDeferredValue,打字不被上万行高亮阻塞。 - 三维描边按需分配:边的顶点列表占 72 B/三角,仅在「边」开启时创建;面数超过 40 万且显示面时省略描边(此时描边本就看不清),切到线框仍显示全部边。
- 预览与编辑阈值分离:16 MB 以内可只读查看,4 MB 以内才允许内联编辑——草稿、基线与每次快照都会复制整串文本。
- 三维重定位先验后扫:先比标注原索引处的重心(O(1)),不一致才全量扫描,且超过 150 万面只标记不扫描。
npm test:13 组、116 项断言,覆盖 Markdown、Collada、标注重定位(文本与三维)、冲突判定、发送语义与控件契约。
最近的变化
0.5.0 · 并排对比与视频标注
- 并排对比:两窗格 + 可拖分隔条(吸附 / 双击复位)、焦点描边、标签栏联动、断点自动上下、
Alt+\\Alt+1/2Esc。 - 防形变:HTML/PDF 按固定逻辑宽度整体等比缩放,图片保持固有比例,三维按宽高比重算投影。
- 视频标注:拖进度条选段生成
00:12.4–00:18.7、暂停后框选画面区域、点时间码跳回该时刻;只有聚焦的窗格出声。 - 一次调用打开多个产物:
canvas的paths参数,每个文件一个标签。
0.4.1 · SVG 彻底沙箱化
- SVG 移入沙箱 iframe:此前为了点选图层而内联注入页面,等于让工作区里的 SVG 与本页面同源——恶意文件可以读标注、带
sessionId调用保存路由。现在与 HTML 预览一样跑在sandbox="allow-scripts"(无allow-same-origin)里,点选、聚光、SMIL 控制改由postMessage驱动,消息回传校验event.source === frame.contentWindow。 - 清洗器作为第二道防线加固:除
<script>与on*之外,还移除foreignObject / iframe / object / embed / handler,href/src只允许#fragment与data:image/,并删除可改写危险属性的 SMIL 节点与@import远程样式的<style>。
0.4.0 · 标得准、发得清、找得回
- 修复保存冲突从未生效:元数据路由把沙箱句柄当路径
stat,每次都抛错,于是客户端拿不到mtime、服务端也没有保存前快照,外部修改被静默覆盖。现在改为stat受限后的绝对路径,并附真实环境探针test/live-probe.cjs。 - 修复桌面端连不上服务:客户端把
http://127.0.0.1:1296写死,服务换端口后每次保存要等 2 秒再报Failed to fetch;改为同源相对路径(桌面壳会把非静态路径转发给本机服务)。 - 修复 Collada 解析不出三角形:补全
vertices → source → float_array两级引用,并支持polylist / polygons / tristrips / trifans、命名空间前缀与交错步长。 - 三维标注带面片信息(顶点 / 法线 / 面积 / 方位 / 总面数),重新导出后按重心自动重定位,几何真变了才标 ⚠。
- 评审回执:点编号标为已处理(✓),保留编号与意见,但不再参与发送。
- 按文件分组发送:多文件评审批次不再散乱。
- 控件语义分层:动作(primary / default / quiet)与开关(
aria-pressed)分开;开关改径向渐变(关闭红、打开绿,圆角更大),主按钮为淡色底 + 双向同心波纹。 - 图片取色 / 标尺 / 裁剪落盘;搜索上一个 / 下一个;常用意见模板;复制操作浮动提示;标注行支持键盘(Delete / Enter)。
- 移除三维导出 OBJ:格式控制属于专门的工具,不该由预览器顺手写文件。
0.3.0 · 标注与渲染
- 标注导出 / 导入:存成 JSON 交接或进版本库;导入按定位串复用原编号。
- 内容指纹重定位:文件改动后按前后文找回标注并更新行号(↻),找不到才标 ⚠。
- 精确高亮:悬停标注只高亮选中的字(CSS Highlights),不再整行涂底色。
- 标注栏分组:按文件分组、只看当前文件、跨文件搜索;每条可复制 / 单发 / 删除。
- Ctrl+Enter 发送,可选「发后清空」。
- Markdown 渲染补全:表格、任务列表、图片占位、删除线;修复结尾段落被吞。
- 三维测距 / 测角;渲染诊断默认隐藏。
- 窄面板自适应:次要按钮折叠进「⋯」,不再溢出。
- 预览注入开关:帮助面板可关闭,不信任的 HTML 也能安全打开。
- 构建:源码在
src/client.js,npm run build生成client.js(−26%)。
0.2.1 · 安全修复
/dsh-canvas/file只返回元数据(大小、mtime),不再回传文件字节;文件内容只经进程内通道读取。- loopback 来源必须与本服务同端口,其它本机端口一律拒绝。
- 移除未使用的范围读取客户端代码。
- 新增「安全模型」章节。
- 三维:面/边独立开关(线框可切消隐或透视),修掉共面边被深度测试剔除的问题。
- 快捷键:暂停/步进改为
Alt+S/Alt+A/Alt+D(单手,避开输入法空格冲突)。
0.2.0 及更早
- 面板头部 打开:工作区文件浏览(进目录、点文件即开),空状态也有「打开文件…」。
- 文本视图支持 编辑 → 保存(Ctrl+S),写回走 Host 路由,限定在会话自己的工作目录内。
.md默认 渲染 显示,可切「原文」;切换保持阅读位置、不抢焦点。- 同一文件只保留一个标签(路径归一化);同一位置重复标注复用原编号。
- 嵌入式对话的 输入框吸底:官方吸底依赖
[data-conversation-composer-overlay],嵌入式出现拿不到该标记,插件补上标记并让视图区成为滚动容器、按输入框高度留白。
已知限制
- 面板打开时 右侧栏不可用(框架只在对话占据主栏时渲染右栏),点「回到对话」恢复。
- 嵌入的对话没有会话头部(标题 / 视图 tab / 右栏开关);面板顶部只保留「画布 · 路径 · 发送标注 · 回到对话」。
- HTML 预览不做资源打包:相对路径的 CSS/JS/图片在 Blob 里加载不到,内联样式与 data URI 正常。
- 图片没有“真图层”:SVG 走真实图层树,位图只能框选区域(不做 PSD、不做自动分层)。
- 保存只能写回会话自己的工作目录内的文件;保存走插件自己的 Host 路由(Origin 白名单 + 目录约束 + 4 MiB 上限)。
- 标注跨设备不同步;换浏览器或清 localStorage 会丢。
- PDF 只能页级标注:预览走运行时内置阅读器(进程外渲染),拿不到页内点击坐标;页内区域标注需要自带渲染器(pdf.js,约 1 MB),与「零第三方网络依赖」的取舍冲突。
- 界面仅中文:
locale/*.json目前只有市场元数据,不含 UI 文案。 - 桌面端页面 origin 是
dsh-app://app/,浏览器上报Origin: null,所以保存路由对“不透明来源”只放行能解析到真实会话工作目录的请求。
开发
- 改客户端:硬刷新页面(Ctrl+Shift+R)。
- 改 Host 半(
index.js):plugin_manager对include:dsh-canvas-panel禁用再启用,即可热重载。 - 重新安装:
plugin_manager install_bundle指向本目录(已安装时先remove_bundle)。
PDF 源码布局
src/client.js 只是加载壳,正文按职责放在 src/parts/,由 scripts/build.cjs 按文件名顺序拼接后再做去注释/缩进变换:
| 片段 | 职责 |
|---|---|
00-head.js | store、通用工具、非 PDF 视图 |
20-pdf-vendor.js | pdf.js 动态加载与 vendor 路径 |
30-pdf-text.js | 页面文本缓存 |
35-pdf-selection.js | 视觉顺序取词 |
40-pdf-metrics.js | 测量字体与累积宽度表 |
45-pdf-ink.js | 渲染像素墨迹测量 |
60-pdf-page.js | 单页渲染、文字层、覆盖层 |
70-pdf-thumb.js | 缩略图 |
80-pdf-viewer.js | 文档、缩放、搜索、选区编排 |
85-pdf-wrapper.js | 失败回退 |
90-tail.js | 其余视图与导出 |
测试通过 test/_source.cjs 读取同一份拼接结果,不要直接读 src/client.js。
坐标约定
| 数据 | 唯一单位 | 产生处 | 消费处 |
|---|---|---|---|
标注 rect / bands | 页面比例 0–1 | 选词、框选 | 覆盖层按 ×100 + '%' 渲染 |
搜索命中 rects | 页面比例 0–1 | 搜索映射 | 同上 |
实时选区 live | 页面比例 0–1 | getClientRects | 同上 |
span 的 left/top/fontSize/width | viewport CSS px | 文字层 | 仅文字层内部 |
| canvas 位图 | 设备像素 | pdf.js render | 绘制与像素测量 |
跨层只允许通过 Geometry 风格的比例换算,禁止直接写 / box.width。
Plugins relacionados
dsh-web (dsh-task-board)
zhu1090093659/dsh-web
dsh-web (dsh-web-all)
zhu1090093659/dsh-web
dsh-TUI
ccch1mneyyy/dsh-tui
dsh-web-ui (dsh-task-board)
zhu1090093659/dsh-web-ui