让 DSH 网页聊天支持拖拽任意类型文件:非图片文件保存到当前会话工作区,供 Agent 读取使用。
安装
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:Zenjibad/dsh-drop-any-file
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
扩展 DeepSeek Harness (DSH) Web UI 聊天的拖拽能力,让任意文件类型都能被接受——内置输入框只允许图片(PNG/JPG/WebP/GIF)内联附加,其余一律拒绝。拖入非图片文件将保存到当前会话工作区,供 Agent 读取使用。
关键词 / Keywords: dsh-plugin · deepseek-harness-plugin · drag-drop · file-upload · workspace · attachment · any-file · 拖拽 · 任意文件 · 工作区
仓库 / Repo
GitHub: Zenjibad/dsh-drop-any-file · 需要 DSH ≥ 0.1 及 web profile。
📑 目录
✨ 特性
| 特性 | 说明 |
|---|---|
| 📥 任意文件类型 | 拖入 .pdf、.zip、.txt、.csv、.bin…… 不再出现「仅支持 PNG/JPG」的拒绝提示 |
| 💾 保存到工作区 | 每个拖入的非图片文件都会写入当前会话的工作区目录,Agent 可用常规文件工具读取/搜索/使用 |
| 🖼️ 图片行为不变 | 只包含图片的拖拽仍走内置内联附加流程——已有的好用功能不受影响 |
| 🚫 绕过输入框拒绝 | 在 window 上以捕获阶段监听拖拽事件,先于输入框的仅图片处理器执行,拒绝 toast 不会触发 |
| 🫳 拖拽遮罩 + 提示 | 拖拽时显示全窗口主题遮罩(「松手保存到工作区」);保存后用 toast 提示结果(成功或失败原因) |
| 🛡️ 文件名清洗 + 大小上限 | 文件名取 basename 并剔除非法路径字符;请求体 80 MB / 文件 60 MB 上限 |
| 🌗 主题自适应 | 遮罩与 toast 全部使用 --dsw-alias-* 设计令牌,亮/暗色自动跟随 |
| ♨️ 重启常驻 | 真实 profile 打包插件:dsh plugin add 安装一次,每次 DSH 启动自动加载 —— 无需 cordis_define、无需每次重装 |
🏗️ 工作原理
用户把非图片文件拖入聊天窗口
│
Client 半区(浏览器) ▼
└─ window 上捕获阶段监听:dragenter / dragover / dragleave / drop
(从 conversation.input.dock 席位注册,该席位携带 sessionId)
└─ drop 含非图片文件?→ preventDefault + stopPropagation
(输入框的仅图片处理器根本收不到该事件)
└─ FileReader → base64 → fetch POST /drop-any-file/api
body: { sessionId, fileName, base64 }
│
Host 半区(DSH 进程内) ▼
└─ webServer 路由 POST /drop-any-file/api
└─ 解析目标目录:sessions.get(sessionId).header.cwd(即工作区)
└─ 清洗 basename → 解码 base64 → node:fs writeFile
└─ 返回 { ok, saved: { name, path, bytes } }
│
Client 半区(浏览器) ▼
└─ toast:「已保存到工作区:<文件名>」(或错误信息)
- 浏览器主动拉取/提交:无推送、无事件;只有用户真正放下文件时才 POST,Host 一律返回 JSON(失败时
{ok:false,error},绝无非 JSON 500)。 - 纯图片拖拽放行:处理器提前 return、不
stopPropagation,内置的内联附加流程照常运行。 - 持久化:随包声明
dsh.bundle(cordis.patch.yml)+dsh.client(exports["./client"]打包产物),作为真实 profile 插件安装,DSH client-modules 每次启动都会扫描加载。
🚀 快速开始
标准安装:dsh plugin add(重启常驻)
从本仓库安装:
# 本地目录(在本仓库父目录执行):
dsh plugin --profile web add ./dsh-drop-any-file
# 或直接从 GitHub(任意 DSH 机器):
dsh plugin --profile web add github:Zenjibad/dsh-drop-any-file
# 或:
dsh plugin --profile web add git+https://github.com/Zenjibad/dsh-drop-any-file.git
dsh plugin add = 向 profile 做 pnpm add + dsh.profile.bundles 协调:识别到本包的 dsh.bundle 声明后,把 dsh-drop-any-file 追加进 bundle 栈。重启 DSH,然后硬刷新浏览器标签页(Ctrl+F5)。启动时 client-modules 扫描器解析 exports["./client"],拖拽处理器随即生效。无需 cordis_define,重启后依旧。
⚠️ 注意:安装(或更新)客户端插件后必须硬刷新页面(
Ctrl+F5)——DSH 客户端 HMR 只会热替换已加载的 bundle,不会把新增的 bundle 注入已打开的标签页。
手动挂载(备选)
git clone https://github.com/Zenjibad/dsh-drop-any-file.git(任意位置)。- 在
~/.dsh/profiles/web/package.json的dependencies加"dsh-drop-any-file": "link:<仓库路径>",然后在 profile 目录pnpm install。 - 重启 DSH。
使用前提
- 任意处于活动状态的 DSH web 会话(保存目标是当前会话的
cwd)。 - 没有打开会话 → 没有可保存的位置 → 插件不生效(输入框保持默认行为)。
⚙️ 配置
无配置文件、无持久化设置。行为由源码中的常量固定:
| 可调项 | 位置 | 默认值 |
|---|---|---|
| POST 请求体上限(base64) | src/index.ts 中的 MAX_BODY_BYTES |
80 MB(约 60 MB 文件) |
| 单个文件上限 | src/index.ts 中的 MAX_FILE_BYTES |
60 MB |
| 保存目标 | 每次 drop 时解析 | sessions.get(sessionId).header.cwd(会话工作区) |
| 拖拽拦截 | src/client/index.tsx |
在 window 上捕获阶段监听 dragenter/dragover/dragleave/drop |
| Toast 时长 | src/client/index.tsx |
6 秒 |
❓ 常见问题
Q: 我拖了文件还是提示「仅支持 PNG、JPG、WebP、GIF 格式的图片」?
A: 插件没有加载到当前页面。先重启 DSH(如果 Host 半区尚未挂载),再硬刷新浏览器标签页(Ctrl+F5)。新增的客户端 bundle 只有整页刷新才会加载——HMR 不会把新 bundle 加进已打开的标签页。
Q: 我拖了文件但没反应 / 没有 toast?
A: 拖拽必须发生在聊天窗口上方、且已打开会话,并且 drop 中至少要有一个非图片文件。如果全是图片,则走内置附加流程(设计如此)。可查看浏览器控制台的 drop-any-file save error 日志。
Q: 文件保存到哪里?
A: 当前会话的工作区目录(sessions.get(sessionId).header.cwd)——也就是 Agent 的工作目录,Agent 可以用 read/glob/grep 等工具直接读取。
Q: 支持一次拖多个文件吗? A: 支持——drop 中每个非图片文件都会按顺序保存,toast 会逐一列出保存的文件名并统计失败数。
Q: 能拖超过上限的大文件吗?
A: 不能——Host 对超过 60 MB 的文件返回 413,客户端显示失败 toast。如需更大文件,改 src/index.ts 里的 MAX_FILE_BYTES(以及 MAX_BODY_BYTES)后重新构建。
Q: 装了本插件后旧的动态插件还在?
A: 本插件是打包式而非动态——没有动态孪生。若你之前安装过动态拖拽处理器,请用 cordis_stop / cordis_undefine 停掉,避免双重拦截。
Q: 如何彻底移除?
A: dsh plugin --profile web rm dsh-drop-any-file(或删除 profile 依赖与 bundle 条目)后重启 DSH。
⚠️ 安全须知
- 防路径穿越:文件名先取
path.basename(),再剔除所有非法路径字符(<>:"/\\|?*与控制字符),恶意文件名也无法逃出工作区。 - 写入范围受限:文件只写入解析出的会话
cwd,绝不写入任意路径。 - 大小上限:请求体上限 80 MB、解码后文件上限 60 MB,Host 不会缓冲无界载荷。
- 无额外 I/O:插件不发起网络请求、不执行 shell、不扫描磁盘——只做请求的那一次文件写入。
- 同源 base64 传输:文件字节以 base64 JSON 提交到同源路由(DSH webServer),不经过任何第三方端点。
📦 项目结构
dsh-drop-any-file/
├── src/
│ ├── index.ts # host 半区:body 读取、会话 cwd 解析、文件名清洗、writeFile、/drop-any-file/api 路由
│ └── client/index.tsx # client 包:捕获阶段拖拽监听、遮罩、toast、POST 到 host 路由
├── cordis.patch.yml # dsh.bundle patch(启动时插入插件行)
├── tsdown.config.ts # 打包 host(node ESM)+ client(CJS ModuleLoader)
├── package.json # name、exports["./client"]、dsh.client + dsh.bundle
├── lib/ # 构建产物(index.js、client.js)
├── AGENTS.md # AI agent 仓库指南
├── llms.txt / llms-full.txt
├── README.md / README.zh.md
└── LICENSE
🙏 致谢
- DeepSeek Harness — DSH 插件/动态运行时、Slots、主题、webServer、client-modules。
- headroom-stats-plugin — 打包式 client 插件构建模式参考(tsdown host/client 拆分、
cordis.patch.yml、dsh.client)。 - DSH-better-sidebar — 打包式 client 插件构建模式的另一参考。
📄 License
链接
同类插件
zhu1090093659/dsh-web#packages/dsh-task-board★ 6885
侧边栏多列任务看板:卡片交给真实 DSH 智能体会话执行,支持 cron 定时(Host 侧到点执行,关浏览器也生效)。
zhu1090093659/dsh-web#packages/dsh-web-all★ 6885
DSH Web UI 插件与皮肤合集:任务看板、git 图、右侧面板、远程移动端 UI、桌宠、实时 token 统计与皮肤中心。
omdsh-dev/DSH-better-sidebar★ 3338
侧边栏完整工作台:内置文件渲染编辑、终端、Git 与子代理,支持三方插件注册新 Tab。
ccch1mneyyy/dsh-TUI★ 2837
Claude Code 风格全屏终端 UI:像素鲸鱼顶栏、实时工作状态行、思考流式展开。
MeteorNOX/DeepSeek-Balance-Whale-Widget★ 1726
右下角常驻的小鲸鱼余额挂件:显示余额、今日已用、每轮对话消耗与随机台词,带音效与设置菜单。
Devin-AXIS/deepseek-design#deepseek-idesign★ 722
可视化设计工作室,支持网站、App 原型、海报、信息卡、报告和杂志的模板创建、元素编辑、选区级 AI 草稿衔接与导出。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。