dsh-plugin-image-tools 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +128 -0
- package/README.en.md +250 -0
- package/README.md +30 -27
- package/package.json +12 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.6.4] - 2026-08-29
|
|
4
|
+
|
|
5
|
+
- 文档:「实现要点」整节下移到「模型用法示例」之后(访客先看价值再看原理);
|
|
6
|
+
- 元数据:npm description 补齐第三个工具 save_received_images,keywords 12 → 19;
|
|
7
|
+
- 新增英文版 README.en.md 与双语切换行;CHANGELOG.md 纳入 npm files。
|
|
8
|
+
## [0.6.3] - 2026-08-25
|
|
9
|
+
|
|
10
|
+
- 相关插件段新增 dsh-plugin-windows-guard(Windows 环境防坑守则 skill 插件,互相引流)。
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [0.6.2] - 2026-08-21
|
|
14
|
+
|
|
15
|
+
- 修复:**历史消息里的图片"见过就消失"**——图片路由(选择卡 pick / 回复内嵌
|
|
16
|
+
show / 附件回显 attachment)此前只下发 `Cache-Control: private, max-age=300`
|
|
17
|
+
(5 分钟),且图片字节仅存服务端内存注册表(30 分钟 TTL / 重启即失效)。当
|
|
18
|
+
源文件在文件夹里被删除/覆盖、或注册表条目过期后,浏览器刷新/回看历史消息
|
|
19
|
+
会重新向服务端请求图片 → 404,图片加载不出来。
|
|
20
|
+
现在三个路由统一改为 `Cache-Control: private, max-age=2592000, immutable`
|
|
21
|
+
(30 天)。这些 URL 是内容寻址的(pickId/showId 为 UUID、attachmentId 为内容
|
|
22
|
+
哈希),同一 URL 的字节永不变,下发 immutable 长缓存完全安全:图片一旦在
|
|
23
|
+
浏览器里加载成功,字节就留在浏览器本地缓存,此后源文件消失、TTL 清理、
|
|
24
|
+
进程重启都不影响——浏览器直接命中缓存,不再向服务端发请求。
|
|
25
|
+
- 测试:smoke-server 增加三处路由的 `cache-control` 长缓存断言。
|
|
26
|
+
|
|
27
|
+
## [0.6.1] - 2026-08-19
|
|
28
|
+
|
|
29
|
+
- README:新增「相关插件」互相引用段(列出同系列已发布插件,npm / GitHub 链接 + 一句话说明),互相引流。
|
|
30
|
+
- 修复:**放大后拖拽看不到图片右侧/下侧**——此前放大层的基准尺寸(`base`)在
|
|
31
|
+
打开时用 `getBoundingClientRect` 一次性实测,若在图片加载完成前缩放(或之后
|
|
32
|
+
视口变化,如 Ctrl+滚轮页面缩放),基准就失效,平移被钳死在 0,拖不到图片右半
|
|
33
|
+
部分。现改为**从图片自然尺寸 + 当前视口实时推导基准**(新增纯函数
|
|
34
|
+
`fitBaseSize`,与 `.dshpick-lightboxFigure img` 的 CSS 约束一致),每次缩放/
|
|
35
|
+
平移/视口变化(监听 `resize`)都重新钳制,左右/上下边缘均可拖到;图片加载前
|
|
36
|
+
不做任何缩放变换,无闪位。
|
|
37
|
+
- 测试:冒烟测试更新 `clampPan`(新基准形状 `{w0,h0}`)并新增 `fitBaseSize`
|
|
38
|
+
用例(宽图/高图/小于约束/小视口)。
|
|
39
|
+
|
|
40
|
+
## [0.6.0] - 2026-08-19
|
|
41
|
+
|
|
42
|
+
- 新功能:**放大层内图片缩放**——聊天栏图片点击放大后,可用**滚轮缩放图片**
|
|
43
|
+
(普通滚轮即可,光标中心缩放)、底部的 −/+/重置 按钮、**双击**在 1x 与 2.5x 间
|
|
44
|
+
切换、放大后**拖拽平移**、快捷键 `+`/`-`/`0`/`Esc`。
|
|
45
|
+
命令式放大层(聊天图片)与 React 选择卡 Lightbox(ask_user_choice 选项图)
|
|
46
|
+
共用同一套缩放参数与样式。
|
|
47
|
+
- 说明:`Ctrl+滚轮` 是**浏览器级页面缩放**(Chrome/Edge/Firefox 在浏览器进程处理,
|
|
48
|
+
网页无法用 preventDefault 拦截);事件到达页面时这里也会同步缩放图片并尽力
|
|
49
|
+
阻止,但页面缩放本身拦不住——放大图片请用普通滚轮或 + 按钮。
|
|
50
|
+
- 实现:新增纯函数 `clampZoom` / `wheelZoomFactor` / `zoomTranslate`(光标中心缩放)/
|
|
51
|
+
`clampPan`(平移钳制:小于视口回中、超出视口限位保证图像覆盖视口);放大层
|
|
52
|
+
遮罩加 `overflow:hidden`、控制条与抓取光标样式。
|
|
53
|
+
- 测试:客户端冒烟测试新增缩放纯函数用例(范围钳制、滚轮方向、光标中心不动性、
|
|
54
|
+
平移钳制边界)。
|
|
55
|
+
|
|
56
|
+
## [0.5.0] - 2026-08-19
|
|
57
|
+
|
|
58
|
+
- 新功能:**聊天栏所有图片点击放大**——不止插件自己的图片,模型回复 markdown
|
|
59
|
+
渲染出的任意 http(s) 图片(聊天消息列 `[data-chat-flow]` 内的内容图)现在都
|
|
60
|
+
支持点击放大查看(复用 Lightbox 样式,Esc / 点遮罩 / 关闭按钮退出)。
|
|
61
|
+
判定规则:插件图片(show/attachment 路由)无条件放大;其他图片要求位于聊天栏、
|
|
62
|
+
不在按钮/链接等交互控件内(附件缩略图等保留各自原生 lightbox)、且达到内容图
|
|
63
|
+
尺寸阈值(渲染宽 ≥ 40px 或自然宽 ≥ 160px,跳过图标/头像)。
|
|
64
|
+
- 修复:`show_images` 回复内嵌图片此前**无法放大**——markdown 里是绝对 URL
|
|
65
|
+
(`http://host:port/dsh-plugin-image-tools/show/...`),而增强器观察器用
|
|
66
|
+
`img[src^="/dsh-plugin-image-tools/show/"]` 前缀匹配,绝对 URL 永远扫不到,
|
|
67
|
+
圆角样式与点击放大都不生效。现改为按内容包含匹配(`img[src*="..."]`),
|
|
68
|
+
并新增 document 级点击委托作为统一放大入口(不再逐图挂监听)。
|
|
69
|
+
- 增强:盲模型收图回显、选择卡、插件内嵌图片的放大行为保持不变(委托统一处理,
|
|
70
|
+
选择卡内图片仍由卡片自己的放大逻辑负责)。
|
|
71
|
+
- 测试:客户端冒烟测试新增 `isPluginImageSrc` / `isZoomableChatImage` 纯函数用例
|
|
72
|
+
(聊天栏内外、小图标、按钮内、选择卡内、插件图无条件放大等)。
|
|
73
|
+
|
|
74
|
+
## [0.4.1] - 2026-08-19
|
|
75
|
+
|
|
76
|
+
- 修复:`ask_user_choice` 对必填/类型字段做服务端校验(与浏览器端
|
|
77
|
+
`muxFrameSchema` 的 `askUserQuestionItemSchema` 对齐)。此前模型偶发漏传
|
|
78
|
+
`question`(或 `id`/`label` 等)字段时,服务端原样转发,浏览器 zod 解析把
|
|
79
|
+
`question/requested` 整帧丢弃,选择卡静默不渲染——用户只见模型文字而看不到
|
|
80
|
+
图片卡,最终只能手动停止(`ASK_ABORTED`)。现在缺失/类型错误会在服务端抛
|
|
81
|
+
面向模型的清晰错误,模型自我修正后重新调用即可。
|
|
82
|
+
- 修复:选择卡图片注册表按**原始选项下标**存图(此前是只含带图选项的紧凑数组)。
|
|
83
|
+
当带图选项下标不从 0 开始连续(如图片选项前面有文字选项)时,客户端
|
|
84
|
+
`/pickId/<原始下标>` 的图片 URL 会错位或 404;现在一一对应。
|
|
85
|
+
- 测试:冒烟测试扩展覆盖必填校验与下标对应。
|
|
86
|
+
|
|
87
|
+
## [0.4.0] - 2026-08-17
|
|
88
|
+
|
|
89
|
+
- 新功能:**盲模型收图**(无视觉输入模型接收用户图片,作为文件处理):
|
|
90
|
+
- `agent/pre-step` 监听器把消息里的 image 内容块重写为文本占位符
|
|
91
|
+
`📷 用户发来的图片 dshimg:<attachmentId>`——文本-only 适配器不再抛
|
|
92
|
+
`UNSUPPORTED_CONTENT`,回合正常进行;
|
|
93
|
+
- `ctx.llm.resolveModelInfo` 能力补丁(保留原方法,`inputModalities` 补 `image`)——
|
|
94
|
+
放行 apiproxy 的 prompt 入队校验(`MODEL_DOES_NOT_SUPPORT_IMAGES`);卸载时还原;
|
|
95
|
+
- 新工具 `save_received_images`:按 attachmentId 经 `ctx.attachments.readImage`
|
|
96
|
+
取回附件字节并落盘工作区(默认 `received/`),返回路径与元数据;
|
|
97
|
+
- 新路由 `/dsh-plugin-image-tools/attachment/<attachmentId>`:用户气泡回显图片;
|
|
98
|
+
- 客户端增强器识别 `dshimg:` 占位符,在用户气泡里替换为可放大的图片。
|
|
99
|
+
- 效果图换为真实 Web GUI 截图(用户提供),删除程序化合成脚本。
|
|
100
|
+
- 冒烟测试扩展:pre-step 重写、附件路由、save_received_images 落盘、llm 补丁。
|
|
101
|
+
|
|
102
|
+
## [0.3.1] - 2026-08-17
|
|
103
|
+
|
|
104
|
+
- 修复:选择卡与消息列对齐——CSS 变量加缺省回退(防止主题变量缺失时卡片全宽贴左)、
|
|
105
|
+
标题/说明/图片网格/选项行统一 24px 左缩进、顶部留白加大。
|
|
106
|
+
- 修复:回复内嵌图片加载失败时的降级样式。
|
|
107
|
+
- 文档:README 效果图按修复后布局重新合成(docs/mockup-*.png),docs 纳入发布内容。
|
|
108
|
+
|
|
109
|
+
## [0.3.0] - 2026-08-17
|
|
110
|
+
|
|
111
|
+
- 更名:`dsh-plugin-pickimages` → `dsh-plugin-image-tools`(目录/包名/bundle id/
|
|
112
|
+
路由前缀 `/dsh-plugin-image-tools`/客户端模块 id 全量同步,novel 插件引用一并更新)。
|
|
113
|
+
- 新功能:`show_images` 工具——在回复正文中展示图片(图片与文字混排),
|
|
114
|
+
返回绝对 URL 的 markdown 片段,客户端对 `/dsh-plugin-image-tools/show/` 图片做
|
|
115
|
+
渐进增强(圆角样式、悬停说明、点击放大查看、加载失败降级)。
|
|
116
|
+
- 图片注册表拆分为 picks(选择卡,回答即释放)与 shows(回复内嵌,TTL 清理);
|
|
117
|
+
路由支持 `/<pickId>/<index>` 与 `/show/<showId>/<index>` 两种形态。
|
|
118
|
+
- 冒烟测试扩展:show_images 全链路(注册→markdown→路由出图→存活)、originOf/safeAlt 纯函数。
|
|
119
|
+
|
|
120
|
+
## [0.2.0] - 2026-08-17
|
|
121
|
+
|
|
122
|
+
- 图片选择卡新增放大查看(Lightbox):点击缩略图或放大镜弹出大图,Esc/点遮罩/关闭按钮退出。
|
|
123
|
+
- 图片/文字/图文混排选项、多题分页、多选、自定义答案、推荐标注等交互完善。
|
|
124
|
+
|
|
125
|
+
## [0.1.0] - 2026-08-17
|
|
126
|
+
|
|
127
|
+
- 首个版本(`dsh-plugin-pickimages`):`ask_user_choice` 图片/图文混合选项工具,
|
|
128
|
+
detail 不可见标记 + 内存图片注册表 + 自定义图片路由,纯插件实现不改核心包。
|
package/README.en.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
[中文](./README.md) | **English**
|
|
2
|
+
|
|
3
|
+
# dsh-plugin-image-tools
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
**The only plugin in the dsh market supporting image choice cards**: it adds image
|
|
10
|
+
capabilities to the DeepSeek Harness Web GUI, with three tools covering three scenarios —
|
|
11
|
+
the model has you pick images among options, displays images in the reply body, and you
|
|
12
|
+
send images to a blind model. All rendered locally with zero tokens:
|
|
13
|
+
|
|
14
|
+
| Tool | Scenario | Effect |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `ask_user_choice` | Model asks the user to pick an image among **options** | The Web GUI renders image choice cards, zoomable; the answer protocol is identical to the native one |
|
|
17
|
+
| `show_images` | Model displays images in the **reply body** | Images render mixed with text in the chat; click to zoom |
|
|
18
|
+
| `save_received_images` | User **sends images to a blind model** (an adapter without vision input) | Images are saved as workspace files the model can download/analyze |
|
|
19
|
+
|
|
20
|
+
Three image sources are uniformly supported: **local paths** (relative to the session
|
|
21
|
+
workspace or absolute, including ComfyUI output), **http(s) URLs** (fetched and re-served
|
|
22
|
+
by the server), and **base64 data URIs**. Pure plugin implementation — no core package changes.
|
|
23
|
+
|
|
24
|
+
## Screenshots
|
|
25
|
+
|
|
26
|
+
### Image choice card (ask_user_choice)
|
|
27
|
+
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
The model asks you to "pick a cover"; each option carries an image, and you choose by
|
|
31
|
+
clicking the card (or the magnifier).
|
|
32
|
+
|
|
33
|
+
### Inline images in replies (show_images)
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
The model calls `show_images` in its reply and pastes the returned markdown snippets into
|
|
38
|
+
the body, so the images appear alongside the text.
|
|
39
|
+
|
|
40
|
+
> Both screenshots are real Web GUI captures (maid-atelier skin).
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- **`ask_user_choice`** (image / mixed image+text options):
|
|
45
|
+
- Each option can carry one image (`path` / `url` / `data`); pure-image, pure-text, and
|
|
46
|
+
image+text options can be mixed within the same question;
|
|
47
|
+
- Supports multi-question pagination, single/multi select, custom answers, skip, and
|
|
48
|
+
recommended markers (a `(Recommended)` / `(推荐)` suffix on the label);
|
|
49
|
+
- Clicking a thumbnail (or the magnifier button) opens a Lightbox with the full image;
|
|
50
|
+
Esc / clicking the overlay / the close button dismisses it.
|
|
51
|
+
- **`show_images`** (inline images in replies):
|
|
52
|
+
- Shows 1–9 images at a time, each with an optional `caption`;
|
|
53
|
+
- The tool returns markdown image snippets with absolute URLs; the model pastes them
|
|
54
|
+
verbatim into the reply body and they render alongside the text;
|
|
55
|
+
- The client plugin automatically enhances these images: rounded corners, captions on
|
|
56
|
+
hover, click to zoom, and graceful fallback on load failure.
|
|
57
|
+
- **`save_received_images`** (images received by blind models → files):
|
|
58
|
+
- When the user sends images into the chat, an `agent/pre-step` listener rewrites the
|
|
59
|
+
image content blocks in the message into text placeholders (`dshimg:<attachmentId>`) —
|
|
60
|
+
text-only adapters (such as DeepSeek) no longer fail with `UNSUPPORTED_CONTENT` over
|
|
61
|
+
image blocks, and the turn proceeds as usual;
|
|
62
|
+
- In the user's message bubble, the client enhancer replaces the placeholder with a
|
|
63
|
+
zoomable image echo (attachment bytes are served through the
|
|
64
|
+
`/dsh-plugin-image-tools/attachment/<id>` route);
|
|
65
|
+
- Seeing the placeholder, the model calls `save_received_images` to save the images as
|
|
66
|
+
workspace files by attachmentId (default directory `received/`), where file/command
|
|
67
|
+
tools can then analyze them (dimensions, pixels, hashes, etc.);
|
|
68
|
+
- Saved filenames prefer the attachment's own safe filename, otherwise they are
|
|
69
|
+
generated as `image-<n>-<timestamp>.<ext>`.
|
|
70
|
+
- Text-only questions without images are passed straight through to the native UI
|
|
71
|
+
automatically; the two never interfere.
|
|
72
|
+
|
|
73
|
+
## Directory structure
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
lib/index.js Server: three tool registrations + pre-step rewrite + image/attachment registry + web routes (zero runtime dependencies)
|
|
77
|
+
lib/client.js Client: composer chain entry + image choice UI + inline/received-image enhancement (browser module loader format, build-free)
|
|
78
|
+
scripts/selfcheck.mjs Pure-function self-check (node scripts/selfcheck.mjs)
|
|
79
|
+
scripts/smoke-server.mjs Server integration smoke test (a fake ctx runs the full tools → routes → pre-step rewrite → disk persistence chain)
|
|
80
|
+
scripts/smoke-client.mjs Client smoke test (real react rendering of the choice card + pure-function enhancement)
|
|
81
|
+
cordis.patch.yml Bundle patch (mount lines)
|
|
82
|
+
docs/ Screenshots (for README display)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
No configuration needed — install and it works:
|
|
88
|
+
|
|
89
|
+
- Reads no environment variables, needs no API Key / token, writes no config files;
|
|
90
|
+
- All three image source forms (local path / http(s) URL / base64 data URI) work directly,
|
|
91
|
+
with no whitelist configuration;
|
|
92
|
+
- Inline image previews go through a same-origin byte route (loopback), with no external
|
|
93
|
+
service dependencies;
|
|
94
|
+
- The package ships with `cordis.patch.yml` mount lines, applied automatically via
|
|
95
|
+
`dsh.profile.bundles` — no manual configuration changes needed.
|
|
96
|
+
|
|
97
|
+
## Installation
|
|
98
|
+
|
|
99
|
+
```powershell
|
|
100
|
+
# npm (recommended)
|
|
101
|
+
dsh plugin --profile web add dsh-plugin-image-tools
|
|
102
|
+
# or GitHub
|
|
103
|
+
dsh plugin --profile web add github:Pasumao/dsh-plugin-image-tools
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Install from source (local development / debugging):
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
git clone https://github.com/Pasumao/dsh-plugin-image-tools.git
|
|
110
|
+
cd dsh-plugin-image-tools
|
|
111
|
+
npm install
|
|
112
|
+
# Mount into the profile via link:
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
After installing, restart dsh (launcher) and then refresh the browser page. The package
|
|
116
|
+
ships with `cordis.patch.yml` mount lines, applied automatically via `dsh.profile.bundles`
|
|
117
|
+
(same mechanism as dsh-notify) — no manual configuration changes needed.
|
|
118
|
+
|
|
119
|
+
## Model usage examples
|
|
120
|
+
|
|
121
|
+
### ask_user_choice: pick an option by image
|
|
122
|
+
|
|
123
|
+
```jsonc
|
|
124
|
+
{
|
|
125
|
+
"questions": [
|
|
126
|
+
{
|
|
127
|
+
"id": "cover",
|
|
128
|
+
"question": "Pick a cover image",
|
|
129
|
+
"header": "Cover choice",
|
|
130
|
+
"options": [
|
|
131
|
+
{ "label": "Deep-sea whale (Recommended)", "image": { "path": "novel/assets/covers/whale.png" } },
|
|
132
|
+
{ "label": "Starry sky",
|
|
133
|
+
"image": { "url": "https://example.com/stars.png" } },
|
|
134
|
+
{ "label": "Hand-drawn style",
|
|
135
|
+
"image": { "data": "data:image/png;base64,iVBORw0KGgo..." } },
|
|
136
|
+
{ "label": "None of these, I'll type my own", "description": "Select this to enter a custom answer below" }
|
|
137
|
+
],
|
|
138
|
+
"multi_select": false
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
// Returns: { "answers": [ { "id": "cover", "selected": ["Deep-sea whale (Recommended)"] } ] }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### show_images: show images in a reply
|
|
146
|
+
|
|
147
|
+
```jsonc
|
|
148
|
+
// Call show_images
|
|
149
|
+
{
|
|
150
|
+
"images": [
|
|
151
|
+
{ "image": { "path": "novel/assets/covers/whale.png" }, "caption": "Deep-sea whale cover" },
|
|
152
|
+
{ "image": { "url": "https://example.com/stars.png" }, "caption": "Starry sky" }
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
// Returns: { "markdown": ["", ""], "note": "..." }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The model pastes the snippets from the `markdown` array **verbatim**, line by line, into
|
|
159
|
+
the reply body, and the images appear alongside the text:
|
|
160
|
+
|
|
161
|
+
```markdown
|
|
162
|
+
Here are the cover candidates generated for you:
|
|
163
|
+
|
|
164
|
+

|
|
165
|
+
|
|
166
|
+
Let me know if you'd like the colors or composition adjusted.
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Implementation notes (why a plugin instead of core changes)
|
|
170
|
+
|
|
171
|
+
When the browser side consumes the `question/requested` frame it parses it strictly with a
|
|
172
|
+
zod schema, and unknown fields on option objects are stripped; assistant message content is
|
|
173
|
+
generated from model text and offers no channel for structured image blocks either. So
|
|
174
|
+
images **cannot** be stuffed into the option / content fields. Instead, this plugin:
|
|
175
|
+
|
|
176
|
+
1. The server normalizes image bytes into an in-memory registry and serves the bytes
|
|
177
|
+
directly through custom web routes:
|
|
178
|
+
`/dsh-plugin-image-tools/<pickId>/<index>` (choice cards),
|
|
179
|
+
`/dsh-plugin-image-tools/show/<showId>/<index>` (inline in replies), and
|
|
180
|
+
`/dsh-plugin-image-tools/attachment/<attachmentId>` (echo of images received from the
|
|
181
|
+
user), loaded as same-origin `<img src>`;
|
|
182
|
+
2. Choice cards: an invisible HTML comment marker `<!--dsh-pick:v1:<base64url JSON>-->` is
|
|
183
|
+
written at the start of the question's `detail` (a standard string field passed through
|
|
184
|
+
verbatim), carrying the pickId and the indexes of image-bearing options; the client
|
|
185
|
+
plugin registers an entry in the `conversation.composer` slot chain (smaller priority
|
|
186
|
+
value, so it takes precedence over the native one) and renders the image choice card
|
|
187
|
+
once it recognizes the marker; questions without the marker go to the native UI;
|
|
188
|
+
3. Inline in replies: `show_images` returns absolute URLs (host origin derived from
|
|
189
|
+
`ctx.webServer.host/port`); the model pastes them into the body and the core markdown
|
|
190
|
+
renderer displays them natively; the client then progressively enhances images whose
|
|
191
|
+
src starts with `/dsh-plugin-image-tools/show/` (discovery via MutationObserver +
|
|
192
|
+
single-node style/event injection, pure DOM, no intrusion into the React render tree);
|
|
193
|
+
4. Images received by blind models: an `agent/pre-step` waterfall listener is registered
|
|
194
|
+
(same mechanism as agent-instructions / time-context) and rewrites the image blocks in
|
|
195
|
+
the message batches entering the LLM step into text placeholders (registering the
|
|
196
|
+
attachment refs in a TTL registry), keeping session logs/UI plain-text safe; the client
|
|
197
|
+
enhancer recognizes `dshimg:<id>` placeholders in user bubble text and replaces them
|
|
198
|
+
with zoomable images; `save_received_images` fetches the attachment bytes via
|
|
199
|
+
`ctx.attachments.readImage` and writes them to disk.
|
|
200
|
+
|
|
201
|
+
See `设计说明.md` for details.
|
|
202
|
+
|
|
203
|
+
## Security and limitations
|
|
204
|
+
|
|
205
|
+
- Image bytes live only in process memory: choice-card images are freed as soon as the
|
|
206
|
+
question is answered/canceled; inline images and attachment echoes rely on a 30-minute
|
|
207
|
+
TTL cleanup (they must survive until reply rendering completes).
|
|
208
|
+
- **Browser cache as a safety net**: image route URLs are content-addressed (pickId/showId
|
|
209
|
+
are UUIDs, attachmentId is a content hash) and the bytes never change, so responses are
|
|
210
|
+
sent with `Cache-Control: private, max-age=2592000, immutable` (30 days). Once an image
|
|
211
|
+
has loaded successfully in the browser, its bytes stay in the browser's local cache —
|
|
212
|
+
afterwards, even if the source file is deleted/overwritten, the server-side TTL cleans
|
|
213
|
+
up, or dsh restarts, refreshing the page or revisiting history hits the local cache
|
|
214
|
+
directly and the images remain visible (no further requests to the server).
|
|
215
|
+
- Each image is capped at 20 MiB; only PNG / JPEG / WebP / GIF are supported (validated by
|
|
216
|
+
magic numbers; a mismatch with the declared type raises an error).
|
|
217
|
+
- Image routes are plain same-origin HTTP routes (same trust level as the GUI) with no
|
|
218
|
+
extra authentication.
|
|
219
|
+
- The markdown URLs of inline images are absolute addresses (`http://host:port`, derived
|
|
220
|
+
from the server's listening configuration); if the GUI is accessed through a reverse
|
|
221
|
+
proxy or on a different port, image addresses in historical messages may break (same
|
|
222
|
+
limitation as choice cards).
|
|
223
|
+
|
|
224
|
+
## Related plugins
|
|
225
|
+
|
|
226
|
+
This plugin is part of **Pasumao's dsh plugin ecosystem**; the published plugins in the
|
|
227
|
+
series can be used together:
|
|
228
|
+
|
|
229
|
+
| Plugin (npm) | GitHub | Description |
|
|
230
|
+
|---|---|---|
|
|
231
|
+
| [dsh-notify](https://www.npmjs.com/package/dsh-notify) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-notify) | Native Windows notifications + system tray |
|
|
232
|
+
| [dsh-plugin-choice-refresh](https://www.npmjs.com/package/dsh-plugin-choice-refresh) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-choice-refresh) | Choice enhancements: regenerate options / more options |
|
|
233
|
+
| [dsh-plugin-dev-kb](https://www.npmjs.com/package/dsh-plugin-dev-kb) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-dev-kb) | Plugin development knowledge base (full mirror of the official docs + skill) |
|
|
234
|
+
| [dsh-plugin-table-zoom](https://www.npmjs.com/package/dsh-plugin-table-zoom) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-table-zoom) | Floating viewer for long chat tables + one-click copy as Markdown |
|
|
235
|
+
| [dsh-plugin-windows-guard](https://www.npmjs.com/package/dsh-plugin-windows-guard) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-windows-guard) | Windows environment safeguards: rules skills + garbled-text detection / dangerous-write blocking / encoding diagnosis & repair |
|
|
236
|
+
| [dsh-plugin-workbench](https://www.npmjs.com/package/dsh-plugin-workbench) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-workbench) | VS Code-style file explorer + editable preview |
|
|
237
|
+
|
|
238
|
+
> For the rest of the series, see [Pasumao · dsh plugins](https://github.com/Pasumao);
|
|
239
|
+
> if you find them useful, feel free to give a ⭐ on GitHub.
|
|
240
|
+
|
|
241
|
+
## AI generation disclosure
|
|
242
|
+
|
|
243
|
+
The code and documentation were generated with AI assistance (DeepSeek Harness), and all
|
|
244
|
+
of it has undergone human review and verification on a live instance
|
|
245
|
+
(`npm run smoke`: selfcheck + full server-side pipeline with a fake ctx + fake client
|
|
246
|
+
rendering).
|
|
247
|
+
|
|
248
|
+
## License
|
|
249
|
+
|
|
250
|
+
[MIT](./LICENSE)
|
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|

|
|
5
5
|

|
|
6
6
|
|
|
7
|
+
[**中文**](./README.md) | [English](./README.en.md)
|
|
8
|
+
|
|
7
9
|
**dsh 插件市场里唯一支持「图片选择卡」的插件**:给 DeepSeek Harness Web GUI 增加图片能力,
|
|
8
10
|
三个工具覆盖三种场景——模型让你在选项里挑图、在回复正文里展示图、你把图发给盲模型。全部零 token 本地渲染:
|
|
9
11
|
|
|
@@ -54,33 +56,6 @@
|
|
|
54
56
|
- 保存的文件名优先用附件自带的安全文件名,否则按 `image-<n>-<时间戳>.<ext>` 生成。
|
|
55
57
|
- 纯文字问题不带图片时,客户端自动放行给原生 UI,互不影响。
|
|
56
58
|
|
|
57
|
-
## 实现要点(为什么是插件而不是改核心)
|
|
58
|
-
|
|
59
|
-
浏览器端消费 `question/requested` 帧时用 zod schema 严格解析,选项对象上的未知
|
|
60
|
-
字段会被剥离;助手消息 content 由模型文本生成,也没有携带结构化图片块的通道。
|
|
61
|
-
所以图片**不能**塞进 option / content 字段。本插件改为:
|
|
62
|
-
|
|
63
|
-
1. 服务端把图片字节归一化进内存注册表,通过自定义 web 路由
|
|
64
|
-
`/dsh-plugin-image-tools/<pickId>/<index>`(选择卡)、
|
|
65
|
-
`/dsh-plugin-image-tools/show/<showId>/<index>`(回复内嵌)与
|
|
66
|
-
`/dsh-plugin-image-tools/attachment/<attachmentId>`(盲模型收图回显)
|
|
67
|
-
直接提供字节(同源 `<img src>` 加载);
|
|
68
|
-
2. 选择卡:在问题的 `detail`(标准字符串字段,原样透传)开头写入不可见的
|
|
69
|
-
HTML 注释标记 `<!--dsh-pick:v1:<base64url JSON>-->`,携带 pickId 与带图选项下标;
|
|
70
|
-
客户端插件在 `conversation.composer` slot 链注册条目(priority 更小,优先于原生),
|
|
71
|
-
识别标记后渲染图片选择卡;无标记的问题交给原生 UI;
|
|
72
|
-
3. 回复内嵌:`show_images` 返回绝对 URL(宿主 origin 由 `ctx.webServer.host/port`
|
|
73
|
-
推导),模型粘贴进正文,核心 markdown 渲染器原生显示;客户端再对
|
|
74
|
-
`/dsh-plugin-image-tools/show/` 前缀的图片做渐进增强(MutationObserver 发现 +
|
|
75
|
-
单节点样式/事件注入,纯 DOM,不侵入 React 渲染树);
|
|
76
|
-
4. 盲模型收图:注册 `agent/pre-step` waterfall 监听器(与 agent-instructions /
|
|
77
|
-
time-context 同机制),把进入 LLM 步骤的消息批次里的 image 块重写为文本占位符
|
|
78
|
-
(登记附件 ref 到 TTL 注册表),会话日志/UI 因此保持纯文本安全;客户端增强器
|
|
79
|
-
在用户气泡文本里识别 `dshimg:<id>` 占位符并替换为可放大图片;`save_received_images`
|
|
80
|
-
经 `ctx.attachments.readImage` 取回附件字节落盘。
|
|
81
|
-
|
|
82
|
-
详见 `设计说明.md`。
|
|
83
|
-
|
|
84
59
|
## 目录结构
|
|
85
60
|
|
|
86
61
|
```
|
|
@@ -172,6 +147,33 @@ npm install
|
|
|
172
147
|
需要调整配色或构图可以告诉我。
|
|
173
148
|
```
|
|
174
149
|
|
|
150
|
+
## 实现要点(为什么是插件而不是改核心)
|
|
151
|
+
|
|
152
|
+
浏览器端消费 `question/requested` 帧时用 zod schema 严格解析,选项对象上的未知
|
|
153
|
+
字段会被剥离;助手消息 content 由模型文本生成,也没有携带结构化图片块的通道。
|
|
154
|
+
所以图片**不能**塞进 option / content 字段。本插件改为:
|
|
155
|
+
|
|
156
|
+
1. 服务端把图片字节归一化进内存注册表,通过自定义 web 路由
|
|
157
|
+
`/dsh-plugin-image-tools/<pickId>/<index>`(选择卡)、
|
|
158
|
+
`/dsh-plugin-image-tools/show/<showId>/<index>`(回复内嵌)与
|
|
159
|
+
`/dsh-plugin-image-tools/attachment/<attachmentId>`(盲模型收图回显)
|
|
160
|
+
直接提供字节(同源 `<img src>` 加载);
|
|
161
|
+
2. 选择卡:在问题的 `detail`(标准字符串字段,原样透传)开头写入不可见的
|
|
162
|
+
HTML 注释标记 `<!--dsh-pick:v1:<base64url JSON>-->`,携带 pickId 与带图选项下标;
|
|
163
|
+
客户端插件在 `conversation.composer` slot 链注册条目(priority 更小,优先于原生),
|
|
164
|
+
识别标记后渲染图片选择卡;无标记的问题交给原生 UI;
|
|
165
|
+
3. 回复内嵌:`show_images` 返回绝对 URL(宿主 origin 由 `ctx.webServer.host/port`
|
|
166
|
+
推导),模型粘贴进正文,核心 markdown 渲染器原生显示;客户端再对
|
|
167
|
+
`/dsh-plugin-image-tools/show/` 前缀的图片做渐进增强(MutationObserver 发现 +
|
|
168
|
+
单节点样式/事件注入,纯 DOM,不侵入 React 渲染树);
|
|
169
|
+
4. 盲模型收图:注册 `agent/pre-step` waterfall 监听器(与 agent-instructions /
|
|
170
|
+
time-context 同机制),把进入 LLM 步骤的消息批次里的 image 块重写为文本占位符
|
|
171
|
+
(登记附件 ref 到 TTL 注册表),会话日志/UI 因此保持纯文本安全;客户端增强器
|
|
172
|
+
在用户气泡文本里识别 `dshimg:<id>` 占位符并替换为可放大图片;`save_received_images`
|
|
173
|
+
经 `ctx.attachments.readImage` 取回附件字节落盘。
|
|
174
|
+
|
|
175
|
+
详见 `设计说明.md`。
|
|
176
|
+
|
|
175
177
|
## 安全与限制
|
|
176
178
|
|
|
177
179
|
- 图片字节仅存于进程内存:选择卡图片随问题回答/取消立即释放;
|
|
@@ -197,6 +199,7 @@ npm install
|
|
|
197
199
|
| [dsh-plugin-choice-refresh](https://www.npmjs.com/package/dsh-plugin-choice-refresh) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-choice-refresh) | 选择增强:重新生成选项 / 更多选项 |
|
|
198
200
|
| [dsh-plugin-dev-kb](https://www.npmjs.com/package/dsh-plugin-dev-kb) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-dev-kb) | 插件开发知识库(官方文档完整镜像 + 技能) |
|
|
199
201
|
| [dsh-plugin-table-zoom](https://www.npmjs.com/package/dsh-plugin-table-zoom) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-table-zoom) | 聊天长表格浮窗查看 + 一键复制 Markdown |
|
|
202
|
+
| [dsh-plugin-windows-guard](https://www.npmjs.com/package/dsh-plugin-windows-guard) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-windows-guard) | Windows 环境防坑:守则技能 + 乱码检测 / 危险写拦截 / 编码诊断修复 |
|
|
200
203
|
| [dsh-plugin-workbench](https://www.npmjs.com/package/dsh-plugin-workbench) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-workbench) | VS Code 风格文件浏览器 + 可编辑预览 |
|
|
201
204
|
|
|
202
205
|
> 本系列其余插件见 [Pasumao · dsh 插件](https://github.com/Pasumao);觉得好用欢迎到 GitHub 点 ⭐。
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-image-tools",
|
|
3
|
-
"description": "DSH
|
|
4
|
-
"version": "0.6.
|
|
3
|
+
"description": "DSH 图片插件,三个工具覆盖三种场景:ask_user_choice 图片/图文混合选择卡(Web GUI 渲染,可放大查看)+ show_images 回复内嵌图片(图文混排)+ save_received_images 盲模型收图存为工作区文件;聊天栏所有图片点击放大,支持滚轮缩放与拖拽平移。来源支持本地路径 / http(s) URL / base64 data URI。零 token 本地渲染,纯插件实现不改核心包。",
|
|
4
|
+
"version": "0.6.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"docs",
|
|
16
16
|
"cordis.patch.yml",
|
|
17
17
|
"README.md",
|
|
18
|
+
"README.en.md",
|
|
19
|
+
"CHANGELOG.md",
|
|
18
20
|
"LICENSE",
|
|
19
21
|
"设计说明.md"
|
|
20
22
|
],
|
|
@@ -40,9 +42,16 @@
|
|
|
40
42
|
"ask",
|
|
41
43
|
"choice",
|
|
42
44
|
"image",
|
|
45
|
+
"image-picker",
|
|
43
46
|
"picker",
|
|
44
47
|
"show",
|
|
45
|
-
"inline"
|
|
48
|
+
"inline",
|
|
49
|
+
"inline-image",
|
|
50
|
+
"lightbox",
|
|
51
|
+
"zoom",
|
|
52
|
+
"multimodal",
|
|
53
|
+
"vision",
|
|
54
|
+
"attachment"
|
|
46
55
|
],
|
|
47
56
|
"repository": {
|
|
48
57
|
"type": "git",
|