Skip to main content
D

dsh-inline-image-ref

da0mine/dsh-inline-image-ref

DSH plugin: images dropped or pasted into the composer become @path reference chips at the caret, with a hover preview.

Install

dsh plugin --profile web add github:da0mine/dsh-inline-image-ref

README

dsh-inline-image-ref

拖进输入框的图片,按文件引用处理:不弹附件栏,而是在光标处插一颗 @路径 胶囊;鼠标悬停胶囊可以看原图。

行为

入口带真实 OS 路径的图片没有路径的图片(剪贴板截图 / 位图)普通文件
拖入→ @路径 引用胶囊(光标处)→ 落盘到 %TEMP%\dsh-inline-image\ 后插 @绝对路径原生 @路径 引用(不变)
Ctrl+V 粘贴→ @路径 引用胶囊(光标处)→ 同上(截图也能变文件)不变(带文字时交回原生)
📎 文件选择器→ @路径 引用胶囊(光标处)→ 同上不变

悬停引用胶囊:浮层显示原图(读不到时给出说明),pointer-events:none,不挡点击。

发出去的消息里也能悬停:用户消息里的图片引用是蓝色链接(DOM 上只有 @路径 文本,或 title 是路径)。命中不看类名(CSS Module 哈希会变),只看三样:title 命中我们记过的名字 / title 本身是路径 / 节点文本本身就是一个 @路径。范围限在输入框与聊天转录([data-composer-card]、[data-composer-input]、[data-chat-turn]、[data-chat-flow])内;整段必须就是一个引用,夹在句子里的片段不触发。

复制草稿再粘回来不退化:官方把胶囊复制成 clipboardText(就是 @"路径" 那段文字),粘回来只是一段文字 —— 胶囊会变成路径符号。本插件在 paste 的 capture 阶段认出「带图片扩展名的 @路径」,自己按原顺序把文字与胶囊拼回去(文字走 insertText,胶囊走 insertReference,两条 API 插入后光标都落在后面,所以逐段重取 caret 即可)。只在这类粘贴上接管:普通文字、@notes.txt 这种非图片引用都照旧交给官方。

代价:这类粘贴走的是纯文本重建,富文本格式(加粗等)不会保留 —— 前提是这一段里真有图片引用。

两条路

① 有真实路径(桌面端拖入 / 选择器) —— 不改官方代码,只把 File 的 MIME「降级」:

官方 dsh-client-ui-conversation 里这条判断决定分流:

if (path === "" || !directory && isImageMediaType(file.type)) uploads.push(file)

而引用分支最后落在 insertFileReferences(),插在实时选区(光标)之后。所以只要在 drop/paste/change 的 capture 阶段把这个 File 的 type 抹成 application/octet-stream,官方自己就走引用分支 —— 光标定位、前导空格、撤销历史、忙碌态拒绝、@路径 格式化全是官方那份实现。

② 没有真实路径(剪贴板里的截图/位图) —— 得先有文件。客户端 Remote 通道(workspaceFiles)只读,所以本插件带一个 Host 半:

POST /dsh-inline-image/save?name=<文件名>&sessionId=<会话 id>   ← 请求体就是文件字节
→ { ok: true, value: { path, name, originalName, dir } }

落点照 WorkBuddy 那套(它的目录叫 workbuddy-image-chip-preview):

%TEMP%\dsh-inline-image\<sha256 前 2 位>\<名字>-<sha256 前 16 位>.<ext>
  • 内容寻址:同图重复粘贴必然同一路径,只刷新 mtime,不多占磁盘;
  • 24 小时 TTL:每次保存顺手扫一遍,超期的旧图删掉(与 WorkBuddy 的 TEMP_IMAGE_TTL_MS = 864e5 同款);
  • 不往项目里写任何东西;单文件上限 32MB。

然后客户端用输入机的 caretSpan() + insertReference() 在光标处插 @<绝对路径> 胶囊(标签仍显示原始文件名)。落盘或插入失败时退回原生附件管线,不丢用户的文件。

DSH 的 fs 沙箱对读不设限、对写允许工作区与平台临时根,所以模型按这个路径读图是合法的。

取缩略图字节用 remote.workspaceFiles.readBytes(sessionId, 绝对路径),不另建读路由。

代价:临时文件 24 小时后会被清掉 —— 隔天再让模型"看下那张图"就会报文件不存在。需要长期可变现的图,请把它拖进工作区再从资源管理器拖入(那条走 @原路径,不受 TTL 影响)。

安装

从 npm 装(推荐):

npm install dsh-inline-image-ref

手工装进桌面 profile:~/.dsh/profiles/desktop/package.json

{
  "dependencies": { "dsh-inline-image-ref": "link:./node_modules/dsh-inline-image-ref" },
  "dsh": { "profile": { "bundles": [ "…", "dsh-inline-image-ref" ] } }
}

并在 node_modules 建同目录 Junction。改完要重启宿主(客户端 bundle 在启动时聚合,只有 Client 半的插件也一样)。

发版(一条命令)

npm run release              # patch:0.5.0 → 0.5.1,发 npm + 提市场 PR
npm run release -- minor
npm run release -- 1.2.0
npm run release -- patch --npm-only      # 只发 npm
npm run release -- --market-only         # 只提市场(版本不变)

它做的事:跑 _publish-smoke → 升版本 → npm publish(显式指定 registry.npmjs.org + 本机代理)→ 打 tag、push → 轮询 registry 确认上线 → 把市场条目 data/plugins/da0mine__dsh-inline-image-ref.yml 提到 awesome-dsh-plugin 并开 PR。

已知限制

  • 文件名 → 绝对路径靠插件插入时记下的表(localStorage["dsh-inline-image/labels/v1"],最多 300 条)。不同目录下的同名图片会互相覆盖,只有最后一张能预览。
  • 引用胶囊 DOM 上没有路径(Lexical 装饰节点只带 title),所以本插件没经手的 @图片(例如手打路径、或由别的插件插入)不会预览。
  • 预览要图片还在原地:%TEMP% 那份 24 小时后会被清,工作区/原路径那份不受影响;文件没了只显示「读不到这张图」。
  • 图片交给模型的仍然是「路径引用」而不是图片字节:文本模型要自己读,多模态模型也不会自动把图塞进上下文 —— 这是「按文件处理」的代价。

回归

node tests/inline-image/run.cjs   # 50 条(客户端,需要 playwright/chromium)
node tests/host-route.test.cjs      # 14 条(宿主路由)
node _publish-smoke.mjs              # 19 条(发布自检,纯 node)

21 条断言:拖放/粘贴的 MIME 降级(含「无路径保持原样」「普通文件不动」)、记忆表、悬停浮层的生成与收起、输入框外/非图片不触发、缺 Remote 时 fail-open、卸载无残留。

Related plugins