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 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
- // 待答载体(与服务端回答协议一致;同 PendingQuestion wire 编码)
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(wait) { this.wait = wait; }
147
- get key() { return this.wait.key; }
148
- get questions() { return this.wait.payload.questions; }
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
- var receipt = await this.wait.respond({ ok: true, value: { sessionId: this.wait.sessionId, answer: answer } });
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
- var receipt = await this.wait.respond({
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
- /** 认领“至少一题带图片标记”的 question 交互;其余交给原生 UI。 */
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 interactions = _ref.interactions;
1305
- for (var i = 0; i < interactions.length; i++) {
1306
- var item = interactions[i];
1307
- if (item.kind !== 'question') continue;
1308
- var payload = item.payload;
1309
- var questions = payload !== null && typeof payload === 'object' && Array.isArray(payload.questions) ? payload.questions : [];
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",
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
  },
@@ -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
- // profile 的共享 node_modules 树解析 react(单实例:jsx-runtime/react-dom 同源)
23
- const profileRequire = createRequire(join('C:/Users/18303/.dsh/profiles/node_modules', 'noop.cjs'))
24
- const reactRoot = dirname(profileRequire.resolve('react'))
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 = profileRequire('react')
27
- const reactDomServer = profileRequire('react-dom/server')
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 withImage = {
128
+ const fakePending = (questions, extra) => ({
127
129
  kind: 'question',
128
- key: 'q:1',
130
+ key: 'q:x',
129
131
  sessionId: 's1',
130
- payload: { type: 'question/requested', sessionId: 's1', questions: [{ id: 'a', question: '选图', detail: markerDetail, options: [{ label: 'A' }, { label: 'B' }, { label: 'C' }] }] },
131
- }
132
- const plain = {
133
- kind: 'question',
134
- key: 'q:2',
135
- sessionId: 's2',
136
- payload: { type: 'question/requested', sessionId: 's2', questions: [{ id: 'b', question: '纯文字', options: [{ label: 'X' }] }] },
137
- }
138
- const approval = { kind: 'approval', key: 'a:1', sessionId: 's1', payload: {} }
139
- assert.equal(mod.selectPickChoice({ interactions: [plain] }), null, '纯文字问题应放行')
140
- assert.equal(mod.selectPickChoice({ interactions: [approval, plain] }), null, '无图问题应放行')
141
- assert.equal(mod.selectPickChoice({ interactions: [plain, withImage] }), withImage, '带图问题应认领')
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
- payload: { type: 'question/requested', sessionId: 's1', questions: [{ id: 'a', question: '选一张封面', header: '封面', detail: markerDetail, multiSelect: false, options: [{ label: 'A (Recommended)', description: '第一张' }, { label: 'B' }, { label: 'C' }] }] },
154
- respond: async (r) => ({ accepted: true }),
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'), '卡片未渲染')