# dsh-upload-file 让 DeepSeek Harness Web GUI 支持把**任意文件上传进对话**,两种入口:composer 输入框的附件按钮(SVG 回形针图标,多选),以及**直接把 OS 文件拖进 composer 区域**。选中的文件先由浏览器上传到宿主进程持久化存储,并以胶囊 chip 显示在 composer 上方的**「待发送文件」条**(与 shell 图片上传的「待发送图片」条同一体验):文件名 + 大小 + 移除按钮,发送时自动把干净的 markdown 链接(`📎 [名称(大小)](http://…/dsh-upload/raw/)`,绝对 URL 内含 sha256 id)并入消息文本;**发送后会话里的用户消息渲染成文件卡片**(图标 + 文件名 + 大小 + 打开,含文件链接的消息为卡片行 + 剩余文本气泡,纯文本消息按复刻的官方气泡渲染,插件渲染崩溃时自动回退官方渲染器);模型通过 `read_uploaded_file` 工具读取文件内容(文本直接返回,二进制返回元数据与下载地址)。 文件字节**不会**进入对话日志——进入对话的只有引用文本,与官方 describe-image 插件的图片上传采用同一套思路。 ## 功能 - composer 工具行新增附件按钮(`conversation.input.left` 槽位,SVG 回形针图标,与 shell 工具按钮同款胶囊样式),支持一次多选文件上传;上传中图标切换为旋转进度弧。 - **待发送文件条**:已上传待发送的文件以胶囊 chip 显示在 composer 上方(`conversation.input.dock` 槽位的 `FileDock`,`role="group" aria-label="待发送文件"`),每个 chip 含回形针图标 + 文件名 + 大小 + 移除按钮;草稿保持干净(仅一个不可见占位符用于让“仅发文件”时发送按钮可用,发送钩子会在提交时剥离)。 - **发送钩子**:结构性包装 `conversation.sendSession`(与 describe-image 同款、幂等、可卸载、发送失败自动恢复待发条),发送时把待发送文件的链接并入消息文本;无待发文件时原样穿透,不拦截任何其他发送。 - **会话内文件卡片**:以 `conversation.chat.node`(key=user / steering,priority -1)接管用户消息渲染——消息含本插件文件链接时渲染成卡片行(`role="group" aria-label="待发送文件"`,图标 + 文件名 + 大小 + 打开链接,纯链接消息不渲染气泡,类图片附件);否则**完整复刻官方用户气泡**(图片画廊 + MessageText + 时钟 + 复制按钮,官方 CSS 令牌逐条对齐)。渲染崩溃时 slots 会退役本 entry 并自动回退到官方渲染器。 - **拖拽上传**:把 OS 文件拖到 composer 区域时显示“松开以上传”提示条(document 级监听),松开即上传并进入待发送文件条。图片拖放不拦截,仍走 shell 自带的图片管线。 - 宿主 `POST /dsh-upload/attach` 接收 base64 JSON,校验大小后按内容寻址(`sha256:`)落盘到 `~/.dsh/uploads/`,返回 `[file attachment …]` 注释与 markdown 链接。 - `GET /dsh-upload/raw/` 以附件形式下载/预览已上传文件(跨重启可用,侧车元数据 + 摘要校验)。 - 模型工具 `read_uploaded_file`:接受裸 id / JSON 注释(含 `[file attachment {…}]` 子串提取)/ markdown 链接 / 原始 URL 路径四种引用形式,文本文件返回解码内容(上限内),二进制文件返回元数据 + 下载 URL。 - 设置分区 `dsh-upload-file`(Settings → 插件配置):`maxFileBytes`(默认 20 MiB)、`maxTextReadBytes`(默认 256 KiB)、`uploadsDir`(默认空 = `~/.dsh/uploads`)。 - 纯 JavaScript(ESM + `__ModuleLoader__` 静态包),**无需构建**,安装即可加载。 ## 安装 ### 方式一:从 GitHub 安装(推荐) ```sh dsh plugin --profile web add github:OctKwong30/dsh-upload-file # 重启 web GUI:dsh web(或按你的启动方式重启) ``` `dsh plugin add` 把参数转发到 profile 目录的 pnpm 执行:`github:` 前缀由 pnpm 直接从 GitHub 拉取仓库(同样支持 `git+https://github.com/OctKwong30/dsh-upload-file.git` 或 `OctKwong30/dsh-upload-file` 简写);安装后因包声明了 `dsh.bundle.patch`,会自动加入 `dsh.profile.bundles` 层。 升级到新版本: ```sh dsh plugin --profile web update dsh-upload-file # 再重启 web GUI ``` ### 方式二:本地源码安装(开发调试) ```sh # 在仓库父目录执行(把 换成你放置仓库的实际目录) cd dsh plugin --profile web add ./dsh-upload-file # 重启 web GUI:dsh web ``` `./dsh-upload-file` 这类相对路径会以你调用 dsh 的目录为锚点解析(等价 pnpm link),改完 `lib/*.js` 后重启 `dsh web` 即生效,适合边改边验;确认后推送到 GitHub,其他人即可用方式一安装。 ### 方式三:--patch overlay 临时开发加载(仅宿主半部) ```sh # 用绝对路径指向 lib/index.js 的覆盖层(仅开发用,路径按你的仓库位置调整) cat > upload-overlay.yml </dsh-upload-file/lib/index.js EOF dsh web --patch ./upload-overlay.yml ``` > **注意**:此方式绕过包解析,只加载宿主半部(`/dsh-upload` 路由 + `read_uploaded_file` 工具)。浏览器半部依赖 `package.json` 的 `dsh.client` 声明注入,附件按钮、待发送文件条、拖放上传与会话内文件卡片**不会出现**。需要完整功能时请用方式一/二安装。 ### 方式四:手动编辑 profile 补丁 把以下内容追加到 `~/.dsh/profiles/web/cordis.patch.yml`,并确保 `dsh-upload-file` 已作为依赖安装到该 profile: ```yaml - insert: - id: dsh-upload-file name: dsh-upload-file ``` ## 使用 ### 按钮上传 1. 打开任意会话,在输入框工具行点击回形针按钮(鼠标悬停提示“上传文件到对话”)。 2. 选择本地文件(默认上限 20 MiB,可多选)。 3. 每个文件以胶囊 chip 出现在 composer 上方的**待发送文件条**(文件名 + 大小 + × 移除),草稿保持干净;即使不输入文字,发送按钮也可用(“仅发文件”消息)。 4. 发送。发送钩子自动把每条链接并入消息文本(链接格式如下),会话里的用户消息渲染成**文件卡片**(图标 + 文件名 + 大小 + 打开;纯链接消息不显示气泡,类似图片附件;文字 + 文件则文字气泡 + 卡片行): ``` 📎 [report.pdf(12.1 KiB)](http://127.0.0.1:3080/dsh-upload/raw/sha256:abc…) ``` 5. 点击卡片打开/下载文件;模型从链接 URL 提取 `sha256:abc…` 后调用 `read_uploaded_file` 读取(参数可传裸 id、链接或 URL)。 ### 拖拽上传 1. 从系统文件管理器把一个或多个文件拖到 Web GUI 的 composer 区域。 2. 出现“松开以上传文件到对话”提示条后松开鼠标。 3. 非图片文件自动上传并进入待发送文件条(同按钮流程);图片文件交给 shell 的图片管线(不冲突,可混用)。 ## 架构 ``` 浏览器半部 lib/client.js 宿主半部 lib/index.js +---------------------------+ +----------------------------------+ | 📎 附件按钮 + 拖放 inlay | base64 | POST /dsh-upload/attach | | (input.left / input.dock)| ───────▶ | → 校验/落盘 ~/.dsh/uploads/ | | 上传 → 待发送文件条(rail) | | → 返回 note + markdown | | 发送钩子: sendSession 包装 | | GET /dsh-upload/raw/ 下载 | | → 链接并入消息文本 | | read_uploaded_file 模型工具 | | 会话内卡片: chat.node 接管 | | | | (key=user/steering, -1) | | | | 含链接→卡片;否则复刻官方 | | | +---------------------------+ +----------------------------------+ ``` ## 双语文案 浏览器半部内置 zh / en 迷你词典,随 GUI 语言设置切换:附件按钮提示、拖放提示、待发送文件条、错误提示(超限/读取失败/网络错误),以及文件卡片复刻官方气泡所需的全部文案(复制、时钟、图片画廊等)。 与官方插件体系的关系(参考文档): - [第一个 Harness 插件](https://deepseek-harness.github.io/deepseek-harness/develop/basic/):`apply(ctx)` + `inject` 插件形态。 - [开发一个 Tool](https://deepseek-harness.github.io/deepseek-harness/develop/basic/tool):`defineTool` 工具定义。 - [打包与安装插件](https://deepseek-harness.github.io/deepseek-harness/develop/basic/publish):`dsh.bundle.patch` 组合包机制与 `dsh plugin --profile add`。 - 客户端模式参考已装插件 `@linxin666/dsh-tool-describe-image`(attach 路由 + `sendSession` 发送钩子)与 `@linxin666/dsh-client-ui-aionui-panel`(`conversation.input.dock` 槽位 + `conversation.input.for(actx)` 写草稿 + 拖放 inlay 的 document 监听模式)。 ## 配置 Settings → 插件配置 → dsh-upload-file: | 字段 | 默认 | 说明 | | --- | --- | --- | | maxFileBytes | 20971520 | 单文件字节上限 | | maxTextReadBytes | 262144 | `read_uploaded_file` 文本预览上限 | | uploadsDir | (空) | 上传根目录;空 = `~/.dsh/uploads` | ## 安全说明 - id 必须匹配 `sha256:[0-9a-f]{64}`,杜绝任意路径读取。 - 文件名清洗为 basename,去掉路径分隔、换行与控制字符,防头注入。 - 上传/下载路由注册在宿主 webServer 上(通常仅本机 loopback 可达);若宿主把 webServer 配置为对外监听,需自行注意暴露风险。内容寻址 id 不可猜测。 - 客户端声明 mediaType 仅作展示与文本判定,不信任为安全边界。 ## 已知限制与扩展方向 - 待发送文件条渲染在 composer **上方**(`conversation.input.dock` 槽位),shell 的图片条渲染在输入卡内部;位置略有差异但交互一致。 - 为让“仅发文件”消息可用,草稿中会写入一个不可见占位符(U+200B),发送钩子提交前剥离;若发送钩子因 shell 升级而失效,占位符会以空文本随消息发出(shell 会忽略空消息)。刷新页面后 rail 清空,但已上传的字节仍在 `~/.dsh/uploads/`(内容寻址,无害)。 - **会话内文件卡片依赖手写复刻官方用户气泡**:随 shell 升级可能漂移(官方 CSS 类名是构建期哈希)。若官方气泡样式更新导致观感不一致,只需同步本插件 `CHAT_CSS` 中的 `.dsh-uf-*` 规则;渲染逻辑崩溃时 slots 会自动回退到官方渲染器,不会白屏。卡片仅识别本插件生成的绝对 URL 链接(`/dsh-upload/raw/sha256:…`),旧格式(相对路径 / JSON 注释)消息按普通文本渲染。 - 混合拖入“图片 + 非图片”时,非图片由本插件上传,图片由 shell 图片管线处理;若你的 shell 的 drop 监听器在插件之后注册或调用了 stopImmediatePropagation,混合/纯图片拖放可能被其独占,此时用附件按钮即可。 - 上传文件不做定期清理;如需会话级生命周期可在宿主侧挂定时清理任务。 - 拖放提示条使用内联样式,未跟随皮肤主题;如需定制可自行加 CSS。 - 本包 lib/*.js 是可直接运行的产物;若想改用 TS/JSX 工程化,可参考 dsh-web-ui 家族仓库(tsdown + `dsh.bundle.patch`)的构建方式。 ## License MIT