🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

聊天图片灯箱 loyalchiiina/dsh-chat-image-lightbox

DeepSeek Harnessv1.9.3
loyalchiiina10 天前有提交已提供中文文档扫描:含敏感能力

DSH 插件(DSH >= 2.0.14):全屏聊天图片灯箱(缩放、平移、下载、翻页),会话级缩略图网格可调列数,隐藏或显示不留空白,TIFF/HEIC 按原生分辨率无损显示,防盗链自动降级,复制路径与在文件夹中显示。

36.5/100
综合分
2
GitHub 星标
668
npm 周下载
—/5
用户评分

数据截至 2026/10/25 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-chat-image-lightbox
npm 源 · dsh-chat-image-lightbox 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · vision
装得上吗
本站已在 dsh 0.2.1-rc.5 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 10 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-chat-image-lightbox @ 1.9.3
✓Node 引擎要求 ^22.19.0 || >=24.0.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖要求 >=0.1.7-0 <0.3.0 · 最新 0.2.1-rc.5 兼容
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/2 20:49:32

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 40 处,展示前 24 条)
  • 写入本地文件
    lib/index.js:16
    try { appendFileSync(IG_DIAG_PATH, new Date().toISOString() + " " + line + "\n"); } catch (e) {}
  • 读取本地文件
    lib/index.js:64
    if (existsSync(profRoot)) {
  • 读取本地文件
    lib/index.js:65
    var names = readdirSync(profRoot);
  • 读取本地文件
    lib/index.js:68
    if (existsSync(nm)) bases.push(nm);
  • 发起网络请求
    lib/index.js:193
    var req = (isHttps ? https : http).request(opts, function (res) {
  • 发起网络请求
    lib/index.js:224
    var res = await fetch(url, init);
  • 执行外部命令固定命令
    lib/index.js:415
    cp = spawn("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", b64], { windowsHide: true });
  • 写入本地文件
    lib/index.js:444
    try { mkdirSync(dir, { recursive: true }); } catch (e) {}
  • 读取本地文件
    lib/index.js:558
    var names = readdirSync(dir);
  • 读取本地文件
    lib/index.js:564
    var st = statSync(p);
  • 删除本地文件
    lib/index.js:576
    try { unlinkSync(rows[idx].path); total -= rows[idx].size; removed++; } catch (e) {}
  • 读取本地文件
    lib/index.js:695
    var entries = readdirSync(dir, {withFileTypes: true});
  • 删除本地文件
    lib/index.js:863
    try { unlinkSync(wicTmp); } catch (e5) {}
  • 执行外部命令固定命令
    lib/index.js:930
    spawn("explorer.exe", ["/select," + filePath], { detached: true, stdio: "ignore" }).unref();
  • 执行外部命令固定命令
    lib/index.js:932
    spawn("open", ["-R", filePath], { detached: true, stdio: "ignore" }).unref();
  • 执行外部命令固定命令
    lib/index.js:935
    spawn("xdg-open", [dirname(filePath)], { detached: true, stdio: "ignore" }).unref();
  • 写入本地文件
    lib/index.js:1398
    writeFileSync(out, conv);
  • 写入本地文件
    lib/index.js:1814
    writeFileSync(d2, g2.body);
  • 写入本地文件
    lib/index.js:1858
    try { mkdirSync(outDir, { recursive: true }); } catch (e) {}
  • 写入本地文件
    lib/index.js:1868
    writeFileSync(dest, got.body);
  • 写入剪贴板
    lib/client.js:1038
    navigator.clipboard.writeText(abs).then(success, function () {
  • 发起网络请求
    lib/client.js:1057
    fetch(GALLERY_REVEAL_URL + "?abs=" + encodeURIComponent(abs))
  • 发起网络请求
    lib/client.js:1167
    fetch(originalUrl).then(function (r) {
  • 发起网络请求
    lib/client.js:3554
    fetch("/api/image-gallery/diag", {
扫描于 2026/11/2(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

界面预览

6 张 · 点击查看大图

功能亮点

  • 全屏查看聊天图片
  • 缩放与拖动图片
  • 下载图片
v1.9.12026-10-24
· v1.9.1 — 混合图文容器被判九宫格 · 症状 混合图文容器(图片与文字混排)被误判为九宫格,自动排成 4 列且文字乱入。 · 根因 列数判定只看容器内图片数量,没有排除「容器里同时有散文」的情况,于是文字块被当成瓦片参与排布。 · 修复 · 列数判定增加散文检测:容器内只要存在非图片的文字节点,就不按九宫格处理 · 文字块恢复为正常段落流,不再参与瓦片 float 流 · 生效条件 替换已安装包的 JS 必须完全退出 DSH 进程再启动。
v1.9.22026-10-24
· v1.9.2 — 正文里一张图都没有 · 症状 模型调 display_image 一切正常(十张图全部正确返回 markdown),但回复正文里一张图都没有。 · 根因 不是插件不稳定,是规则放的位置不对。铁律是常驻 system prompt section,离「刚拿到返回值」那一刻太远;工具结果被界面折叠成一行「已调用工具」,用户滚动正文时什么都看不到,而模型的注意力在工具返回那一刻最集中。 · 修复 lib/index.js 的 render() 从「只返回 value.markdown」改为在返回值最上方加一条投递 banner,明确要求模型把 markdown 原样复述进正文。 · 生效条件 替换已安装包的 JS 必须完全退出 DSH 进程再启动。
v1.9.32026-10-24
· v1.9.3 — 首图比同排高 20px · 症状 1.9.1 修完「四列 + 文字乱入」后,十张图横向成行了,但第一行第 1 张卡顶边 y=26,第 2、3 张 y=46 —— 首图独自高出 20px。 · 根因 瓦片成行靠「零高 + float 跨父级连成一条 float 流」。但 仍拥有行盒:只要段落里残留任何行内幸存者(零尺寸 span、空灯箱槽、Fragment 留下的兄弟节点),该段就不再零高,它的行盒会把之后整条 float 流往下推。 · 修复 · 新增 CSS .dsh-ig-tileline:只归零承载瓦片的那个段落的盒模型与字体度量,让它彻底不拥有行盒 · tickThumb() 在拿到 dsh-ig-tilewrap 的同一分支里调 markTileLine(host);untickThumb() 调 unmarkTileLine(host) · 三重门禁防回归:tileLineOwner 必须是段落且拒绝 body/documentElement;tileLineIsDirect 要求恰好 1 个瓦片子元素;tileLineIsSafe 有散文即拒绝 · 回归 新增 test/probe-tileline.mjs(23 项);node test/run-all.mjs → 12/12 全绿。 · 生效条件 替换已安装包的 JS 必须完全退出 DSH 进程再启动(Ctrl+R 不重载 client bundle)。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
DSH Chat Image Lightbox 🖼 🛡 v1.8.9 — 工具行折叠 ≠ 已发送:铁律升级为「必须复述 + 计数自检」 / Collapsed tool row ≠ delivered 实测事故(2026-10-21):插件 20 张图(10 种格式)全部交付成功,但模型把图全留在 display_image 的工具结果里,正文只写了一张格式对照表 → 用户回「一张没看见啊」。原因是 1.7.8~1.8.8 的铁律只写了「不得只依赖工具行」,可以被「正文写了字但没写图」绕过。 1.8.9 起铁律升级为 6 条硬约束 + 1 条收尾自检:必须把工具返回的 原样复述进正文、表格/结论/清单都不算图、发前数一遍正文里 的数量必须等于声称的张数。
展开完整文档(README 剩余 1 节)
DSH Chat Image Lightbox 🖼

🛡 v1.8.9 — 工具行折叠 ≠ 已发送:铁律升级为「必须复述 + 计数自检」 / Collapsed tool row ≠ delivered

实测事故(2026-10-21):插件 20 张图(10 种格式)全部交付成功,但模型把图全留在 display_image 的工具结果里,正文只写了一张格式对照表 → 用户回「一张没看见啊」。原因是 1.7.8~1.8.8 的铁律只写了「不得只依赖工具行」,可以被「正文写了字但没写图」绕过。

1.8.9 起铁律升级为 6 条硬约束 + 1 条收尾自检:必须把工具返回的 原样复述进正文、表格/结论/清单都不算图、发前数一遍正文里 的数量必须等于声称的张数。
Opt out with config.injectImageLaw: false.

Real incident (2026-10-21): all 20 images were delivered fine, but the model left every one of them inside the
collapsed tool result and only wrote a format table in the body — the user replied "not a single one visible". Since 1.8.9 the rule demands verbatim echoing of the returned markdown into the reply body, forbids passing a table off as an image, and requires a pre-send count of ! occurrences.

🛠 v1.7.5 — 发图片失败已修复 / Image-sending bug fixed

如果你在 DSH 里发图片失败(报 prompt rejected + ERR_DLOPEN_FAILED + session/agent-busy,纯文字正常),那是 1.7.4 及更早的已知问题,1.7.5 已修复。升级 + 重启 DSH 即可:

pnpm add dsh-chat-image-lightbox@latest

原因:本插件旧版把 sharp 精确锁在 0.34.4,与 DSH 内核的 ^0.35.3 冲突,两个版本争抢同名 libvips-42.dll。
详见 [Release v1.7.5 · 排查见 [⑥ 发图失败 与 Troubleshooting ⑥。

If sending an image fails with those errors (plain text works fine), it is a known issue in 1.7.4 and earlier, fixed in 1.7.5. Upgrade and restart DSH. Cause: the old version pinned sharp to 0.34.4, conflicting with the kernel's ^0.35.3 over the same-named libvips-42.dll.

对话里的图,值得被认真看。

Images in chat deserve a proper look.

📦 npm 包名 / npm package name:dsh-chat-image-lightbox(无 scope / unscoped)旧包名 @loyalchiiina/dsh-chat-image-lightbox 已被取代,历史版本已标记弃用。
The legacy scoped name @loyalchiiina/dsh-chat-image-lightbox is superseded; its older versions are deprecated.

dsh plugin --profile desktop add dsh-chat-image-lightbox

当前版本 / Current version:1.8.9 适用内核 / Requires:本包声明 engines.dsh: ">=0.1.7-0 <0.3.0" 与 peerDependencies["@deepseek-ai/dsh"]: ">=0.1.7-0 <0.3.0"(带世代上界,因为 peer 是运行时门禁,不匹配会被宿主直接禁用);本版实测验证于 DSH NEXT 2.0.19-next,客户端组件 @deepseek-ai/dsh-client-ui-tool 实测 0.2.1-rc.2。
This package declares engines.dsh: ">=0.1.7-0 <0.3.0" and peerDependencies["@deepseek-ai/dsh"]: ">=0.1.7-0 <0.3.0" (bounded by generation, because peers are a runtime gate: a mismatch makes the host disable the plugin outright); this release was verified end to end on DSH NEXT 2.0.19-next, with @deepseek-ai/dsh-client-ui-tool measured at 0.2.1-rc.2.

⚠️ 升级到 1.7.3 前请注意 / Before upgrading to 1.7.3 1.7.2 起 移除了「跳图」功能(该功能在多次迭代中不够稳定,已整体删除,界面上不再出现 ⌖ 按钮)。
从 1.7.2 起,跳转到图片功能已移除——它在多次迭代中始终不够稳定,现已彻底删除,不再出现 ⌖ 按钮。

效果预览 · Screenshots

灯箱查看 · Full-screen lightbox

灯箱全屏查看

点开正文里任意一张图即全屏灯箱:原图按屏幕高度自适应,右下角标注当前是第几张(如「图4」)。
右侧竖排工具栏自下而上依次是:✕ 关闭、⬇ 下载、‹ › 上一张 / 下一张、复制、在文件夹中显示、缩略图网格、隐藏图片、− + 1×(缩小 / 放大 / 复位比例)、↑ ← ↺ → ↓(上移 / 左旋 / 复位 / 右旋 / 下移),底部显示当前缩放百分比。

切换图片 · Paging between images in the same message

切换图片

同一条消息里的多张图可以连续翻页(截图里是 30 张的第 21 张)。红色箭头指的是右侧的 ‹ › 两颗按钮,也可以用键盘方向键翻页;翻页后计数(如「21 / 30」)与缩放比例同步刷新。

缩略图分列 · Thumbnail grid with adjustable columns

3 列 · 3 columns

3 列缩略图

4 列 · 4 columns

4 列缩略图

5 列 · 5 columns

5 列缩略图

一键把整个会话的图片收成网格;面板里的「缩略图显示列数」可设 1–12 列,输入即生效并保存为默认值。
每格高度统一、object-fit: contain 居中,所以竖图横图混排也不会参差不齐;网格之后的正文从最左侧开始,不会被挤到中间。

隐藏 / 展开 · Hide / restore

图片隐藏效果

一键隐藏所有图片,并把它占的空间完全收掉(不会留下空白带);再点一下原样恢复。
点一下隐藏全部图片,并把它占的空间完全收掉(不会留下空白带);再点一下原样恢复。

功能总览 · At a glance(中英对照 / Bilingual)

灯箱查看 · Lightbox viewer

| 中文 | English |
| 点击任意对话图片进入全屏灯箱 | Click any chat image for a full-screen lightbox |
| 滚轮缩放、拖拽平移 | Wheel zoom, drag to pan |
| 工具栏缩放按钮 ⊕ ⊖ 与 1× 复位 | Toolbar ⊕ / ⊖ zoom buttons with 1× reset |
| 方向键平移视角(按住连续移动) | D-pad moves the view (press-and-hold repeats) |
| 左右翻页:按钮 / 键盘 / 触屏滑动 | Prev/next via toolbar, keyboard or swipe |
| 下载原图(自动识别扩展名) | Download original (extension auto-detected) |
| 复制图片文件绝对路径 | Copy the image's absolute local path |
| 在文件夹中显示(资源管理器定位) | Reveal in folder (file manager locates the file) |

会话级图片控制 · Conversation-wide controls

工具行里的 「对话图显控制」 按钮打开面板,面板内含三项:

工具行里的 「对话图显控制」 按钮打开面板,面板内含三项:

| 中文 | English |
| 缩略图:一键把整个会话的图片收成网格 | Thumbnail: collapse every image in the conversation into a grid |
| 缩略图显示列数:1–12 可调,改过即存为默认值 | Columns per row: 1–12, and your value is saved as the default |
| 隐藏:一键隐藏 / 再点恢复,不留空白 | Hide: hide / restore all images, leaving no blank space |

列数说明:默认 3 列。设置后会记住,下次启动仍用该值;改回其他值即覆盖默认。
列数:默认 3 列。设置后会记住,下次启动仍用该值;改回其他值即覆盖默认。

格式与清晰度 · Formats & fidelity

| 中文 | English |
| TIFF/TIF、HEIC 自动转换显示 | TIFF/TIF, HEIC auto-transcode for display |
| TIFF/PNG/BMP 无损原像素(逐像素零误差) | TIFF/PNG/BMP lossless, native pixels (bit-exact) |
| 2 亿像素大图流畅显示 | 200-megapixel images display smoothly |
| 11 种格式实测支持(JPG/PNG/WebP/GIF/AVIF/SVG/BMP…) | 11 formats verified (JPG/PNG/WebP/GIF/AVIF/SVG/BMP…) |
| 网图被防盗链拦截时自动转存本地显示 | Hotlink-protected remote images are auto-saved locally |

宿主侧工具 · Host-side tool (display_image)

| 中文 | English |
| 关键词搜网图(Bing/花瓣/搜狗 3 源回退,按抽样可加载率选源) | Keyword image search (Bing / Huaban / Sogou fallback, source chosen by sampled loadability) |
| 搜图自动过滤广告与他人面孔 | Auto-filters adverts & other celebrities |
| 本地文件/目录直读(TIFF/HEIC 转码) | Read local files/dirs (TIFF/HEIC transcoded) |
| 网图落地保存到本地目录(save_to) | Save remote images to a local folder (save_to) |
| 能直连就直连,被拦截才自动转存 | Direct CDN when it works, local copy only when blocked |
| 代理自动探活(无需手配) | Auto proxy discovery (TCP probe) |

隐私与安全 · Privacy & safety

| 中文 | English |
| 一切本地服务仅回环(loopback-only) | All local services are loopback-only |
| 打开文件夹用参数化调用,绝不拼接 shell 命令 | "Show in folder" spawns argv, never a shell string |
| 保存说明不回显本机路径 | No local paths echoed in confirmations |
| 只作用于对话区图片,不碰侧栏/头像 | Only touches conversation images, never the sidebar or avatar |

DeepSeek Harness 插件,在聊天里内联显示图片,并带灯箱浮层——缩放、下载(弹出另存为对话框)、上一张/下一张切换。

智能体发来的图片挤在气泡里,小得看不清细节,想看清就得先存下来再到别处打开。装上这个插件,点一下就能把聊天里的任意图片全屏打开:缩放、拖动平移、下载,还能在同一消息的其他图片之间翻页。

功能

- 内联显示:AI 回复里的图片直接渲染在聊天中(走 markdown alt)
- 点击缩放:点任意图片打开全屏灯箱;再点放大的图片会继续放大,已经放大时再点则关闭灯箱(放大后的图片盖住了背景,这样能保证关闭目标一定点得到)。按 Esc 或点背景也能关闭。
- 滚轮缩放与平移:放大后可用鼠标滚轮继续缩放,拖动可在图片上平移
- 下载:点下载按钮 ⬇ 保存图片(同源图片会弹出浏览器另存为对话框;文件名经过清洗,扩展名按图片类型推导)
- 导航:左右箭头按钮、键盘方向键,或在触屏设备上左右滑动,在多张图片间切换
- 说明文字:查看器显示当前图片的文件名/说明
- 无障碍:灯箱是规范的 role="dialog",带 aria-modal、按钮 aria-label,并管理焦点(关闭后焦点回到触发元素)
- 流畅:预取相邻图片,翻页几乎无等待
- 关闭:点背景、按 Escape,或点 ✕ 关闭
- ZCode 风格渲染:聊天图片就地加样式——从不移动、缩放或改变父节点——因此不会和 DSH 的 React 虚拟列表打架(不会再出现图片消失/重复,或控制台报 removeChild 错误)
- 限制高度,不裁剪:每张聊天图片都按原始宽高比渲染,高度上限为 min(460px, 64vh)(ZCode 的观感);图表和示意图完整可读
- 响应式图片行:一条消息在同一个容器里包含多张图片时,按响应式 CSS-grid 行排布——只在容器上加一个 class,不移动任何节点
- 打开即看高清原图:点击任意图片打开灯箱,显示全分辨率原图——放大后看到的是真正的高清文件
- 界面干净:灯箱工具栏(下载 / 关闭)、上一张/下一张箭头、计数器和说明文字在灯箱关闭时全部隐藏——只有你真正查看图片时才出现,绝不挤占聊天布局
- 缩放 / 平移面板:灯箱还给手势操作配了显式控件——⊖ ⊕ 1× 控制缩放,带实时百分比读数;方向键面板移动视图(按住可持续移动)。滚轮、拖拽和 +/-/Shift+arrow 依然可用
- 一个弹出面板管住整个对话的图片控制:访问模式控件旁边只有一个紧凑的「对话图显控制」触发器,点开滑出面板,内含缩略图开关、隐藏开关和列数字段——不再有一排按钮挤在输入框旁
- 可配置列数的缩略图网格:一键把对话里所有图片收成小图块网格。缩略图显示列数字段设置每行列数(1–12);填写的值会保存,下次启动继续沿用
- 隐藏 / 显示:一键隐藏所有图片,占用的空间完全收起(不留空白带);再点一下原样恢复
- 只管对话图片:插件刻意忽略消息列表之外的图片——登录账号头像和侧边栏图标同样是普通 元素,一律不动(1.7.2 修复)
- 构造上安全:缩略图外观只用 class 和内联样式实现。任何节点都不移动、不删除、不改变父级——早期版本这么做过,导致 DSH 的对话槽位抛出 DOMException,对话记录渲染成空白
- 自动增强:MutationObserver 自动增强新加入聊天的图片,并重新接管被 React 重渲染覆盖掉 class 的图片
- 把图片带进聊天(display_image 工具,宿主侧):一个工具接受本地 path(文件或目录——TIFF/HEIC 自动转码,可选降采样)、远程 url,或 query(关键词图片搜索:先 Bing,再 Huaban 和 Sogou——每个来源都要通过抽样可加载性检查,有结果但图片加载不出来的来源直接跳过),返回可直接粘贴的 Markdown。本机任意文件都能通过 /images/?abs=<path> 就地提供(仅限回环地址),无需复制到图库根目录
- 防盗链回退(1.7.2):搜索结果可能返回 200,在浏览器里却仍然打不开。有些 CDN(花瓣走腾讯 EdgeOne)会拒绝 Referer 不是自家域名的请求,图片于是显示成「无法预览」。工具现在会按浏览器的方式逐个探测 URL,只重新保存那些会被拦截的链接,能用的链接保持直连,不做多余下载
- 来源健康检查(1.8.1):以前「API 返回了结果」就被当成「来源可用」。花瓣的搜索 API 一直返回 200 和二十个 pin,可它的每一个图片 URL 都被腾讯 EdgeOne 返回 567——于是一次搜索返回五张图,全部渲染成「图片无法预览」,而因为第一个来源「有结果」,搜索再也不会落到别的来源。现在每个来源都会抽样(4 个 URL,按浏览器的方式探测);可加载率低于 30% 就跳过该来源并记录原因,如果没有来源达标,就用最好的那个,并在说明里写清楚。来源顺序也改成实测可用的顺序(Bing → 花瓣 → 搜狗;搜狗现在对匿名调用返回 forbid,会报告为「来源不可用」,而不是悄悄当成「没找到图片」)
- 并发救援(1.8.1):救援被拦截的图片原来是串行的——十张图要 18.4s。现在四路并发(结果顺序不变,单个失败不会取消其余任务),健康抽样已经探测过的 URL 不再重复探测
- 失败要说出来(1.8.1):救援失败原来会消失在空的 catch (e) {} 里,用户只看到坏掉的缩略图和一条沉默的日志。现在原因记录在 rescueError 上,并写进说明(「N 张图片被源站拦截,无法救援」),「一张图都没出来」至少是看得见的
- 自动救援缓存不再无限增长(1.8.1):~/.dsh/image-gallery-auto 原来只写不清(实测:129 个文件 / 187 MB)。现在每次救援前都会检查,超过 500 个文件或 512 MB 就按最旧优先清理(删不掉的文件不会中断清理)
- 经得住 Junction 安装(1.8.4):三个配置档里,插件都是指向 local-plugins 的目录 Junction。Node 从模块的真实路径解析 require,所以 createRequire(import.meta.url)("sharp") 会从 local-plugins/ 往上找,而 sharp/undici 住在 profiles/<name>/node_modules——链接把它们从查找链里切掉了。后果:启动行显示 sharp: unavailable,每个 TIFF/HEIC 都渲染成「图片无法预览」,undici 也找不到。resolveDep() 现在会搜多个根(模块路径 / process.execPath / EXEPATH / cwd,每个向上八层),三个依赖都走它
- 直连就是真直连(1.8.4):设置 NODE_USE_ENV_PROXY=1 后,Node 的全局 fetch 会自行套用 HTTPS_PROXY——省略 dispatcher 并不等于「不走代理」。因此,只回应直连请求的站点(duitang)被报成「探测失败」,好图也被当成坏图丢掉。现在直连抓取改用 node:https 加显式 Agent(完全忽略环境代理,最多 5 次重定向);getBytes() 先试运行时 fetch,传输失败再回退直连,把 HTTP 状态码当作答案,而不是再走一条路的理由
- 正文不再指向浏览器解不了的文件(1.8.5)——1.8.4 修好 sharp 之后,TIFF 在工具行里显示正常,回复正文里却仍是「图片无法预览」,因为这两条路径由不同的处理器提供。正文里写成 的图片会被 Host 改写成 api/file?path=…,而那里的 serveFile() 只做 mime.lookup(extension),然后把原始字节直接流出去——完全不做转码。Chromium 没有 TIFF/HEIC 解码器, 自然失败。现在每张本地图片在交付前都会转码成浏览器能渲染的副本(无损源 → PNG,其余 → WebP q92);正文引用这个副本,localPath 保留原图。如果结果仍超出 Host 的单图上限(默认 20 MB——413 同样会渲染成「图片无法预览」),就把宽度乘以 0.7 重试,最多五轮。后缀说是图片、魔数却不是的文件(比如存下来的 CDN 错误页)在交付前直接丢弃,并说明原因(「后缀是 .jpg,内容却不是图片」),而不是给一句含糊的转码失败
- 候选池是需求量的四倍,死条目才有替补(1.8.8) 要三张「喜羊羊」的图,结果一张都没有——不是源里没有,而是 Bing 把不欢迎外链的站点排在了前面:前三名是 bkimg.cdn.bcebos.com 和 i0.hdslb.com,全是 HTTP 403,而旧代码只取 want 条命中,一次失败就当成整次查询失败。现在候选池是 want × 4(上限 40),每批下载八个,一旦凑够请求数量就停:既不会为了交付三张去下四十个文件,也不会让三个 403 毁掉整次查询。实测:「喜羊羊」从「0 张图 + 一个报错」变成「8 秒 3 张图」;十二张「刘亦菲」从 17.5 秒降到 7.9 秒。
- 遇到 403 就用站点自己的 Referer 重试一次(1.8.8) 防盗链问的是「这个请求看起来是不是来自我自己的站点」,所以没有哪个 Referer 对所有主机都正确。在 36 个真实候选上实测:用花瓣的 Referer 取到 32 张,用各主机自己的 Referer 也取到 32 张,两种都试取到 33 张 —— i1.hdslb.com 只认自己的 Referer(403 → 200,1.5 MB),而 gd-hbimg.huaban.com 正好相反(用它自己的 Referer 会被腾讯 EdgeOne 拦成 567)。现在遇到 403/401/567 会用同源 Referer 重试一次;仍失败就跳过这张图,并在 note 里写明原因。
- 搜索不再交付未经审核的链接(1.8.7):「35 个里能加载 4 个」说的是源的整体质量,不代表这一张图能不能显示。现在每个候选都会先下载、校验 magic byte 再交付;死链(sinaimg 404 NoSuchKey)和 307 跳到别的图床(renwuji → static.xsite.com.cn)都会被丢弃,note 会报告失败了多少张、为什么失败。
- 正文编号跨批次连续(1.8.7):display_image 新增 start 参数。第一批之后的每一批都传「上一个编号 + 1」,回复就从 图1..图N 接着编号,而不是重新从 图1 开始。
- 真正的 HEIC 以前从未跑通(1.8.6):这台机器上唯一的 .heic 样本其实是 JPEG(开头字节是 FFD8FF —— 扩展名在撒谎),所以「HEIC 已验证」是假的;sharp 自带的 libheif 编译时没有 HEVC 解码器,它能正常读容器,但一取像素就抛错(Support for this compression format has not been built in: HEVC)。在 Windows 上插件现在回退到系统 WIC 解码器(-EncodedCommand,路径永远不经过命令行);实测一张真正的 1440x960 HEIC → 520x347 PNG,耗时 532 ms。新增 test/probe-heic-real.mjs(24 项检查),自带真实 HEVC 样本,并断言样本本身不是伪装成 HEIC 的 JPEG。
- 信字节,别信 200 和 content-type(1.8.4) —— 一个「成功救回」的缓存文件其实是 CDN 错误页(HTTP 200,content-type: image/jpeg,正文以 开头)。它被当作 image/jpeg 发出,浏览器解码失败,结果就是「图片无法预览」。现在下载、探测、发送三处都会校验 magic byte (JPEG/PNG/GIF/WEBP/TIFF/BMP/ISOBMFF),不是图片的一律拒收 —— 对自己撒谎的文件会收到带原因的 415,而不是悄悄给出一张坏图。
- 文件名含 % 现在能解析了(1.8.4) —— ?abs= 被百分号解码了两次,于是 a%20b.jpg 变成 a b.jpg(404),折扣50%的图.jpg 抛出未捕获的 URIError,直接打断请求。现在直接使用已解码的值,只有当该路径不在磁盘上时才做一次兜底解码,转义格式错误则返回 400。
- 「Probe failed」不再算作「可加载」(1.8.3):健康采样过去只把结果分成「被拦截」和「未被拦截」两类,于是网络/TLS 失败——什么都没测到——也被算成可加载。有一次真实搜索里,4 个探针有 2 个以 fetch failed 失败,备注却仍写着「4 loadable」,ratio 停在 1.0,闸门因此从未切换来源,用户拿到的还是坏图。现在结果按三种方式统计(可加载 / 被拦截 / 探针失败),ratio 只由确认的 2xx 图片决定;备注和跳过原因分别写明各项计数(「N blocked by the origin」与「N probe-failed, unverified」)
- 图片投递法则(1.8.9):插件常驻一条系统提示词规则(image-gallery:delivery-law,order 1500,约 826 字符),要求每张投递的图片都原样出现在回复正文里,写成 markdown,而不是只放在 display_image 工具行里。工具行默认折叠,用户滚动对话记录时什么都看不到,自然会认为没发图片。六条硬性条款加一次发送前自检:(1) 把返回的 markdown 回显到正文;(2) 表格 / 摘要 / 文件列表不算图片;(3) 每张图片单独一段,不许写「见上文」或「已发送 N 张图片」;(4) 路径或裸链接不算图片;(5) 引用式配对 ![图1][1] + [1]: C:/… 必须一起粘贴;(6) 编号由插件掌管——后续批次用 start=<last+1>;最后 (7) 数一数自己回复里 ![ 出现的次数,必须等于你声称的图片数量。用 config.injectImageLaw: false 关闭。

安装

方式 1:npm(推荐)

dsh plugin --profile desktop add dsh-chat-image-lightbox

方式 2:手动

1. 把 lib/ 文件夹和 cordis.patch.yml 复制到 DSH 配置档的 node_modules/dsh-chat-image-lightbox/
2. 把 dsh-chat-image-lightbox 加入配置档 package.json 的 dsh.profile.bundles 数组
3. 重启 DSH Desktop

用法

1. 把图片放进 ~/.dsh/uploads/(或 DSH 实例可访问的任意目录)
2. 调用 display_image 工具,把它返回的 markdown 直接粘贴到回复正文里(这一步不能省——工具行是折叠的):
![图1][2ab1]

[2ab1]: C:/Users/me/pic.jpg

这才是 1.8.x 的真实形态:引用式 + 绝对本地路径,Host 会把它改写成 api/file?path=…。
3. 图片会内联显示,并带灯箱增强

⚠️ 不要手工拼 /images/?abs=…。 那是 1.7.x 的旧形态。在 Desktop 上(页面 origin 为 dsh-app://app),只要目标地址含 ?,parseFileLink 就返回 undefined;localPathMediaUrl 又会用
split(/[?#]/) 剥掉 ?abs=,剩下 dsh-app://app/api/file?path=%2Fimages%2F——一个目录——它返回
FS_NOT_REGULAR_FILE + 403,渲染成「failed · filename」。GitHub 上 1.7.9 发布的正文构建器用的是正是这种形态,所以它在 Desktop 上什么都不渲染 —— 不要把它当成参考实现。

⚠️ 也绝不要用 http://127.0.0.1:<port>/images/...。 DSH Desktop 在自定义协议 dsh-app://app 里渲染 GUI,所以回环 http:// 源是另一个源:Chromium 在渲染进程里就把它拦下了,请求根本到不了 web 服务器(fetch 抛 TypeError: Failed to fetch, 触发 onerror)。

注意:要让下载按钮弹出另存为对话框,图片必须和你的 DSH 实例同源提供(例如走 /images/ 路由)。跨源图片会改为在新标签页打开。

排查图片显示问题(重要)

图片显示为空白、显示成一堆文本,或者灯箱打开是空的,按下面顺序检查:

① 图片必须用 http(s) URL 或根相对路径 —— base64/data-URI 和附件都不会渲染

DSH 把图片渲染进 ,来源是 http(s) URL 或 markdown 里写的绝对本地路径。它不会内联渲染 data:image/png;base64,...,也不会渲染「文件投递/附件」类消息(那些会显示成工具调用文本,不是图片)。

- ✅ 正确 —— 原样照抄 display_image 的输出(引用式 + 绝对本地路径;Host 会把它改写成 api/file?path=…):
![图1][2ab1]

[2ab1]: C:/Users/me/pic.jpg

- ✅ 正确 —— 任何没有防盗链的外部图片: Description

- ❌ 不会渲染 —— 回环绝对 URL(在 dsh-app://app 下被跨源拦截): Description

- ❌ 不会渲染 —— base64 内联: Description

② 图片空白 / 灯箱为空 = 图床开了防盗链(源头限制,不是本插件的问题)

有些图床(例如 新浪微博 sinaimg、部分 CDN)会校验 Referer:请求没带预期的 Referer 就返回 403,于是图片框渲染出来了,但一个像素都没加载(点开还是能进灯箱,只是里面没有图)。

- 任何普通网页都没法直接盗链这些图(微博以外的站点也一样)—— 这不是插件能修好的。
- 绕行办法:把图片下载到 ~/.dsh/uploads/(带上正确的 Referer 去抓,或者干脆从浏览器里另存),再通过 /images/ 提供。

示例 —— 抓一张带防盗链的图片(Windows):
curl -e "https://weibo.com" -o image.jpg "https://wx1.sinaimg.cn/.../xxx.jpg"

③ 支持的图片格式

| 格式 | 是否显示 | 说明 |
| jpg / jpeg / png / gif / webp / bmp / svg / ico / avif | ✅ 直接显示 | 主流格式 —— 显示和灯箱都正常,字节原样不动 |
| tiff / tif | ✅ 服务端转换后显示 | 浏览器渲染不了 TIFF;插件服务端按原始分辨率把它转成 WebP(内置 sharp)—— 高清显示,下载拿到的仍是原始 .tif |
| heic | ✅ 通过服务端转换 | 与 TIFF 相同——显示为 WebP,下载为原始 .heic |

文件放在 ~/.dsh/uploads/,通过 /images/ 显示——无需手动转换。

④ 改动后插件不工作——彻底重启

安装或编辑插件文件后,完全退出所有 DSH Desktop 进程(任务管理器 → 结束每一个 DSH Desktop.exe),再重新打开。只关闭窗口不算退出(后台进程仍在)。否则旧代码或未加载的代码会让插件保持不激活。

⑤ 到底是不是插件的问题?快速检查清单

- 用 /images/ 提供的上传文件测试——如果能显示并能灯箱查看,插件没问题。
- 用非热链图床的图片测试(例如 https://picsum.photos/800/600)——如果这张能用,插件是通用的。
- 如果上传文件和外部图片都不能灯箱查看 → 插件客户端没加载,通常是因为 DSH 没有完全重启,或者安装不完整。

⑥ 发送图片失败,报 session/agent-busy / ERR_DLOPEN_FAILED(1.7.5 已修复)

如果发送图片失败并报:

prompt rejected: Could not load the "sharp" module using the win32-x64 runtime
ERR_DLOPEN_FAILED: The specified procedure could not be found.
(session/agent-busy)

这是 1.7.4 及更早版本的已知问题,1.7.5 已修复。升级即可:

pnpm add dsh-chat-image-lightbox@latest

原因:DSH 内核自带 sharp ^0.35.3,而旧版插件把 sharp 锁定在 0.34.4——两个不同的 sharp 构建进了同一个进程。它们加载的 DLL 文件名相同(libvips-42.dll),但需要的配套文件不同(0.34.x 用 libvips-cpp-8.17.2.dll,0.35.x 用 libvips-cpp-8.18.3.dll)。Windows 按文件名解析 DLL,所以先加载的那个占用了名字,另一个就报「找不到指定的程序」。

末尾的 agent-busy 是内核给未分类错误套的通用包装——跟实际并发毫无关系,所以单看报错信息很难找到真正原因。

注意:这只影响发送图片;纯文本聊天从未受影响。

⑦ 装了插件还是看不到图片?按顺序检查

本节针对「刚装好插件,一切看起来正常,但助手发图片时消息体里没有图」。按顺序来——哪一步命中,问题就在哪。

1. 检查三行宿主启动日志(~/.dsh/logs/desktop-next.log):

| 日志行 | 缺失意味着什么 |
| image-gallery: serving from ... (sharp: available) | 插件根本没加载(见第 3 条) |
| image-gallery: image delivery law registered (order 1500, ... chars) | systemPrompt 服务不可用 → 插件警告 delivery law skipped。此时系统提示词里没有投递规则,模型很可能把图片留在工具行里,你看不到 |
| image-gallery: display_image tool registered | tools 服务或 defineTool 不可用 → 插件会警告 display_image tool skipped。没有工具可调用 |

2. (sharp: unavailable) 不是「没有图片」的原因。 它只影响 TIFF/HEIC(这两种格式会显示为「图片无法预览」);JPG/PNG/WEBP 不受影响。根本原因:用 junction 方式安装后,Node 从插件的真实路径解析,找不到 profiles/<name>/node_modules(sharp 装在这里——那是另一条分支,不是祖先目录)。插件已经按多个根目录做兜底解析。

3. 确认插件真的挂载上了——三步只做两步不够。
- <profile>/package.json 在 dependencies 里列出该包(本地安装写 "link:<absolute plugin path>");
- 同一个文件在 dsh.profile.bundles 里列出包名;
- <profile>/node_modules/<package name> 存在(junction 或真实目录)。

只做前两步,宿主会静默跳过这个包:没有警告,没有报错,插件表现得像不存在一样。用 Test-Path <profile>/node_modules/dsh-chat-image-lightbox 检查。

4. After changing client code, fully exit the process before restarting. With closeToTray: true, clicking the window's × only hides it to the system tray — the process keeps running, and host plugins load at boot with no hot reload, so this is not a restart. Ctrl+R does not reload client bundles either. Correct way: tray icon → right click → Quit, wait for the process to disappear, then start again.

5. Do not chase dsh.client.inject. A wrong package name there is silently ignored (the host simply does not schedule an inject entry it cannot resolve, and reports nothing); only external fails hard. So a bad inject entry cannot cause "no images".

6. A 403 Browser access is disabled from a plain browser or curl on /images/?abs=... is expected. The desktop app defaults to browserAccess: false and only admits requests carrying the Electron renderer token. Viewing images inside the desktop client is unaffected.

The most important point: if everything above is green and all three log lines are present, yet the assistant's message body still has only text — that is not a plugin fault, it is the model not following the image delivery law. DSH's host only auto-promotes results of the built-in read_image tool (source: imageCardModel contains if (call?.name !== "read_image") return null;); results from third-party tools always fold into the tool row, and the tool row is collapsed by default. So a third-party plugin can only deliver images by writing the markdown image into the reply body. That constraint is enforced by the system-prompt section the plugin registers — if that log line is missing, the constraint is not there.

Keyboard & Mouse

| Action | Input |
| Open lightbox | Click an image |
| Zoom in / out | Click image, or mouse wheel when zoomed |
| Pan | Drag the image while zoomed |
| Prev / next | ← / → keys, on-screen arrows, or swipe |
| Close | Esc, click backdrop, or ✕ |

HTTP API

| Endpoint | Method | Description |
| /images/<path> | GET | Serve an uploaded image (loopback-only, with extension/security guards) |
| /api/image-gallery/list | GET | List images under the gallery root; supports ?limit=N |
| /api/image-gallery/root | GET | Return the configured gallery root path |
| /api/image-gallery/reveal | POST | Reveal a file in the OS file manager (Explorer / Finder) |
| /api/image-gallery/diag | POST | Append a line to ~/.dsh/image-gallery-diag.log (client-side debug sink; no-op unless IG_DEBUG is on) |

How It Works

| Component | Description |
| Host (lib/index.js) | Registers /images/ file-serving route plus 4 /api/image-gallery/ exact routes (list, root, reveal, diag) on ctx.webServer |
| Client (lib/client.js) | Uses MutationObserver to watch for elements in the chat, adds a display class + click handlers that open a lightbox overlay (class-only enhancement, no DOM restructuring) |

Requirements

- DSH kernel — the package declares engines.dsh: ">=0.1.7-0 <0.3.0" (and the same range in peerDependencies). The upper bound is deliberate: peerDependencies is a runtime gate — the host checks it before importing the plugin and silently disables the row on mismatch, so an open-ended range would let a future breaking generation install "successfully" and then fail quietly. This release was verified end to end on DSH NEXT 2.0.19-next (kernel @deepseek-ai/dsh 0.2.1-rc.2). The client half relies on @deepseek-ai/dsh-client-ui-tool being injectable (measured 0.2.1-rc.2).
- Host must expose the webServer service (standard in DSH Desktop).
- Node.js ≥ 22
- sharp ^0.35.3 (declared as a dependency; it is what performs the lossless TIFF/HEIC transcoding). The range follows the DSH kernel's own ^0.35.3 — an exact pin here would put two different sharp builds into one process and break image sending (see Troubleshooting).
- Windows note: "show in folder" uses explorer /select,

License

MIT

DeepSeek Harness 插件:在对话框中直接显示图片,支持放大、下载(弹出另存为)、左右切换。

功能

- 内联显示:AI 回复中的图片直接在对话框渲染(通过 markdown 描述)
- 点击放大:点击任意图片打开全屏 lightbox;在原始比例下点图会放大到 1.6 倍,已放大时再点一下则关闭灯箱(放大的图会盖住背景,这样保证点得到关闭目标)。Esc 或点击背景同样可关闭。
- 滚轮缩放 + 拖拽平移:放大后可用鼠标滚轮继续缩放,按住拖拽平移查看细节
- 下载:点击下载按钮 ⬇ 保存图片(同源图片弹出"另存为"对话框;文件名会清洗,扩展名按图片类型自动补全)
- 切换:左右箭头按钮、键盘方向键,或在触屏上左右滑动切换多张图片
- 文件名:查看器底部显示当前图片的文件名/标题
- 无障碍:lightbox 是标准的 role="dialog"(含 aria-modal、按钮 aria-label 与焦点管理,关闭后焦点回到触发元素)
- 预取:自动预取相邻图片,切换更顺滑
- 关闭:点击遮罩层、按 Esc 或点 ✕ 关闭
- ZCode 式渲染:聊天图片原地加样式——绝不搬移、改父级或重排 DOM 节点,不与 DSH 的 React 虚拟列表打架(不再出现图片消失/重复、控制台 removeChild 报错)
- 限高不裁剪:每张聊天图保持原始纵横比,高度封顶 min(460px, 64vh)(ZCode 的观感);图表、流程图不再被裁得没法看
- TIFF/HEIC 直显:浏览器原生不渲染 TIFF——插件服务端用内置 sharp 将 TIFF/HEIC 按原始分辨率即时转 WebP 显示(本地图表导出照样高清直显),下载按钮保存的仍是原始 .tif/.heic 文件
- 多图九宫格:一条消息里有多张图时自动排成整齐网格(约 3 列),文字段落横跨整行排在网格上方——纯 CSS(display:contents),不搬任何节点。1.9.1 起:只有「容器里全是图片」才会走网格,混合图文(图片 + 表格/散文)一律不进网格,不再出现「自动变 4 列 + 表格被塞进图片行之间」
- 同排瓦片严格齐平(1.9.3):承载缩略图的 会加 .dsh-ig-tileline 把 margin/padding/border/line-height/font-size/min-height 全部归零,让它不拥有行盒——修掉「第一张图比同排其他图高 20px」
- 点开即高清:点击任意图片打开 lightbox 看全分辨率大图,放大看到每个像素;下载永远保存原始文件
- 界面干净:lightbox 的工具按钮(下载/关闭)、左右切换箭头、计数、标题在 lightbox 关闭时全部隐藏,仅在实际看图时才显示,不会在对话界面留下按钮干扰布局
- 灯箱缩放/平移面板:手势能做的都给了一组显式按钮——⊖ ⊕ 1× 缩放(带实时百分比),加一组方向键移动视角(按住可连续移动);滚轮、拖拽、+/-/Shift+方向键 全部保留
- 会话内图片控制收进一个弹出面板:输入框旁只留一个紧凑的 「对话图显控制」 触发器,点它滑出面板,缩略图开关、隐藏开关、列数输入框都在里面——不再让一排按钮占着输入区
- 可调列数的缩略图网格:一键把整个会话的图片收成小图网格;面板里的 「缩略图显示列数」 输入框可设每行几张(1–12),输入的值会保存为默认值,下次启动仍用它
- 隐藏 / 展开:一键隐藏所有图片,并把它占的空间完全收掉(不会留下一大块空白);再点一下原样恢复
- 只管对话区的图:插件刻意忽略消息列表以外的图片——登录账号的头像和侧栏图标同样是 ,但一律不动(1.7.2 修复)
- 结构上安全:缩略图效果只用 class 和内联样式实现,绝不移动、删除或重挂任何节点。早期版本做过 reparent,结果让 DSH 的会话槽抛 DOMException、整段对话渲染成空白
- 自动增强:MutationObserver 自动增强新加入对话的图片;React 重渲染覆盖 class 后也会自动重新接管
- 把图带进对话(宿主侧 display_image 工具):一个工具吃三种输入——本地 path(文件或目录,TIFF/HEIC 自动转码、可缩放)、网络 url 直链、query 关键词搜图(Bing 优先,花瓣/搜狗次之,按抽样可加载率选源),返回可直接粘贴的 Markdown。本机任意文件可通过 /images/?abs=<路径> 原地直读(仅限本机回环),无需先复制进图库目录
- 防盗链自动降级(1.7.2):搜图结果可能返回 200 却在浏览器里显示不出来——部分 CDN(花瓣走腾讯云 EdgeOne)会拒绝 Referer 不是自家域名的请求,图片于是显示成「无法预览」。工具现在会按浏览器的方式逐张探测,只把会被拦的那几张转存到本地,能直连的保持直连,不浪费带宽
- 图源健康度门禁(1.8.1):过去把「接口有结果」当成「图源可用」——花瓣的搜索 API 一直正常返回 200 + 20 条,但它的图片链接全部被腾讯 EdgeOne 拦成 567,于是搜出来的 5 张图在对话里全是「图片无法预览」,而且因为第一个源「有结果」就 return,永远不会降级到别的源。现在每个图源会抽 4 张按浏览器的方式真探一次,可加载率低于 30% 就记下原因并自动换下一个源;所有源都不达标时退回可加载率最高者,并在 note 里如实写明「因图源不可用已换源/可加载率偏低」。图源顺序也回到实测可用者优先(Bing → 花瓣 → 搜狗;搜狗接口已对匿名请求回 forbid,会明确报「此图源暂不可用」而不是静默当成「没搜到」)
- 转存改 4 路并发(1.8.1):转存是逐张 await 的,10 张要等 18.4 秒。现在固定 4 路并发(顺序不错乱、单张失败不影响其他张),并跳过健康度采样时已经探过的图(每张少一次往返)
- 「探测失败」不再算「可加载」(1.8.3):健康度采样原先只区分「被拦」和「没被拦」,网络/TLS 失败(根本没探到)也被算成可加载——真搜一次 4 张里 2 张 fetch failed,note 却写「4 张可加载」,ratio 还是 1.0,于是门禁不换源、坏图照样发给用户。现在三种结果分开计数(可加载 / 被拦 / 探测失败),ratio 只由「确认取到 2xx 图片」的张数决定,note 与换源原因都如实分列(「N 张被站点拦截」vs「N 张探测失败(未验证)」)
- 失败必须说出来(1.8.1):转存失败原先被空 catch (e) {} 吞掉,用户只看到坏图、日志一片安静。现在失败原因记在 rescueError 上,并在 note 里写明「有 N 张被原站点拦截且无法转存」,让「发不出图」这件事至少是可见的
- 依赖装成 Junction 也不会失联(1.8.4):插件在三个 profile 里都是目录 Junction 指向 local-plugins。Node 解析 require 用的是模块真实路径,所以 createRequire(import.meta.url)("sharp") 会从 local-plugins/ 往上找,而 sharp/undici 都在 profiles/<name>/node_modules —— 直接被链接抹掉。后果是启动日志写 sharp: unavailable,所有 TIFF/HEIC 一律「图片无法预览」,而且 undici 也拿不到。现在用 resolveDep() 多根解析(模块路径 / process.execPath / EXEPATH / cwd,各自向上 8 层),三处依赖全部改走它
- 直连必须真直连(1.8.4):本机 NODE_USE_ENV_PROXY=1 时,Node 全局 fetch 会自己套用 HTTPS_PROXY —— 不传 dispatcher 并不等于不走代理。于是 duitang 这类只认直连的站点被抓成「探测失败」,好图被当坏图剔掉。现在直连用 node:https + 显式 Agent(完全无视 env 代理,自带 5 跳重定向),getBytes() 先试 runtime fetch、transport 失败再直连;HTTP 状态码视为答案、不换路重试
- 正文里的图不再指向浏览器解不了的原文件(1.8.5) —— 1.8.4 修好 sharp 之后,工具行的 TIFF 能显示了,但正文里的图仍然是「图片无法预览」:这两条路根本不走同一个 handler。正文里的 由宿主改写成 api/file?path=…,而 serveFile() 只按扩展名给 MIME、把原始字节直接流出去,一个字节都不转码,Chromium 没有 TIFF/HEIC 解码器,于是加载失败。现在交付前先用 sharp 转一份浏览器能直接渲染的副本(无损源出 PNG、其余出 WebP),正文指向这份副本、原文件仍保留在 localPath;转码后仍超过宿主单图上限(默认 20 MB,超了答 413 同样是「图片无法预览」)就按 0.7 递减宽度重转,最多 5 轮。顺带在交付前用 magic 字节把「后缀是图片、内容是错误页」的文件就地剔除,并在 note 里说明是「内容却不是图片(多半是图源返回的错误页)」而不是笼统说转码失败
- 候选池比要的多四倍,前几条坏了还有替补(1.8.8) 搜「喜羊羊」要 3 张,结果一张都给不出来——不是图源没有,是 Bing 把百度百科、B 站这几个防盗链站排在了最前面,前 3 条全是 HTTP 403,而旧代码只取 want 张、下载一张不成就算整查询失败。现在候选池取 want × 4(上限 40),分批下载(每批 8 张)、凑够要的张数就停手:既不会为 3 张图去下 40 张,也不会让前 3 条 403 直接毁掉整个查询。实测喜羊羊从「0 张 + 报错」变成「3 张 / 8 秒」,刘亦菲 12 张从 17.5 秒降到 7.9 秒。
- 被 403 挡住的图会换自己的 Referer 再试一次(1.8.8) 防盗链的判断标准是「这个请求像不像从我自己站点发出来的」,所以不存在一个对所有主机都正确的 Referer。实测 36 个真实候选:花瓣 Referer 取到 32 个、主机自身 Referer 取到 32 个、两个都试取到 33 个——i1.hdslb.com 只有用 B 站自己的 Referer 才能取到(403 → 200,1.5 MB),而 gd-hbimg.huaban.com 正好相反,用自身 Referer 会被腾讯 EdgeOne 拦成 567。现在遇到 403/401/567 会自动换一次同源 Referer 重试,仍失败才跳过,并在 note 里写明原因。
- 搜图不再只抽样 4 张就放行(1.8.7):健康门禁的「4/35 可加载」只说明图源整体质量,说明不了这一张能不能显示。现在每张候选在交付前都下载并校验 magic 字节,坏链(sinaimg 404 NoSuchKey)、307 跨域跳转(renwuji → static.xsite.com.cn)一律不发,并在 note 里报「N 张下载失败…原因示例:…」。
- 正文编号能续号(1.8.7):display_image 新增 start 参数。一次回复发多批图时第二批传「上一批最后编号+1」,正文编号从图1 连到图N,不再出现两组「图1」(用户原话:「这次为啥从序号3开始」)。
- 真 HEIC 之前从没成功过(1.8.6):本机唯一那个 .heic 样本其实是 JPEG(首字节 FFD8FF,后缀骗人),所以"HEIC 已验证"是假的;sharp 自带的 libheif 没编入 HEVC 解码器,读容器正常、一要像素就报 Support for this compression format has not been built in: HEVC。现在 Windows 下自动走系统 WIC 兜底解码(-EncodedCommand 传脚本,路径不进命令行),实测 1440x960 真 HEIC → 520x347 PNG 用时 532ms。新增 test/probe-heic-real.mjs(24 项),内置真 HEVC 素材并先断言素材本身不是错标 JPEG。
- 不信任 200 和 content-type,只信任字节(1.8.4) —— 一个「转存成功」的缓存文件实际是 CDN 的错误页(HTTP 200、content-type 写着 image/jpeg、正文是 ), handler 照旧标成 image/jpeg 发出去,浏览器解不开就是「图片无法预览」。现在下载、探测、发送三层都校验 magic 字节(JPEG/PNG/GIF/WEBP/TIFF/BMP/ISOBMFF),不是图片就明确报错,伪装文件返回 415 并说明原因。
- 文件名带 % 也能取到图(1.8.4) —— ?abs= 曾被解码两次,a%20b.jpg 变成 a b.jpg 得 404, 折扣50%的图.jpg 更是抛 URIError 把请求打死。现在优先用已解码的值,只有磁盘上不存在时才再解一次,转义非法时答 400。
- 自动转存目录不再无限增长(1.8.1):~/.dsh/image-gallery-auto 原先只写不删(实测已 129 个文件 / 187 MB)。现在每次转存前检查一次:文件数 > 500 或总量 > 512 MB 就从最旧的开始清理(单个文件删不掉也不中断整轮)
- 图片投递铁律(1.8.9):插件会在 system prompt 里常驻一条规则(image-gallery:delivery-law,order 1500,约 826 字符),要求模型把 display_image 返回的 markdown 原样复述进回复正文(),而不是只放在工具调用行里。因为工具行默认折叠,用户滚动正文时看不到折叠区内容,会合理认为「根本没发图」。六条硬约束 + 一条收尾自检:①必须复述;②表格/结论/清单都不算图;③每张图各自成段,禁止「见上文」「已发送 N 张」;④路径与普通链接不是图;⑤引用式 ![图1][1] 与 [1]: C:/… 必须成对出现;⑥编号由插件决定,多批传 start=<上一批末尾+1>;⑦发前自检:数一遍正文里 ![ 的出现次数,必须等于声称的张数。想关掉:在 profile 的 cordis patch 里给本插件加 config.injectImageLaw: false。

安装

方式一:npm(推荐)

dsh plugin --profile desktop add dsh-chat-image-lightbox

方式二:手动安装

1. 把 lib/ 文件夹和 cordis.patch.yml 复制到 DSH profile 的 node_modules/dsh-chat-image-lightbox/
2. 在 profile 的 package.json → dsh.profile.bundles 数组中添加 dsh-chat-image-lightbox
3. 重启 DSH Desktop

使用方法

1. 把图片放到 ~/.dsh/uploads/ 目录(或 DSH 实例提供的任意目录)
2. 直接调 display_image 工具,把它返回的 markdown 原样贴进回复正文(这一步不能省,工具行是折叠的):
![图1][2ab1]

[2ab1]: C:/Users/me/pic.jpg

这是 1.8.x 的真实形态:引用式 + 本机绝对路径,由宿主改写成 api/file?path=… 直出。
3. 图片会内联显示并自动带 lightbox 增强

⚠️ 不要自己拼 /images/?abs=…。那是 1.7.x 的旧形态,在桌面端(页面源 dsh-app://app)会被 parseFileLink(遇到 ? 直接返回 undefined)与 localPathMediaUrl(split(/[?#]/) 剥掉 ?abs=)联手打碎成 dsh-app://app/api/file?path=%2Fimages%2F —— 一个目录,/api/file 回 FS_NOT_REGULAR_FILE + 403,界面显示「失败 · 文件名」。
GitHub 上 1.7.9 的正文拼装就是这个写法,所以它在桌面端根本不出图,不要当参考实现。

⚠️ 也不要用 http://127.0.0.1:<端口>/images/...。 桌面端页面源是 dsh-app://app,http:// 回环对页面而言是另一个源,Chromium 在渲染进程里直接拦掉,请求根本到不了 webServer(fetch 抛 TypeError: Failed to fetch,触发 onerror,服务端命中 0)。

注意:下载按钮要弹出"另存为"对话框,图片必须从 DSH 同源路由提供(如 /images/ 路由)。跨域图片会在新标签页打开。

图片显示常见问题排查(重要)

遇到"图片显示不出来 / 点开是空白 / 只看到一串文字"时,按以下顺序排查:

① 图片必须用 http(s) 外链或站内根相对路径,不能依赖 base64 或附件

DSH 对话只对 http(s) 图片 URL 或 markdown 里的本机绝对路径渲染成 ,不支持 data:image/png;base64,... 内嵌,也不支持以"附件/文件交付"方式直接显示(那只会显示成工具调用文字)。

- ✅ 正确:原样粘贴 display_image 的输出(引用式 + 本机绝对路径,宿主会改写成 api/file?path=…)
![图1][2ab1]

[2ab1]: C:/Users/me/pic.jpg

- ✅ 正确:任意无防盗链的外链图片描述

- ❌ 错误:回环绝对地址(dsh-app://app 下跨源被拦,永远加载不出)描述

- ❌ 错误:base64 内嵌(DSH 渲染不出,只看到一串 iVBORw0...)描述 ← 不会显示

② 图片空白 / 点开灯箱没图案 = 图源防盗链(图床限制,非插件问题)

部分图床(如新浪微博 sinaimg、部分 CDN)强制 Referer 校验:请求不带特定 Referer 就返回 403,导致图片加载不出(有 框但内容空白,点击能弹灯箱但没图)。

- 这类图任何普通网页都无法直接外链显示(包括微博自己以外的所有站点),不是本插件能解决的。
- 解决办法:把图下载到本地 ~/.dsh/uploads/(可用带 Referer 的脚本抓取,或直接用浏览器保存),再通过 /images/ 路由显示。

示例:抓取带防盗链的图(Windows 下用带 Referer 的 curl 或脚本下载后放入 uploads):
curl -e "https://weibo.com" -o 图.jpg "https://wx1.sinaimg.cn/.../xxx.jpg"

③ 支持哪些图片格式

| 格式 | 显示 | 说明 |
| jpg / jpeg / png / gif / webp / bmp / svg / ico / avif | ✅ 直接显示 | 主流格式,直接显示 + 灯箱,字节原样不动 |
| tiff / tif | ✅ 服务端转码显示 | 浏览器不能渲染 TIFF,插件服务端用内置 sharp 按原始分辨率转 WebP 显示——高清直显,下载仍是原始 .tif 文件 |
| heic | ✅ 服务端转码显示 | 同 TIFF——显示为 WebP,下载保存原始 .heic |

图片放到 ~/.dsh/uploads/,用 /images/ 显示——无需手工转码。

④ 彻底重启后插件不生效

改过插件文件或刚安装后,必须彻底退出 DSH 进程再重开(任务管理器结束所有 DSH Desktop.exe 进程),否则旧代码/未加载状态不生效。仅"关闭窗口"不算退出(后台进程残留)。

⑤ 判断是不是插件问题的清单

- 用 /images/ 上传图测:如果上传图正常显示 + 灯箱,说明插件正常
- 换无防盗链图床(如 picsum.photos)的图测:正常则插件通用
- 若上传图、外链图都不亮灯箱 → 插件 client 未加载,多半是没彻底重启或没装正确

⑥ 发图片时提示 session/agent-busy / ERR_DLOPEN_FAILED(1.7.5 已修复)

如果发图片时报错:

prompt rejected: Could not load the "sharp" module using the win32-x64 runtime
ERR_DLOPEN_FAILED: The specified procedure could not be found.
(session/agent-busy)

这是 1.7.4 及更早版本的已知问题,1.7.5 已修复。升级即可:

pnpm add dsh-chat-image-lightbox@latest

为什么会这样:DSH 内核自带 sharp ^0.35.3,而旧版本把本插件的 sharp 精确锁定在 0.34.4,于是同一个进程里出现了两个不同版本的 sharp。它们加载的 DLL 同名(都叫 libvips-42.dll),但各自需要不同的配套文件(0.34.x 要 libvips-cpp-8.17.2.dll,0.35.x 要 libvips-cpp-8.18.3.dll)。Windows 按文件名解析 DLL——谁先加载谁占住,后来者就报"找不到指定的过程"。

报错末尾的 agent-busy(会话忙)是 DSH 对无法分类的错误套用的通用标签,与实际是否繁忙无关,因此很难一眼看出真因。

注:此问题只影响发图片,纯文字对话始终正常。

⑦ 装了插件还是看不到图?按这个顺序查(安装侧排查)

这一条专门给「刚装好插件、功能看起来正常、但让 AI 发图时正文里就是没有图」的情况。按顺序查,命中哪条就说明问题在哪:

1. 先看宿主启动日志里这三条是否齐全(~/.dsh/logs/desktop-next.log):

| 日志行 | 缺了说明什么 |
| image-gallery: serving from ... (sharp: available) | 插件没装载(看下面第 3 条) |
| image-gallery: image delivery law registered (order 1500, ... chars) | systemPrompt 服务不可用 → 插件会 warn delivery law skipped。这时铁律没进 system prompt,模型很可能只在工具行里出图,你就看不到 |
| image-gallery: display_image tool registered | tools 服务或 defineTool 不可用 → 插件会 warn display_image tool skipped。这时根本没有工具可调 |

2. (sharp: unavailable) 不是「看不到图」的原因。 它只影响 TIFF/HEIC(会显示成「图片无法预览」),JPG/PNG/WEBP 照常。根因是 junction 安装后 Node 从插件真实路径向上找不到 profiles/<name>/node_modules(sharp 在那里,是兄弟分支不是祖先);插件已用多根解析兜底。

3. 确认插件真的挂上了(只做前两件等于没挂)。 三件事必须同时成立:
- <profile>/package.json 的 dependencies 里有这个包(本地安装写 "link:<插件绝对路径>");
- 同文件 dsh.profile.bundles 数组里有包名;
- <profile>/node_modules/<包名> 存在(Junction 或实体目录)。

只有前两件、没有第三件时,宿主会静默跳过,日志里既不 warn 也不 error,插件像不存在。判别:Test-Path <profile>/node_modules/dsh-chat-image-lightbox。

4. 改了客户端代码必须「完全退出进程」再启动。 closeToTray: true 时点窗口右上角 × 只是收进系统托盘,进程并不退出,宿主插件是 boot 装载不热重载 → 等于没重启。Ctrl+R 刷新页面也不重载客户端 bundle。正解:托盘图标右键 → 退出,等进程消失后再启动。

5. 不要往 dsh.client.inject 方向排查。 里面写错包名是静默无效的(宿主对 inject 里查不到的包只是不排队,不报错);只有 external 写错才会硬失败。所以 inject 写错不会导致「不显示」。

6. 用浏览器或 curl 直接访问 /images/?abs=... 拿到 403 Browser access is disabled,这是预期行为。 桌面端默认 browserAccess: false,只放行带 Electron 渲染器令牌的请求。你在桌面客户端界面里看图不受影响。

最重要的一条:如果以上全绿、日志三条齐全,但 AI 发图时正文里仍然只有文字——那不是插件故障,是 AI 没遵守「图片投递铁律」。DSH 宿主只给内置的 read_image 工具自动出图(源码里 imageCardModel 有 if (call?.name !== "read_image") return null; 的白名单),第三方工具的结果一律被折进工具行,而工具行默认是收起的。所以第三方插件发图,只能靠把 markdown 图片写进回复正文。这条约束由插件注册的 system prompt 段强制执行;若那条日志缺失,约束就不在。

键盘与鼠标

| 操作 | 输入 |
| 打开 lightbox | 点击图片 |
| 放大 / 缩小 | 点击图片,或放大后滚动滚轮 |
| 平移 | 放大后按住拖拽 |
| 上一张 / 下一张 | ← / → 方向键、屏幕箭头、或滑动 |
| 关闭 | Esc、点击遮罩层、或 ✕ |

HTTP 接口

| 接口 | 方法 | 说明 |
| /images/<path> | GET | 提供上传图片(仅本机回环,带扩展名/安全校验) |
| /api/image-gallery/list | GET | 列出图库根目录下图片,支持 ?limit=N |
| /api/image-gallery/root | GET | 返回配置的图库根目录路径 |
| /api/image-gallery/reveal | POST | 在系统文件管理器(资源管理器 / Finder)中定位并选中某个文件 |
| /api/image-gallery/diag | POST | 向 ~/.dsh/image-gallery-diag.log 追加一行(客户端调试通道;IG_DEBUG 关闭时无实际写入) |

工作原理

| 组件 | 说明 |
| Host (lib/index.js) | 在 ctx.webServer 上注册 /images/ 文件服务路由 + 4 条 /api/image-gallery/ 精确路由(list、root、reveal、diag) |
| Client (lib/client.js) | 用 MutationObserver 监听对话中的 元素,添加展示样式与点击处理器打开 lightbox(只加 class,不改 DOM 结构) |

环境要求

- DSH 内核 —— 本包声明 engines.dsh: ">=0.1.7-0 <0.3.0"(peerDependencies 同区间)。上界是刻意的:peerDependencies 是运行时门禁——宿主在 import 插件之前校验,不匹配就直接禁用该行且模块永不导入,所以开区间上界会让将来某个破坏性世代「装上看似正常、然后静默失效」。本版实测验证于 DSH NEXT 2.0.19-next(内核 @deepseek-ai/dsh 0.2.1-rc.2)。浏览器端依赖 @deepseek-ai/dsh-client-ui-tool 可被注入(实测 0.2.1-rc.2)。
- 宿主需提供 webServer 服务(DSH 桌面端标准配置)。
- Node.js ≥ 22
- sharp ^0.35.3(已声明为依赖,TIFF/HEIC 的无损转码由它完成)。采用区间跟随 DSH 内核自身的 ^0.35.3——若在此精确锁版,会让两个不同 sharp 版本共存于同一进程,导致发图失败(见「图片显示常见问题排查」)。
- Windows 说明:「在文件夹中显示」使用 explorer /select,

许可证

MIT
预览分享卡片长什么样 ↓loyalchiiina/dsh-chat-image-lightbox
分享 · DeepSeek Harness Hub
loyalchiiina/dsh-chat-image-lightbox

DSH 插件(DSH >= 2.0.14):全屏聊天图片灯箱(缩放、平移、下载、翻页),会话级缩略图网格可调列数,隐藏或显示不留空白,TIFF/HEIC 按原生分辨率无损显示,防盗链自动降级,复制路径与在文件夹中显示。

回到顶部