dsh-plugin-image-tools 0.6.4 → 0.6.5

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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.5] - 2026-09-04
4
+
5
+ - 适配 dsh 0.1.2-rc.1:conversation.composer 链条目 select 入参由 `{interactions}` 改为 `{pendingInteraction}`(原生 PendingQuestion 单对象),按 questions/answer/cancel 鸭子类型认领带图片标记的问题批,plan-review 放行;
6
+ - 待答载体 PendingChoice 改为包装 PendingQuestion(`.answer({answers})` / `.cancel()` 直调,替代旧 `wait.respond` 协议);
7
+ - DOM 增强部分(data-chat-flow 容器、dshimg 占位符、lightbox)经核验在新版 dsh-client-ui-chat 中锚点全部保留,无需改动。
8
+ # Changelog
9
+
3
10
  ## [0.6.4] - 2026-08-29
4
11
 
5
12
  - 文档:「实现要点」整节下移到「模型用法示例」之后(访客先看价值再看原理);
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.5",
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'), '卡片未渲染')