@wszhoho/dsh-file-attachment 0.2.1 → 0.2.2

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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/lib/client.js +39 -40
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,8 +8,8 @@ DeepSeek Harness (dsh) web GUI 插件:在会话输入框中**拖入或 Ctrl+V
8
8
 
9
9
  | 操作 | 行为 |
10
10
  | --- | --- |
11
- | 拖入/粘贴 **文档**(docx/xlsx/pdf/md/txt/rpk 等非图片文件) | 浏览器读全文 → base64 → 宿主落盘 `<会话工作区>/.dsh-file-attachment/<时间戳>-<文件名>` → 光标插入 `@绝对路径`,底部 toast 提示保存目录 |
12
- | 一次拖入/粘贴 **多个文件**(支持多文件) | 批量处理:逐个读取全文 → base64 → 落盘 → 在光标处一次性插入全部 `@` 引用,toast 汇总保存数量与目录 |
11
+ | 拖入/粘贴 **文档**(docx/xlsx/pdf/md/txt/rpk 等非图片文件) | 浏览器读全文 → base64 → 宿主落盘 `<会话工作区>/.dsh-file-attachment/<时间戳>-<文件名>` → 光标插入 `@绝对路径`,成功不提示、失败简短 toast |
12
+ | 一次拖入/粘贴 **多个文件**(支持多文件) | 批量处理:逐个读取全文 → base64 → 落盘 → 在光标处一次性插入全部 `@` 引用;全部成功不提示,部分失败仅简短提示「N 个已跳过」 |
13
13
  | 拖入 **目录** | 拒绝,toast 提示「不支持拖入目录」,不插入引用 |
14
14
  | 拖入/粘贴 **图片** | 不拦截,原样走 dsh web 既有草稿图片流程(零变化) |
15
15
  | 粘贴 **纯文本**(含从地址栏复制的目录路径字符串) | 不改写,完全原生行为 |
