@wszhoho/dsh-file-attachment 0.4.0 → 0.4.1
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 +6 -2
- package/lib/client.js +47 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ DeepSeek Harness (dsh) web GUI 插件:在会话输入框中**拖入或 Ctrl+V
|
|
|
4
4
|
|
|
5
5
|
图片文件由插件接管后走 dsh web 既有草稿图片流程(`addImages`):不落盘、不进文件条、不缩放;仅当输入框未就绪且为纯图片时,才原样交回原生。
|
|
6
6
|
|
|
7
|
-
另外,输入框工具栏(加号之后)提供了一个**上传按钮(📎 回形针图标)**,点击后弹出系统文件选择框,可多选文档/图片,复用与拖入/粘贴完全一致的落盘 + `@路径` 插入管线。PC
|
|
7
|
+
另外,输入框工具栏(加号之后)提供了一个**上传按钮(📎 回形针图标)**,点击后弹出系统文件选择框,可多选文档/图片,复用与拖入/粘贴完全一致的落盘 + `@路径` 插入管线。PC 与移动端浏览器均适用(移动端走原生文件选择器)。
|
|
8
8
|
|
|
9
9
|
## 界面
|
|
10
10
|
|
|
@@ -14,11 +14,15 @@ DeepSeek Harness (dsh) web GUI 插件:在会话输入框中**拖入或 Ctrl+V
|
|
|
14
14
|
|
|
15
15
|
**上传按钮**:输入框工具栏 `+` 之后的 📎 回形针图标,点击弹出系统文件选择框(可多选文档/图片)。
|
|
16
16
|
|
|
17
|
+
**可上传类型设置**:「设置 → 文件附件」页可按 文档 / 代码 / 配置文件 三类增删扩展名(小写、不带点),图片恒可发送。
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
17
21
|
## 行为一览
|
|
18
22
|
|
|
19
23
|
| 操作 | 行为 |
|
|
20
24
|
| --- | --- |
|
|
21
|
-
| 点击**上传按钮(📎)**,在系统文件选择框中**多选**文档/图片 | 与拖入/粘贴同一管线:文档 → 落盘 + 光标插入全部 `@` 路径;图片 → 走既有草稿图片流程;`accept` 限定图片 +
|
|
25
|
+
| 点击**上传按钮(📎)**,在系统文件选择框中**多选**文档/图片 | 与拖入/粘贴同一管线:文档 → 落盘 + 光标插入全部 `@` 路径;图片 → 走既有草稿图片流程;`accept` 限定图片 + 常见文档扩展名,移动端自动弹出文件选择器 |
|
|
22
26
|
| 拖入/粘贴 **文档/代码/配置文件**(非图片,默认 doc·code·config 三类扩展名,如 docx/xlsx/pdf/md/txt/js/json) | 浏览器读全文 → base64 → 宿主落盘 `<会话工作区>/.dsh-file-attachment/<日期>/<文件名>` → 光标插入 `@绝对路径`,成功不提示、失败简短 toast |
|
|
23
27
|
| 一次拖入/粘贴 **多个文件**(支持多文件) | 批量处理:逐个读取全文 → base64 → 落盘 → 在光标处一次性插入全部 `@` 引用;全部成功不提示,部分失败仅简短提示「N 个已跳过」 |
|
|
24
28
|
| 拖入 **目录** | 拒绝,toast 提示「不支持拖入目录」,不插入引用 |
|
package/lib/client.js
CHANGED
|
@@ -146,7 +146,7 @@ window.__ModuleLoader__.load({
|
|
|
146
146
|
const isTarget = (oc) => oc.ref === plain || oc.ref === quoted
|
|
147
147
|
|| (oc.clipboardText !== void 0 && (oc.clipboardText === plain || oc.clipboardText === quoted))
|
|
148
148
|
for (let n = 0; n < 64; n++) {
|
|
149
|
-
const snap = shell
|
|
149
|
+
const snap = readShellSnapshot(shell)
|
|
150
150
|
if (snap === null || snap === void 0 || typeof snap.draft !== 'string') break
|
|
151
151
|
// 发送进行中不动草稿(此时 occurrence 会随发送完成清空)
|
|
152
152
|
if (snap.phase === 'adjudicating' || snap.phase === 'submitting') break
|
|
@@ -300,10 +300,25 @@ window.__ModuleLoader__.load({
|
|
|
300
300
|
return seg
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
|
|
303
|
+
// 输入机快照读取:兼容两代 dsh。
|
|
304
|
+
// 旧版(纯文本机,alpha.2 之前):shell.snapshot 直接是 InputState;
|
|
305
|
+
// 新版(Lexical composer,alpha.2 起):shell.state 是 SnapshotStore,经
|
|
306
|
+
// getSnapshot() 取值(字段不变:draft/draftRev/phase/occurrences)。
|
|
307
|
+
function readShellSnapshot(shell) {
|
|
308
|
+
if (shell === null || shell === void 0) return undefined
|
|
309
|
+
const st = shell.state
|
|
310
|
+
if (st !== null && st !== void 0 && typeof st.getSnapshot === 'function') {
|
|
311
|
+
return st.getSnapshot()
|
|
312
|
+
}
|
|
313
|
+
return shell.snapshot
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 找可写的 composer 输入表面:可见、未禁用、未只读、phase 为 plain;
|
|
317
|
+
// 多个候选时优先文本与桥内草稿一致的那一个(排除审批弹窗里的同名元素)。
|
|
318
|
+
// 双代兼容:旧版 textarea([data-composer-card] textarea);
|
|
319
|
+
// 新版 Lexical contenteditable([data-composer-card] [data-composer-input])。
|
|
320
|
+
function findComposerInput() {
|
|
321
|
+
const list = Array.from(document.querySelectorAll('[data-composer-card] textarea, [data-composer-card] [data-composer-input]'))
|
|
307
322
|
const live = list.filter((ta) => {
|
|
308
323
|
if (ta.disabled || ta.readOnly) return false
|
|
309
324
|
const phase = ta.getAttribute('data-phase')
|
|
@@ -312,9 +327,10 @@ window.__ModuleLoader__.load({
|
|
|
312
327
|
})
|
|
313
328
|
if (live.length === 0) return null
|
|
314
329
|
const draft = bridge.input !== void 0 ? bridge.input.draft : undefined
|
|
330
|
+
const textOf = (el) => (el.tagName === 'TEXTAREA' ? el.value : (el.textContent || ''))
|
|
315
331
|
if (typeof draft === 'string') {
|
|
316
332
|
for (let i = 0; i < live.length; i++) {
|
|
317
|
-
if (live[i]
|
|
333
|
+
if (textOf(live[i]) === draft) return live[i]
|
|
318
334
|
}
|
|
319
335
|
}
|
|
320
336
|
return live[0]
|
|
@@ -430,7 +446,7 @@ window.__ModuleLoader__.load({
|
|
|
430
446
|
announce(`输入框不可用 [mounted:${bridge.mounted} shell:${shell === null ? 'null' : 'ok'} addImg:${typeof bridge.addImages}]`)
|
|
431
447
|
return
|
|
432
448
|
}
|
|
433
|
-
const snap0 = shell
|
|
449
|
+
const snap0 = readShellSnapshot(shell)
|
|
434
450
|
const phase = snap0 ? snap0.phase : 'plain'
|
|
435
451
|
if (phase !== 'plain' && phase !== 'command' && phase !== 'claimed') {
|
|
436
452
|
announce('输入框正忙')
|
|
@@ -484,8 +500,24 @@ window.__ModuleLoader__.load({
|
|
|
484
500
|
const m = formatMention(res.path, false)
|
|
485
501
|
const clipboardText = m !== undefined ? m : '@' + res.path
|
|
486
502
|
// 每次插入前重取 snapshot(draftRev 随插入递增,span 取当前末尾)
|
|
487
|
-
const snap = shell
|
|
488
|
-
|
|
503
|
+
const snap = readShellSnapshot(shell)
|
|
504
|
+
// 前导分隔空格:聊天记录对 @引用 的 file-chip 渲染(projectUserText)
|
|
505
|
+
// 要求 @ 前是行首或空白,而 insertReference 只在 chip 后补空格。
|
|
506
|
+
// 草稿末尾紧贴文字时先补一格,否则发送后聊天记录显示裸 @路径
|
|
507
|
+
//(旧版 insertTextAtCaret 的 padStart 同款保底)。
|
|
508
|
+
if (snap !== void 0 && typeof snap.draft === 'string' && snap.draft !== ''
|
|
509
|
+
&& !isSpace(snap.draft.charCodeAt(snap.draft.length - 1))) {
|
|
510
|
+
const leadSpan = { draftRev: snap.draftRev, start: snap.draft.length, end: snap.draft.length }
|
|
511
|
+
if (typeof shell.insertText === 'function') {
|
|
512
|
+
try { shell.insertText(' ', leadSpan) } catch (err) { /* 忽略:补空格失败不阻塞插入 */ }
|
|
513
|
+
} else if (typeof shell.setDraft === 'function') {
|
|
514
|
+
// 含 chip 的草稿末尾必为空白(chip 自动尾随空格),故此处必为纯文本,拼接安全
|
|
515
|
+
try { shell.setDraft(snap.draft + ' ') } catch (err) { /* 忽略 */ }
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
// 补空格会递增 draftRev,重新取快照后再定插入 span
|
|
519
|
+
const snap2 = readShellSnapshot(shell)
|
|
520
|
+
const span = { draftRev: snap2.draftRev, start: snap2.draft.length, end: snap2.draft.length }
|
|
489
521
|
const okInsert = shell.insertReference(
|
|
490
522
|
{
|
|
491
523
|
source: 'reference', // 复用内置已注册的 @引用 source(带 codec.serialize),否则报 no serializer
|
|
@@ -523,12 +555,12 @@ window.__ModuleLoader__.load({
|
|
|
523
555
|
handleFilesEvent(e, files, dt.items, 'drop')
|
|
524
556
|
}
|
|
525
557
|
|
|
526
|
-
// document 级 paste(capture):仅当粘贴目标就是 composer
|
|
558
|
+
// document 级 paste(capture):仅当粘贴目标就是 composer 输入表面时接管文件;
|
|
527
559
|
// 纯文本(含目录路径字符串)完全原生,不改写。
|
|
528
560
|
function onPaste(e) {
|
|
529
561
|
const t = e.target
|
|
530
|
-
const ta =
|
|
531
|
-
if (ta === null || t !== ta) return
|
|
562
|
+
const ta = findComposerInput()
|
|
563
|
+
if (ta === null || (t !== ta && !(ta.contains && ta.contains(t)))) return
|
|
532
564
|
const cd = e.clipboardData
|
|
533
565
|
if (cd === null || cd === void 0) return
|
|
534
566
|
const files = Array.prototype.slice.call(cd.files)
|
|
@@ -599,7 +631,7 @@ window.__ModuleLoader__.load({
|
|
|
599
631
|
}
|
|
600
632
|
}
|
|
601
633
|
} catch (err) { shell = null; addImages = null }
|
|
602
|
-
const input = shell
|
|
634
|
+
const input = readShellSnapshot(shell)
|
|
603
635
|
react.useEffect(() => {
|
|
604
636
|
bridge.shell = shell
|
|
605
637
|
bridge.addImages = addImages
|
|
@@ -635,12 +667,12 @@ window.__ModuleLoader__.load({
|
|
|
635
667
|
attachedListeners.add(fn)
|
|
636
668
|
return () => { attachedListeners.delete(fn) }
|
|
637
669
|
}, [])
|
|
638
|
-
//
|
|
670
|
+
// 发送后清空:轮询输入机快照,occurrences 从有→无 即一次发送完成
|
|
639
671
|
const hadOcc = react.useRef(false)
|
|
640
672
|
react.useEffect(() => {
|
|
641
673
|
const timer = setInterval(() => {
|
|
642
674
|
const shell = bridge.shell
|
|
643
|
-
const snap = shell
|
|
675
|
+
const snap = readShellSnapshot(shell)
|
|
644
676
|
const occ = snap ? snap.occurrences : null
|
|
645
677
|
const has = !!(occ && occ.length > 0)
|
|
646
678
|
const s = bridge.sessionId
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wszhoho/dsh-file-attachment",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "文件附件:输入框上传按钮 + 拖拽/粘贴文件(支持多文件);图片走 dsh 原生图片草稿机制(addImages,不缩放/不落盘),文档落盘到 .dsh-file-attachment 并以芯片 @短名 引用(发送时还原 @绝对路径);文档/代码/配置文件可上传类型可在设置页配置;支持 PC 与移动端浏览器",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|