# dsh-image-preview
> [English](./README.md)
**dsh(DeepSeek Harness)会话区图片预览插件。** 当模型执行 `read_image` 工具时,结果默认以小图缩略展示,不再显示为原始 JSON;点击缩略图以与 dsh 自带 `ImageLightbox` 一致的样式全屏查看原图。
## 功能
| | |
| --- | --- |
| 🖼️ **默认小图** | `read_image` 结果以约 240px 圆角缩略图展示,不撑爆会话流 |
| 🔍 **点击看大图** | 点击缩略图弹出原图预览 — `Esc` / 点击遮罩 / 关闭按钮均可关闭,关闭后焦点还原到触发元素 |
| ↔️ **跨行导航** | 左右按钮 / ← → 键可切换**本会话已加载的全部图片**(计数器显示 `3 / 4`)—— dsh 自带 lightbox 只能看单张 |
| 🔎 **缩放 / 旋转 / 拖动** | 工具条含缩小 / 放大 / 左转 / 右转 / 下载。滚轮可缩放,放大后可拖动平移,快捷键 `+` `-` `0` `,` `.`。缩放限制在 25%–800%,切图自动复位 |
| ⬇️ **下载原图** | 按附件原始文件名,经同源 `` 保存原图(对宿主加载器返回的 `blob:` URL 同样有效) |
| 🔐 **复用官方图片通路** | 图片经 dsh 注入到 slot owner 的会话鉴权加载器 `loadImage` 解析,不再自己手写附件 RPC |
| ⏱️ **状态完整** | 运行中(`row.running`)、失败(可点击重试 `image.loadFailed`)、加载中(`image.loading`)都有对应展示 |
| 🌗 **主题自适应** | 颜色全部引用 dsh 主题 token(`--dsw-*`),随 light/dark 主题自动切换 |
### Lightbox 操作
| 操作 | 控件 | 快捷键 |
| --- | --- | --- |
| 放大 / 缩小 | 工具条 `+` / `−`,或滚轮 | `+` / `-` |
| 复位视图 | 点击缩放百分比 | `0` |
| 旋转 | 工具条 ↺ / ↻ | `,` / `.` |
| 拖动平移 | 拖拽(仅放大后) | — |
| 下载 | 工具条 ⤓ | — |
| 上一张 / 下一张 | 两侧箭头 | `←` / `→` |
| 关闭 | ✕ / 遮罩 / `Esc` | `Esc` |
## 兼容性
针对 **dsh `0.1.5-rc.2`** 构建并验证。
> **为什么必须用低 priority 接管。** dsh 现已**自带** `read_image` 的 toolview。`tool.call.toolview` 是 **keyed** slot,其 registry 会拒绝「同 key 同 priority」的第二次注册:
>
> ```
> keyed slot "tool.call.toolview" already has an entry for key "read_image"
> at priority 0 — register at a different priority to shadow it (lowest renders)
> ```
>
> 因此本插件以 **`priority: -1`** 注册来接管家自带的这一行。少了它,`apply()` 会在浏览器启动阶段抛异常,**一行都渲染不出来** —— 这正是修复前的实际状况。
## 效果预览
**会话中的缩略图** — 每个 `read_image` 工具行默认显示小缩略图:

**大图 Lightbox** — 点击任意缩略图打开原图,支持 ← → 按钮或键盘左右箭头在对话中所有图片间跨行切换(底部计数器显示 `3 / 4`):

## 工作原理
本插件是**纯浏览器端 client 插件**,以 `priority: -1` 注册 `tool.call.toolview` 的 keyed slot(`key: "read_image"`),接管 `read_image` 工具行的渲染:
```
模型执行 read_image
└─ 工具结果 content: [{ text 信封 }, { type: "image", attachment }]
└─ tool-call 节点 → tool.call.toolview (key = read_image, priority -1 遮蔽官方行)
└─ ReadImageRow:小图缩略图 + 点击打开跨行 Lightbox
```
- `sessionId` 与 `t` 由会话级 slot 的 standard kit 注入(`dsh-client-ui-session` / locale seat)。
- `loadImage` 是 dsh 通过 slot owner props 注入的会话鉴权加载器(`MessageImageLoader`,即 `uiConversation.imageUrl(sessionId, attachment)`,按会话缓存)。插件**不再**直接访问 `/api/session.attachment`。
- 附件元数据(`attachmentId` / `mediaType` / `width` / `height`)直接从工具结果块获取。
- Lightbox 视觉与 dsh 自带 `ImageLightbox` 一致(portal 到 `body`、遮罩背景、ESC/遮罩/关闭按钮关闭、焦点还原),但额外提供跨图导航与缩放 / 旋转 / 平移 / 下载。
- 缩放与旋转只是 `
` 上的一个 CSS `transform`(不重新编码);下载把加载器给出的 `blob:` URL 直接交给 ``,不复制也不重新拉取字节。
- 图片注册表按 `sessionId` 分桶:同时打开多个会话时不会互相串图导航。
## 安装
### 从 GitHub 安装(推荐)
需要 pnpm ≥ 9。使用官方插件命令安装到你的 web profile:
```sh
dsh plugin --profile web add 'github:algerkong/dsh-image-preview'
```
或手动在 `~/.dsh/profiles/web/package.json` 中添加:
```json
{
"dependencies": {
"@alger-ai/dsh-image-preview": "github:algerkong/dsh-image-preview"
},
"dsh": {
"profile": {
"bundles": [ "@alger-ai/dsh-image-preview" ]
}
}
}
```
然后在 profile 目录执行 `pnpm install`,**重启 `dsh web`**,最后刷新页面。
### 本地开发
直接 link 本地目录:
```sh
cd ~/.dsh/profiles/web
pnpm add link:/path/to/dsh-image-preview
```
在 `dsh.profile.bundles` 中添加 `"@alger-ai/dsh-image-preview"`,重启 `dsh web` 并刷新。
## 使用
1. 开始一个会话,让模型对图片文件执行 `read_image`。
2. 工具行显示小缩略图而非原始 JSON。
3. 点击缩略图即可在 lightbox 中全屏查看原图。
## 开发
```sh
node verify.mjs # 自检:bundle 结构、slot 接管契约、运行时渲染
```
`verify.mjs` 校验三层:`__ModuleLoader__.load` 自注册形状、**keyed slot 接管契约**(复刻 dsh registry 规则;若插件忘了 `priority: -1` 会直接失败)、以及一次真实渲染(loadImage 通路、缩略图、lightbox、Esc、多图网格与导航,外加缩放 / 旋转 / 下载工具条)。
## 文件结构
```
dsh-image-preview/
├── package.json # bundle 声明(dsh.bundle.patch / dsh.client platform=web)
├── cordis.patch.yml # 注册 client 入口(id: ui-image-preview)
├── lib/
│ ├── index.js # 宿主端空入口(纯浏览器端插件)
│ └── client.js # 浏览器端:read_image toolview + 缩略图 + 原图预览
└── verify.mjs # 自检脚本
```
## License
MIT