dsh-plugin-image-tools 0.6.4 → 0.6.6
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 +15 -0
- package/README.en.md +14 -0
- package/README.md +10 -0
- package/lib/client.js +26 -23
- package/package.json +5 -1
- package/scripts/smoke-client.mjs +29 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.6] - 2026-09-04
|
|
4
|
+
|
|
5
|
+
- 文档:中英 README 新增「兼容性」节——实测于 dsh 0.1.2-rc.1;选择卡 0.6.5 起适配
|
|
6
|
+
`pendingInteraction` / `PendingQuestion` 新协议;聊天图片放大依赖的 `data-chat-flow`
|
|
7
|
+
容器、`dshimg:` 占位符、lightbox 已逐一核验保留;测试基建自带 react/react-dom@19
|
|
8
|
+
devDependencies,`npm run smoke` 不再依赖宿主安装布局。
|
|
9
|
+
# Changelog
|
|
10
|
+
|
|
11
|
+
## [0.6.5] - 2026-09-04
|
|
12
|
+
|
|
13
|
+
- 适配 dsh 0.1.2-rc.1:conversation.composer 链条目 select 入参由 `{interactions}` 改为 `{pendingInteraction}`(原生 PendingQuestion 单对象),按 questions/answer/cancel 鸭子类型认领带图片标记的问题批,plan-review 放行;
|
|
14
|
+
- 待答载体 PendingChoice 改为包装 PendingQuestion(`.answer({answers})` / `.cancel()` 直调,替代旧 `wait.respond` 协议);
|
|
15
|
+
- DOM 增强部分(data-chat-flow 容器、dshimg 占位符、lightbox)经核验在新版 dsh-client-ui-chat 中锚点全部保留,无需改动。
|
|
16
|
+
# Changelog
|
|
17
|
+
|
|
3
18
|
## [0.6.4] - 2026-08-29
|
|
4
19
|
|
|
5
20
|
- 文档:「实现要点」整节下移到「模型用法示例」之后(访客先看价值再看原理);
|
package/README.en.md
CHANGED
|
@@ -221,6 +221,20 @@ See `设计说明.md` for details.
|
|
|
221
221
|
proxy or on a different port, image addresses in historical messages may break (same
|
|
222
222
|
limitation as choice cards).
|
|
223
223
|
|
|
224
|
+
## Compatibility
|
|
225
|
+
|
|
226
|
+
- Tested on DSH `0.1.2-rc.1` (since 0.6.5 the choice-card entry is adapted to that
|
|
227
|
+
version's new protocol: the `conversation.composer` chain selector receives a single
|
|
228
|
+
`pendingInteraction` (the native `PendingQuestion`), and answers go through
|
|
229
|
+
`.answer({answers})` / `.cancel()`); since 0.6.6 the tests bundle their own
|
|
230
|
+
react/react-dom and no longer depend on the host installation layout.
|
|
231
|
+
- The `data-chat-flow` container, the `dshimg:` placeholder protocol, and the lightbox —
|
|
232
|
+
all anchors the chat image zoom relies on — were verified one by one against
|
|
233
|
+
`dsh-client-ui-chat` in 0.1.2-rc.1.
|
|
234
|
+
- Plain-text questions are unaffected (handed to the native UI or
|
|
235
|
+
`dsh-plugin-choice-refresh`).
|
|
236
|
+
- Depends on the client services `slots` / `locale`.
|
|
237
|
+
|
|
224
238
|
## Related plugins
|
|
225
239
|
|
|
226
240
|
This plugin is part of **Pasumao's dsh plugin ecosystem**; the published plugins in the
|
package/README.md
CHANGED
|
@@ -189,6 +189,16 @@ npm install
|
|
|
189
189
|
- 内嵌图片的 markdown URL 是绝对地址(`http://host:port`,由服务端监听配置推导);
|
|
190
190
|
若 GUI 经过反向代理/换端口访问,历史消息里的图片地址可能失效(同选择卡的限制)。
|
|
191
191
|
|
|
192
|
+
## 兼容性
|
|
193
|
+
|
|
194
|
+
- 实测于 DSH `0.1.2-rc.1`(0.6.5 起适配该版选择卡新协议:`conversation.composer` 链的
|
|
195
|
+
select 入参改为单对象 `pendingInteraction`(原生 `PendingQuestion`),应答走
|
|
196
|
+
`.answer({answers})` / `.cancel()`);0.6.6 起测试自带 react/react-dom,不再依赖宿主安装布局。
|
|
197
|
+
- 聊天图片放大所依赖的 `data-chat-flow` 容器、`dshimg:` 占位符协议、lightbox 已在
|
|
198
|
+
0.1.2-rc.1 的 `dsh-client-ui-chat` 中逐一核验保留。
|
|
199
|
+
- 纯文字问题不受本插件影响(放行给原生 UI 或 `dsh-plugin-choice-refresh`)。
|
|
200
|
+
- 依赖客户端服务:`slots` / `locale`。
|
|
201
|
+
|
|
192
202
|
## 相关插件
|
|
193
203
|
|
|
194
204
|
本插件属于 **Pasumao 的 dsh 插件生态**,同系列已发布插件可搭配使用:
|
package/lib/client.js
CHANGED
|
@@ -140,22 +140,20 @@ window.__ModuleLoader__.load({
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// ------------------------------------------------------------------
|
|
143
|
-
//
|
|
143
|
+
// 待答载体(dsh 0.1.2 起 composer 链的 matched 即原生 PendingQuestion:
|
|
144
|
+
// .answer({answers}) / .cancel() / .key / .sessionId / .questions,
|
|
145
|
+
// 这里包一层维持组件既有协议)
|
|
144
146
|
// ------------------------------------------------------------------
|
|
145
147
|
var PendingChoice = class {
|
|
146
|
-
constructor(
|
|
147
|
-
get key() { return this.
|
|
148
|
-
get
|
|
148
|
+
constructor(matched) { this.matched = matched; }
|
|
149
|
+
get key() { return this.matched.key; }
|
|
150
|
+
get sessionId() { return this.matched.sessionId; }
|
|
151
|
+
get questions() { return this.matched.questions; }
|
|
149
152
|
async answer(answer) {
|
|
150
|
-
|
|
151
|
-
if (!receipt.accepted) throw new Error('question response rejected: ' + receipt.reason);
|
|
153
|
+
await this.matched.answer(answer);
|
|
152
154
|
}
|
|
153
155
|
async cancel() {
|
|
154
|
-
|
|
155
|
-
ok: false,
|
|
156
|
-
error: { code: 'cancelled', message: 'the user closed this question request', details: {} }
|
|
157
|
-
});
|
|
158
|
-
if (!receipt.accepted) throw new Error('question cancellation rejected: ' + receipt.reason);
|
|
156
|
+
await this.matched.cancel();
|
|
159
157
|
}
|
|
160
158
|
};
|
|
161
159
|
|
|
@@ -1299,19 +1297,24 @@ window.__ModuleLoader__.load({
|
|
|
1299
1297
|
return jsx(ImageChoiceFlow, { pending: pending, t: props.t }, pending.key);
|
|
1300
1298
|
}
|
|
1301
1299
|
|
|
1302
|
-
/**
|
|
1300
|
+
/**
|
|
1301
|
+
* 认领“至少一题带图片标记”的 question 交互;其余交给原生 UI。
|
|
1302
|
+
* dsh 0.1.2 起 select 入参为 { pendingInteraction }(单对象,即原生
|
|
1303
|
+
* PendingQuestion 实例;无交互时为 null),不再是 { interactions } 数组。
|
|
1304
|
+
*/
|
|
1305
|
+
function isPendingQuestion(value) {
|
|
1306
|
+
return value !== null && typeof value === 'object'
|
|
1307
|
+
&& Array.isArray(value.questions)
|
|
1308
|
+
&& typeof value.answer === 'function'
|
|
1309
|
+
&& typeof value.cancel === 'function';
|
|
1310
|
+
}
|
|
1303
1311
|
function selectPickChoice(_ref) {
|
|
1304
|
-
var
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
var claimed = false;
|
|
1311
|
-
for (var j = 0; j < questions.length; j++) {
|
|
1312
|
-
if (parseMarker(questions[j].detail) !== null) { claimed = true; break; }
|
|
1313
|
-
}
|
|
1314
|
-
if (claimed) return item;
|
|
1312
|
+
var pendingInteraction = _ref.pendingInteraction;
|
|
1313
|
+
if (!isPendingQuestion(pendingInteraction)) return null;
|
|
1314
|
+
if (pendingInteraction.kind === 'plan-review') return null;
|
|
1315
|
+
var questions = pendingInteraction.questions;
|
|
1316
|
+
for (var j = 0; j < questions.length; j++) {
|
|
1317
|
+
if (questions[j] !== null && typeof questions[j] === 'object' && parseMarker(questions[j].detail) !== null) return pendingInteraction;
|
|
1315
1318
|
}
|
|
1316
1319
|
return null;
|
|
1317
1320
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-image-tools",
|
|
3
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
|
+
"version": "0.6.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
31
31
|
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"react": "^19.0.0",
|
|
34
|
+
"react-dom": "^19.0.0"
|
|
35
|
+
},
|
|
32
36
|
"engines": {
|
|
33
37
|
"node": ">=18"
|
|
34
38
|
},
|
package/scripts/smoke-client.mjs
CHANGED
|
@@ -19,12 +19,13 @@ import { buildPickMarker } from '../lib/index.js'
|
|
|
19
19
|
|
|
20
20
|
const require = createRequire(import.meta.url)
|
|
21
21
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
const
|
|
22
|
+
// 优先用插件自带的 devDependencies react(0.6.5 起 dsh-app 根部 react 可能被降级
|
|
23
|
+
// 到 16.8、无 jsx-runtime,与 react-dom 19 不同源);本地没有时退回 profile 共享树。
|
|
24
|
+
const localRequire = createRequire(join(ROOT, 'noop.cjs'))
|
|
25
|
+
const reactRoot = dirname(localRequire.resolve('react'))
|
|
25
26
|
const jsxRuntime = join(reactRoot, 'jsx-runtime.js')
|
|
26
|
-
const reactModule =
|
|
27
|
-
const reactDomServer =
|
|
27
|
+
const reactModule = localRequire('react')
|
|
28
|
+
const reactDomServer = localRequire('react-dom/server')
|
|
28
29
|
|
|
29
30
|
// 1) 模拟浏览器模块加载器
|
|
30
31
|
let spec = null
|
|
@@ -121,24 +122,28 @@ assert.deepEqual(mod.extractImageTokenIds('📷 图片 dshimg:att-aaa111bbb222
|
|
|
121
122
|
assert.deepEqual(mod.extractImageTokenIds('没有 token'), [])
|
|
122
123
|
assert.deepEqual(mod.extractImageTokenIds(undefined), [])
|
|
123
124
|
|
|
124
|
-
// 2) select
|
|
125
|
+
// 2) select:认领带标记问题,放过纯文字/plan-review(dsh 0.1.2 协议:
|
|
126
|
+
// 入参 { pendingInteraction },questions 直挂、带 answer()/cancel())
|
|
125
127
|
const markerDetail = buildPickMarker('pick-1', [0, 2]) + '\n\n请选择一张图'
|
|
126
|
-
const
|
|
128
|
+
const fakePending = (questions, extra) => ({
|
|
127
129
|
kind: 'question',
|
|
128
|
-
key: 'q:
|
|
130
|
+
key: 'q:x',
|
|
129
131
|
sessionId: 's1',
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
assert.equal(mod.selectPickChoice({
|
|
132
|
+
questions,
|
|
133
|
+
answer: async () => {},
|
|
134
|
+
cancel: async () => {},
|
|
135
|
+
...extra,
|
|
136
|
+
})
|
|
137
|
+
const withImage = fakePending([{ id: 'a', question: '选图', detail: markerDetail, options: [{ label: 'A' }, { label: 'B' }, { label: 'C' }] }], { key: 'q:1' })
|
|
138
|
+
const plain = fakePending([{ id: 'b', question: '纯文字', options: [{ label: 'X' }] }], { key: 'q:2', sessionId: 's2' })
|
|
139
|
+
const planReview = fakePending(
|
|
140
|
+
[{ id: 'c', question: '确认计划', detail: '计划', intent: { kind: 'plan-review', approve: '执行' }, options: [{ label: '执行' }] }],
|
|
141
|
+
{ key: 'q:3', kind: 'plan-review' },
|
|
142
|
+
)
|
|
143
|
+
assert.equal(mod.selectPickChoice({ pendingInteraction: plain }), null, '纯文字问题应放行')
|
|
144
|
+
assert.equal(mod.selectPickChoice({ pendingInteraction: planReview }), null, 'plan-review 应放行')
|
|
145
|
+
assert.equal(mod.selectPickChoice({ pendingInteraction: null }), null, '无 pendingInteraction 应放行')
|
|
146
|
+
assert.equal(mod.selectPickChoice({ pendingInteraction: withImage }), withImage, '带图问题应认领')
|
|
142
147
|
|
|
143
148
|
// 3) parseMarker 与服务端互通
|
|
144
149
|
const parsed = mod.parseMarker(markerDetail)
|
|
@@ -148,10 +153,12 @@ assert.deepEqual({ pickId: parsed.pickId, images: parsed.images, human: parsed.h
|
|
|
148
153
|
const { renderToString } = reactDomServer
|
|
149
154
|
const fakeT = (key) => ({ 'nav.cancel': '取消', 'action.skip': '跳过', 'action.next': '下一步', 'submit': '提交', 'option.recommended': '推荐', 'custom.placeholder': '输入答案', 'image.failed': '加载失败', 'image.zoom': '放大查看', 'image.close': '关闭' }[key] ?? key)
|
|
150
155
|
const fakeWait = {
|
|
156
|
+
kind: 'question',
|
|
151
157
|
key: 'q:1',
|
|
152
158
|
sessionId: 's1',
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
questions: [{ id: 'a', question: '选一张封面', header: '封面', detail: markerDetail, multiSelect: false, options: [{ label: 'A (Recommended)', description: '第一张' }, { label: 'B' }, { label: 'C' }] }],
|
|
160
|
+
answer: async () => {},
|
|
161
|
+
cancel: async () => {},
|
|
155
162
|
}
|
|
156
163
|
const html = renderToString(reactModule.createElement(mod.ImageChoiceComposer, { matched: fakeWait, t: fakeT }))
|
|
157
164
|
assert.ok(html.includes('dshpick-card'), '卡片未渲染')
|