attach-plus —— dshui 输入框「/」指令按钮 + 「+」文件上传插件

August 16, 2026 · View on GitHub

部署状态(持久化:profile 补丁 + 符号链接,随 DSH 启动自动挂载)

  • 包体plugin/pkg/attach-plus/(package.json + lib/index.js + lib/client.js)
  • 挂载~/.dsh/profiles/web/cordis.patch.yml 追加 - insert: {id: attach-plus, name: 'attach-plus'}
  • 解析~/.dsh/profiles/web/node_modules/attach-plus → 符号链接到本目录(与 dsh-ocr 同模式)
  • 生效机制:profile 补丁被 dsh profile boot 的 cordis HMR 热监视(watchUserPatches), 改补丁即热挂载,无需重启;代码变更需重启 dsh web(ESM 模块缓存)。
  • Host 端POST /api/dsh-attach-plus/upload(loopback 校验 → 会话工作区 uploads/ 落盘)
  • Client 端conversation.input.left 槽入驻(id attach-plus),bundle 经 clientModules 注入
  • 回滚:删除补丁 insert 段 + 符号链接,重启 dsh web
  • 验证curl -s -X POST http://127.0.0.1:3080/api/dsh-attach-plus/upload -H 'content-type: application/json' -d '{"sessionId":"<DSH_SESSION_ID>","name":"a.txt","base64":"aGk=","size":2}'

早期动态版本(attp-1/pkg-2)已由本持久化版本取代(重启后动态注册表清空)。

功能

  1. 「/」指令按钮:原输入框左侧打开指令菜单的「+」圆钮改为显示「/」字形 ([data-composer-card] button[aria-haspopup="listbox"] 字形替换 CSS), 点击行为不变(仍打开指令菜单,键盘上下选择 / 回车执行)。
  2. 「+」上传按钮:紧邻新增圆钮,弹出「图片 / 文档 / 其他文件」菜单, 各自调用浏览器原生文件选择器(<input type="file">):
    • 图片(png/jpeg/webp/gif)→ 产品原生草稿图片管线:缩略图栏、限额校验 (单张 5MB / 每条 20 张 / 总量 100MB)、随消息以 image block 发送;
    • 文档(pdf/office/文本/csv 等)与其他文件 → Host 落盘到会话工作区 uploads/<时间戳>-<清洗名>,并在草稿末尾追加「附件: <路径>」行, 模型借助自身文件工具读取(提示词协议仅支持 text/image,无文档块类型)。

架构

Client (conversation.input.left 槽, replaceRisk:none)
  ├─ AttachPlus 组件:+ 按钮 / 菜单 / 三隐藏 file input / toast
  ├─ 图片 → conversation.createDraftImages(files) → inputActions.addImages(ids)
  ├─ 文档/其他 → file.arrayBuffer() → base64 → fetch POST /api/dsh-attach-plus/upload
  └─ 加载期注入 CSS:字形替换 + 按钮/菜单/toast 样式(主题变量)

Host (webServer 路由 /api/dsh-attach-plus/upload)
  ├─ 校验(size ≤ 20MiB、base64 长度、文件名清洗、loopback)
  ├─ sessions.get(id).header.cwd(兜底 sessionPersistence.list())→ 工作区
  └─ shell.resolve({command:'mkdir -p uploads && base64 -d > …', workdir,
                    stdin:base64, sandboxPolicy}) → shell.run

关键接口(均已核实):

  • conversation.input.left:list / session 作用域;标准 props 含 useInput / inputActions / useProjection / sessionId / t;entry 级 inject 可用。
  • client 服务 conversationConversationController):createDraftImages(files)draftImages(ids)releaseDraftImage(id)inputActionsaddImages(ids)/setDraft(text)
  • useProjection('imageLimits'){maxImageBytes, maxImagesPerMessage, maxMessageImageBytes, mediaTypes}
  • Host:harness.handlesessions.get(id).meta.cwdshell.resolve/run(stdin 直写二进制)、 sandboxPolicy.resolve({session})

网络调研(相关案例)

业界共识:指令走「键盘 / + 可视化按钮」双轨;上传用「回形针 / +」按钮调起原生文件选择器, DeepSeek 网页版「+」菜单即拆「上传图片 / 上传文件」两项,与本插件分类一致。

边界与失败处理

  • 无会话(hero 态):conversation.input.left 不渲染,「/」按钮照常可用。
  • 机器忙(adjudicating/submitting):「+」禁用。
  • 图片超限 / 类型不符 / 非图片混入图片入口:toast 拒绝,不污染草稿; createDraftImages 全量校验后才注册,失败即无副作用;addImages 被拒时逐个 release。
  • 上传失败 / 超大文件(>20MiB):toast 提示,不插入文本。
  • 文件名清洗([\\/:*?"'<>|控制符]_)+ 时间戳前缀防冲突。
  • 字形替换依赖产品稳定 a11y 属性;若产品更新导致失效(原 + 按钮复现), 兜底方案:隐藏原按钮,在 conversation.input.left 自绘「/」按钮,经 inputTriggers.sessionOf(actx).toggleSource('command', {trigger:'/', …}) 复刻行为。
  • 停止/更新插件:slot / locale / styles / RPC 均为 fiber 副作用,自动回收,界面完整还原。

重新定义(DSH 进程重启后)

// 将 attach-plus.host.js / attach-plus.client.js 的内容分别作为
// code.host / code.client 传给 cordis_define(新插件,idPrefix 建议 'attp'),
// 再 cordis_run 激活;验证见下。

验收:GUI 指令按钮显示「/」且点击弹指令菜单;「+」菜单三项可用; 图片发送带图;文档发送后 uploads/ 下文件存在且内容一致;cordis_stop 后界面还原。

移植为正式包(可选)

  1. 在 dsh-web-ui 全家桶仓库新建 packages/dsh-client-ui-attach-plussrc/client/index.ts 引用本插件逻辑,改为 ctx.plugin() 正式注册;
  2. CSS 改用包内 *.module.css(构建期编译,不再依赖内联字符串);
  3. Host 半放入 packages/dsh-host-attach-plus(或用现有 fs 上传路由);
  4. 加入 web-ui-all 聚合包一键安装,即成为跨进程常驻插件。