@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.
- package/README.md +3 -3
- package/lib/client.js +39 -40
- 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/<时间戳>-<文件名>` → 光标插入
|
|
12
|
-
| 一次拖入/粘贴 **多个文件**(支持多文件) | 批量处理:逐个读取全文 → base64 → 落盘 → 在光标处一次性插入全部 `@`
|
|
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
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('
|
|
319
|
+
announce('不支持拖入目录')
|
|
322
320
|
return
|
|
323
321
|
}
|
|
324
|
-
announce('
|
|
322
|
+
announce('文件处理失败')
|
|
325
323
|
return
|
|
326
324
|
}
|
|
327
|
-
|
|
328
|
-
|
|
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
|
|
386
|
+
// input.dock 槽:比 composer.dock 更早且不受 hero(首轮空白无底部栏)限制,
|
|
387
|
+
// 仅同步桥(actions/input/sessionId/conversation),不渲染可见文本。
|
|
407
388
|
function FaBridge(props) {
|
|
408
|
-
const actions = props.
|
|
409
|
-
const input = props.
|
|
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
|
-
|
|
431
|
-
|
|
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(
|