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槽入驻(idattach-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)已由本持久化版本取代(重启后动态注册表清空)。
功能
- 「/」指令按钮:原输入框左侧打开指令菜单的「+」圆钮改为显示「/」字形
(
[data-composer-card] button[aria-haspopup="listbox"]字形替换 CSS), 点击行为不变(仍打开指令菜单,键盘上下选择 / 回车执行)。 - 「+」上传按钮:紧邻新增圆钮,弹出「图片 / 文档 / 其他文件」菜单,
各自调用浏览器原生文件选择器(
<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 服务
conversation(ConversationController):createDraftImages(files)、draftImages(ids)、releaseDraftImage(id);inputActions含addImages(ids)/setDraft(text)。 useProjection('imageLimits'):{maxImageBytes, maxImagesPerMessage, maxMessageImageBytes, mediaTypes}。- Host:
harness.handle、sessions.get(id).meta.cwd、shell.resolve/run(stdin 直写二进制)、sandboxPolicy.resolve({session})。
网络调研(相关案例)
- Slash command UX 模式:AI UX Playground – Slash Commands
- 附件按钮 → 浏览器文件选择器 → 草稿附件卡:MUI X Chat – Attachments
- 上传预览(模型读到什么)模式:AI UX Playground – File Upload with AI Preview
- ChatGPT composer 拆解:AI UX Playground – ChatGPT Composer
- OpenAI 文件上传能力与限制:OpenAI – File Uploads FAQ
- DeepSeek 网页端图片上传(格式/数量限制):头条教程、百度文库
- DeepSeek 指令体系:知乎《DeepSeek 官方指令库》解读
- DSH 自身命令注册:DeepSeek Harness – Commands
业界共识:指令走「键盘 / + 可视化按钮」双轨;上传用「回形针 / +」按钮调起原生文件选择器,
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 后界面还原。
移植为正式包(可选)
- 在 dsh-web-ui 全家桶仓库新建
packages/dsh-client-ui-attach-plus,src/client/index.ts引用本插件逻辑,改为ctx.plugin()正式注册; - CSS 改用包内
*.module.css(构建期编译,不再依赖内联字符串); - Host 半放入
packages/dsh-host-attach-plus(或用现有 fs 上传路由); - 加入
web-ui-all聚合包一键安装,即成为跨进程常驻插件。