# attach-plus —— dshui 输入框「/」指令按钮 + 「+」文件上传插件 ## 部署状态(持久化: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":"","name":"a.txt","base64":"aGk=","size":2}'` > 早期动态版本(`attp-1`/`pkg-2`)已由本持久化版本取代(重启后动态注册表清空)。 ## 功能 1. **「/」指令按钮**:原输入框左侧打开指令菜单的「+」圆钮改为显示「/」字形 (`[data-composer-card] button[aria-haspopup="listbox"]` 字形替换 CSS), 点击行为不变(仍打开指令菜单,键盘上下选择 / 回车执行)。 2. **「+」上传按钮**:紧邻新增圆钮,弹出「图片 / 文档 / 其他文件」菜单, 各自调用浏览器原生文件选择器(``): - **图片**(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](https://www.aiuxplayground.com/pattern/slash-command/) - 附件按钮 → 浏览器文件选择器 → 草稿附件卡:[MUI X Chat – Attachments](https://mui.com/x/react-chat/behavior/attachments/) - 上传预览(模型读到什么)模式:[AI UX Playground – File Upload with AI Preview](https://www.aiuxplayground.com/pattern/file-upload-preview/) - ChatGPT composer 拆解:[AI UX Playground – ChatGPT Composer](https://www.aiuxplayground.com/teardowns/chatgpt/composer/) - OpenAI 文件上传能力与限制:[OpenAI – File Uploads FAQ](https://help.openai.com/en/articles/8555545-file-uploads-with-chatgpt-and-gpts) - DeepSeek 网页端图片上传(格式/数量限制):[头条教程](https://www.toutiao.com/article/7479267801006440987/)、[百度文库](https://wenku.baidu.com/noteview/1a992f56a05177232f60ddccda38376baf1fe0a0.html) - DeepSeek 指令体系:[知乎《DeepSeek 官方指令库》解读](https://zhuanlan.zhihu.com/p/25686088815) - DSH 自身命令注册:[DeepSeek Harness – Commands](https://deepseekdocs.com/docs/features/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 进程重启后) ```js // 将 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-plus`, `src/client/index.ts` 引用本插件逻辑,改为 `ctx.plugin()` 正式注册; 2. CSS 改用包内 `*.module.css`(构建期编译,不再依赖内联字符串); 3. Host 半放入 `packages/dsh-host-attach-plus`(或用现有 fs 上传路由); 4. 加入 `web-ui-all` 聚合包一键安装,即成为跨进程常驻插件。