@@ -71,7 +71,7 @@ packages/dsh-file-attachment/
71
71
  ```
72
72
 
73
73
  - **Host 半**:`webServer.register` 前缀路由 `/dsh-file-attachment`,POST `/dsh-file-attachment/save` 接收 `{name, data(base64), sessionId}`,base64 解码后用 `node:fs/promises` 写盘到会话工作区 `.dsh-file-attachment/`,返回 `{ok, value:{path,dir,name,size}}`。
74
- - **Client 半**:`conversation.composer.dock`(list 槽,同步桥状态,不渲染文本)+ `shell.overlay`(list 槽,toast 通知展示),document 级 capture 监听先于应用 bubble 监听执行。
74
+ - **Client 半**:`conversation.input.dock`(list 槽,同步桥状态,不渲染文本;经会话作用域取输入机,hero 首轮空白态也挂载)+ `shell.overlay`(list 槽,toast 通知展示),document 级 capture 监听先于应用 bubble 监听执行。
75
75
  - **通知**:`shell.overlay` frame-wide 浮动 toast(不参与文档流,不破坏布局),4 秒自动消失。
76
76
  - **浮层**:拖入任何文件时 capture 阶段拦截应用自带「拖入图片…」DropOverlay(文案面向图片,对文档是误导)。
77
77
 
package/lib/client.js CHANGED
@@ -216,7 +216,7 @@ window.__ModuleLoader__.load({
216
216
  }
217
217
 
218
218
  // 拖放/粘贴文件共用核心。source:'drop' | 'paste'
219
- // 返回是否已接管事件(false = 纯图片拖放,原样交给既有图片路径)
219
+ // 返回是否已接管事件(false = 纯图片拖放/粘贴,原样交给既有图片路径)
220
220
  function handleFilesEvent(e, files, items, source) {
221
221
  const images = []
222
222
  const docs = [] // { path, name, isDir, file }
@@ -236,10 +236,14 @@ window.__ModuleLoader__.load({
236
236
  docs.push({ path: resolveDropPath(raw), name, isDir, file })
237
237
  }
238
238
 
239
- // 纯图片拖放:完全不碰事件,交给既有图片路径(零行为变化)
240
- if (docs.length === 0 && source === 'drop') return false
239
+ // 纯图片(拖放或粘贴):完全不碰事件,原样交给 dsh web 既有图片路径
240
+ //(onPaste/onDrop intakeImages addImages),行为零变化。
241
+ // 不能对「纯图片粘贴」接管:接管依赖输入桥 bridge.actions(挂在 conversation.composer.dock
242
+ // 槽、仅非 hero 态才挂载),首次对话(hero/blank 态)桥未就绪会误报「没有可用的输入框」,
243
+ // 且 preventDefault+stopImmediatePropagation 会吞掉原生粘贴导致图片丢失。
244
+ if (docs.length === 0) return false
241
245
 
242
- // 含文档(或纯图片粘贴:应用内无既有 paste 路径,按同一草稿图片机制登记)—— 完全接管
246
+ // 含文档(可混图片)—— 完全接管:文档落盘并插 @ 引用,混入的图片按草稿图片机制登记
243
247
  e.preventDefault()
244
248
  e.stopImmediatePropagation()
245
249
  dismissDropOverlay()
@@ -251,21 +255,18 @@ window.__ModuleLoader__.load({
251
255
  async function runBatch(images, docs) {
252
256
  const actions = bridge.actions
253
257
  if (actions === null || typeof actions.setDraft !== 'function') {
254
- announce('无法插入文件引用:当前没有可用的输入框')
258
+ announce('输入框不可用')
255
259
  return
256
260
  }
257
261
  const phase = bridge.input !== void 0 ? bridge.input.phase : 'plain'
258
262
  if (phase !== 'plain') {
259
- announce('输入框正忙,完成当前操作后再试')
263
+ announce('输入框正忙')
260
264
  return
261
265
  }
262
266
  const mentions = []
263
- const savedNames = []
264
267
  const dirRejected = []
265
- let savedDir = ''
266
268
  let savedCount = 0
267
269
  let skipped = 0
268
- let lastErr = ''
269
270
  for (let i = 0; i < docs.length; i++) {
270
271
  const d = docs[i]
271
272
  if (d.isDir) {
@@ -283,10 +284,7 @@ window.__ModuleLoader__.load({
283
284
  if (m === undefined) throw new Error('path')
284
285
  mentions.push(m)
285
286
  savedCount++
286
- if (typeof res.name === 'string' && res.name !== '') savedNames.push(res.name)
287
- if (typeof res.dir === 'string' && res.dir !== '') savedDir = res.dir
288
287
  } catch (err) {
289
- lastErr = err && err.message ? err.message : String(err)
290
288
  skipped++
291
289
  }
292
290
  }
@@ -294,7 +292,7 @@ window.__ModuleLoader__.load({
294
292
  let inserted = 0
295
293
  if (mentions.length > 0) {
296
294
  const ta = findComposerTextarea()
297
- if (ta === null) announce('未找到可用的输入框,无法插入文件引用')
295
+ if (ta === null) announce('输入框不可用')
298
296
  else { insertTextAtCaret(ta, actions, mentions.join(' ')); inserted = mentions.length }
299
297
  }
300
298
 
@@ -318,32 +316,14 @@ window.__ModuleLoader__.load({
318
316
  if (inserted === 0 && imagesAdded === 0) {
319
317
  if (savedCount > 0 && skipped === 0) return
320
318
  if (dirRejected.length > 0 && skipped === dirRejected.length && savedCount === 0) {
321
- announce('不支持拖入目录:' + dirRejected.slice(0, 3).join('、') + (dirRejected.length > 3 ? ' 等' : '') + '。仅支持文档文件')
319
+ announce('不支持拖入目录')
322
320
  return
323
321
  }
324
- announce('文件处理失败:' + (lastErr !== '' ? lastErr : '全部文件不可用') + '(共 ' + docs.length + ' 个)')
322
+ announce('文件处理失败')
325
323
  return
326
324
  }
327
-
328
- let text = ''
329
- if (inserted > 0) {
330
- text = '已插入 ' + inserted + ' 个文件引用'
331
- if (savedCount > 0) {
332
- text += '(已保存 ' + savedCount + ' 个文件'
333
- if (savedNames.length > 0) text += ':' + savedNames.slice(0, 3).join('、') + (savedNames.length > 3 ? ' 等' : '')
334
- text += ')'
335
- }
336
- if (savedDir !== '') text += ',保存目录:' + savedDir
337
- }
338
- if (imagesAdded > 0) text += (text === '' ? '' : ',') + imagesAdded + ' 张图片已按原有方式添加'
339
- if (skipped > 0) {
340
- text += '(' + skipped + ' 个已跳过'
341
- if (dirRejected.length > 0) text += ',不支持拖入目录:' + dirRejected.slice(0, 3).join('、') + (dirRejected.length > 3 ? ' 等' : '')
342
- if (dirRejected.length < skipped) text += ',超 50MB、不可读或保存失败'
343
- if (lastErr !== '') text += ';原因:' + lastErr
344
- text += ')'
345
- }
346
- announce(text)
325
+ // 至少登记了一个引用/图片即成功:完全成功不提示,部分跳过仅简短提示
326
+ if (skipped > 0) announce(skipped + ' 个已跳过')
347
327
  }
348
328
 
349
329
  // document 级 drop(capture):保证先于图片路径的 document 级 bubble 监听器执行
@@ -403,10 +383,11 @@ window.__ModuleLoader__.load({
403
383
  }, text)
404
384
  }
405
385
 
406
- // composer.dock 槽:仅同步桥(actions/input/sessionId/conversation),不渲染可见文本。
386
+ // input.dock 槽:比 composer.dock 更早且不受 hero(首轮空白无底部栏)限制,
387
+ // 仅同步桥(actions/input/sessionId/conversation),不渲染可见文本。
407
388
  function FaBridge(props) {
408
- const actions = props.inputActions !== void 0 ? props.inputActions : null
409
- const input = props.input !== void 0 ? props.input : undefined
389
+ const actions = props.shell !== void 0 ? props.shell.actions : null
390
+ const input = props.shell !== void 0 ? props.shell.snapshot : undefined
410
391
  const sessionId = props.sessionId !== void 0 ? props.sessionId : ''
411
392
  react.useEffect(() => {
412
393
  bridge.actions = actions
@@ -427,8 +408,26 @@ window.__ModuleLoader__.load({
427
408
  ctxRef = ctx
428
409
  const slots = ctx.get('slots')
429
410
  if (slots !== undefined) {
430
- slots.inject('conversation.composer.dock', () => slots.register(
431
- { name: 'conversation.composer.dock', id: 'dsh-file-attachment', order: 300 },
411
+ // input.dock:hero(首轮空白无底部栏)态也渲染;inject 在渲染前经会话作用域直取输入机 shell
412
+ slots.inject('conversation.input.dock', () => slots.register(
413
+ {
414
+ name: 'conversation.input.dock',
415
+ id: 'dsh-file-attachment',
416
+ order: 300,
417
+ inject: (sessionId) => {
418
+ // 无会话(纯 hero 空白)无输入机可挂;有会话(含首轮空白态)则取 shell 作 props
419
+ if (sessionId === void 0) return {}
420
+ try {
421
+ const actx = ctx.sessions.scope(sessionId)
422
+ if (actx === void 0) return {}
423
+ const conversation = actx.get('conversation')
424
+ if (conversation === void 0 || conversation.input === void 0) return {}
425
+ return { shell: conversation.input.for(actx), sessionId }
426
+ } catch (err) {
427
+ return {}
428
+ }
429
+ }
430
+ },
432
431
  FaBridge
433
432
  ))
434
433
  slots.inject('shell.overlay', () => slots.register(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wszhoho/dsh-file-attachment",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "拖拽/粘贴文件(支持多文件)进输入框:图片沿用原有草稿图片流程;文档读取内容存入项目根 .dsh-file-attachment 临时目录并插入 @绝对路径引用",
5
5
  "keywords": [
6
6
  "dsh",