@wszhoho/dsh-file-attachment 0.3.4 → 0.4.0
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 +12 -9
- package/lib/client.js +52 -10
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,29 +4,28 @@ DeepSeek Harness (dsh) web GUI 插件:在会话输入框中**拖入或 Ctrl+V
|
|
|
4
4
|
|
|
5
5
|
图片文件由插件接管后走 dsh web 既有草稿图片流程(`addImages`):不落盘、不进文件条、不缩放;仅当输入框未就绪且为纯图片时,才原样交回原生。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
另外,输入框工具栏(加号之后)提供了一个**上传按钮(📎 回形针图标)**,点击后弹出系统文件选择框,可多选文档/图片,复用与拖入/粘贴完全一致的落盘 + `@路径` 插入管线。PC 与移动端浏览器均适用(移动端走原生文件/拍照选择器)。
|
|
8
8
|
|
|
9
9
|
## 界面
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**演示**:拖入/粘贴文档 → 落盘并在输入框插入 `@` 引用 → 输入框上方 dock 文件条待发送状态(完整流程见 gif)。
|
|
12
12
|
|
|
13
|
-

|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-

|
|
15
|
+
**上传按钮**:输入框工具栏 `+` 之后的 📎 回形针图标,点击弹出系统文件选择框(可多选文档/图片)。
|
|
18
16
|
|
|
19
17
|
## 行为一览
|
|
20
18
|
|
|
21
19
|
| 操作 | 行为 |
|
|
22
20
|
| --- | --- |
|
|
23
|
-
|
|
|
21
|
+
| 点击**上传按钮(📎)**,在系统文件选择框中**多选**文档/图片 | 与拖入/粘贴同一管线:文档 → 落盘 + 光标插入全部 `@` 路径;图片 → 走既有草稿图片流程;`accept` 限定图片 + 常见文档扩展名,移动端自动弹出文件/拍照选择器 |
|
|
24
22
|
| 拖入/粘贴 **文档/代码/配置文件**(非图片,默认 doc·code·config 三类扩展名,如 docx/xlsx/pdf/md/txt/js/json) | 浏览器读全文 → base64 → 宿主落盘 `<会话工作区>/.dsh-file-attachment/<日期>/<文件名>` → 光标插入 `@绝对路径`,成功不提示、失败简短 toast |
|
|
25
23
|
| 一次拖入/粘贴 **多个文件**(支持多文件) | 批量处理:逐个读取全文 → base64 → 落盘 → 在光标处一次性插入全部 `@` 引用;全部成功不提示,部分失败仅简短提示「N 个已跳过」 |
|
|
26
24
|
| 拖入 **目录** | 拒绝,toast 提示「不支持拖入目录」,不插入引用 |
|
|
27
25
|
| 拖入/粘贴 **图片** | 接管后走 dsh 既有草稿图片流程(`addImages`):不落盘、不进文件条、不缩放;输入框未就绪且纯图片时原样交原生 |
|
|
28
26
|
| 粘贴 **纯文本**(含从地址栏复制的目录路径字符串) | 不改写,完全原生行为 |
|
|
29
27
|
| 重复拖入同名文件(同一天内) | 追加 `-1`、`-2` 序号,绝不覆盖 |
|
|
28
|
+
| 点文件条(dock)`×` 移除 | 移除 dock 条目,并清除输入框中指向该文件路径的**全部** `@` 引用 chip(同一文件拖入多次产生的多个 chip 一并清除);其他文件的引用不受影响 |
|
|
30
29
|
| 单文件 > 50MB | 跳过并提示 |
|
|
31
30
|
|
|
32
31
|
### 文件命名
|
|
@@ -85,18 +84,22 @@ packages/dsh-file-attachment/
|
|
|
85
84
|
|
|
86
85
|
- **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}}`。
|
|
87
86
|
- **Client 半**:`conversation.input.dock`(list 槽,同步桥状态,不渲染文本;经会话作用域取输入机,hero 首轮空白态也挂载)+ `shell.overlay`(list 槽,toast 通知展示)+ `conversation.input.left`(list 槽,**上传按钮**,渲染为左动作簇第 3 项),document 级 capture 监听先于应用 bubble 监听执行。
|
|
88
|
-
- **上传按钮**:`FaUploadButton` 组件注入 `conversation.input.left`(`order:10`),内部是隐藏 `<input type="file" multiple accept>`(`accept` 限定 `image/*` + 常见文档扩展名),点击触发原生选择框;`onChange` 按 `file.type` 是否为 `image/` 分派到 `runBatch`(文档落盘插 `@`、图片走草稿图片流程),完成后清空 `input.value` 以便重复选择同一文件。图标为内联 SVG
|
|
87
|
+
- **上传按钮**:`FaUploadButton` 组件注入 `conversation.input.left`(`order:10`),内部是隐藏 `<input type="file" multiple accept>`(`accept` 限定 `image/*` + 常见文档扩展名),点击触发原生选择框;`onChange` 按 `file.type` 是否为 `image/` 分派到 `runBatch`(文档落盘插 `@`、图片走草稿图片流程),完成后清空 `input.value` 以便重复选择同一文件。图标为内联 SVG 回形针(📎,`stroke=currentColor`,随主题变色)。
|
|
89
88
|
- **通知**:`shell.overlay` frame-wide 浮动 toast(不参与文档流,不破坏布局),4 秒自动消失。
|
|
90
89
|
- **浮层**:拖入任何文件时 capture 阶段拦截应用自带「拖入图片…」DropOverlay(文案面向图片,对文档是误导)。
|
|
91
90
|
|
|
92
91
|
## 开发说明
|
|
93
92
|
|
|
94
93
|
- `lib/*.js`、`package.json`、`cordis.patch.yml`、`README.md` 当前均为 **UTF-8 无 BOM**(以仓库实测为准);编辑时保持原编码,不引入 BOM。
|
|
95
|
-
- Host 半用 `webServer.register({ kind: 'prefix', path: '/dsh-file-attachment', handler })`
|
|
94
|
+
- Host 半用 `webServer.register({ kind: 'prefix', path: '/dsh-file-attachment', handler })` 收文件,纯 ESM 无构建,不依赖装饰器/远程反射。
|
|
96
95
|
- Client 半保存用 `fetch('/dsh-file-attachment/save', { method: 'POST', body: JSON.stringify({ name, data, sessionId }) })`,信封为 `{ ok, value | error }`。
|
|
97
96
|
- 项目根 = 会话工作区(`session.header.cwd` → `sandboxPolicy.workspaceRoot` → `process.cwd()` 兜底)。
|
|
98
97
|
- 50MB 上限两侧一致(client 跳过 + host 校验)。
|
|
99
98
|
|
|
99
|
+
## Star
|
|
100
|
+
|
|
101
|
+
如果这个插件帮到了你,欢迎到 [GitHub 仓库](https://github.com/wszhoho/dsh-file-attachment) 点个 ⭐ Star,感谢支持!
|
|
102
|
+
|
|
100
103
|
## License
|
|
101
104
|
|
|
102
105
|
MIT
|
package/lib/client.js
CHANGED
|
@@ -16,7 +16,7 @@ window.__ModuleLoader__.load({
|
|
|
16
16
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
17
17
|
const react = require('react')
|
|
18
18
|
|
|
19
|
-
// Client 服务依赖:槽位 / 输入机 /
|
|
19
|
+
// Client 服务依赖:槽位 / 输入机 / 会话。
|
|
20
20
|
// 保存文件不走 remote,而是 fetch POST 宿主 webServer 路由 /dsh-file-attachment/save。
|
|
21
21
|
const inject = [
|
|
22
22
|
"slots",
|
|
@@ -131,7 +131,42 @@ window.__ModuleLoader__.load({
|
|
|
131
131
|
if (changed) emitAttached()
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
//
|
|
134
|
+
// 文件条移除时清除草稿中指向给定路径的全部 @引用 chip(同一文件拖多次会留多个 chip,需全清)。
|
|
135
|
+
// 兼容两代 dsh 输入机:旧版(Lexical)draft 是 clipboard 投影(chip 展开为 @路径 文本),
|
|
136
|
+
// 新版(纯文本机,chip 外观是文本上的扫描装饰)draft 是纯文本——两代里 occurrence 的
|
|
137
|
+
// [offset, offset+length) 都是 snap.draft 内的文本区间,故统一 setDraft 整文拼接删除
|
|
138
|
+
//(新版 consumeToken 的 span 分支内部本就是 setDraft 拼接)。每轮删一个目标区间
|
|
139
|
+
//(连带 chip 后机器自动补的尾随空格 gap),draftRev 递增后重读 snapshot,循环至无目标。
|
|
140
|
+
function removeDraftRefs(path) {
|
|
141
|
+
const shell = bridge.shell
|
|
142
|
+
if (shell === null || typeof path !== 'string' || path === '') return
|
|
143
|
+
if (typeof shell.setDraft !== 'function') return
|
|
144
|
+
const plain = '@' + path
|
|
145
|
+
const quoted = '@"' + path + '"'
|
|
146
|
+
const isTarget = (oc) => oc.ref === plain || oc.ref === quoted
|
|
147
|
+
|| (oc.clipboardText !== void 0 && (oc.clipboardText === plain || oc.clipboardText === quoted))
|
|
148
|
+
for (let n = 0; n < 64; n++) {
|
|
149
|
+
const snap = shell.snapshot
|
|
150
|
+
if (snap === null || snap === void 0 || typeof snap.draft !== 'string') break
|
|
151
|
+
// 发送进行中不动草稿(此时 occurrence 会随发送完成清空)
|
|
152
|
+
if (snap.phase === 'adjudicating' || snap.phase === 'submitting') break
|
|
153
|
+
// 取第一个有效(类型+边界健全)且指向目标路径的 occurrence,边界失配直接跳过防误伤用户文本
|
|
154
|
+
const occs = snap.occurrences || []
|
|
155
|
+
let oc = null
|
|
156
|
+
for (let i = 0; i < occs.length; i++) {
|
|
157
|
+
const o = occs[i]
|
|
158
|
+
if (typeof o.offset !== 'number' || typeof o.length !== 'number' || o.length <= 0) continue
|
|
159
|
+
if (o.offset < 0 || o.offset + o.length > snap.draft.length) continue
|
|
160
|
+
if (isTarget(o)) { oc = o; break }
|
|
161
|
+
}
|
|
162
|
+
if (oc === null) break
|
|
163
|
+
let end = oc.offset + oc.length
|
|
164
|
+
if (snap.draft.charCodeAt(end) === 32) end += 1 // 吃掉插入 chip 时机器自动补的尾随空格 gap
|
|
165
|
+
shell.setDraft(snap.draft.slice(0, oc.offset) + snap.draft.slice(end))
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---- 宿主保存调用:fetch POST 宿主 webServer 路由----
|
|
135
170
|
async function saveFileToHost(fileName, b64, sessionId) {
|
|
136
171
|
if (typeof fetch !== 'function') throw new Error('当前环境无 fetch,无法上传')
|
|
137
172
|
let response
|
|
@@ -637,7 +672,11 @@ window.__ModuleLoader__.load({
|
|
|
637
672
|
style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0 },
|
|
638
673
|
}, e.name),
|
|
639
674
|
react.createElement('span', {
|
|
640
|
-
onClick: () =>
|
|
675
|
+
onClick: () => {
|
|
676
|
+
// 先清草稿中指向该文件的全部 @引用 chip,再移除文件条条目
|
|
677
|
+
try { removeDraftRefs(e.path) } catch (err) { /* 忽略:引用清理失败不阻塞移除 */ }
|
|
678
|
+
detachFile(e.id)
|
|
679
|
+
},
|
|
641
680
|
title: '移除',
|
|
642
681
|
style: { cursor: 'pointer', opacity: 0.6, fontSize: '14px', lineHeight: '1', flex: 'none', padding: '0 2px' },
|
|
643
682
|
}, '×'),
|
|
@@ -704,20 +743,23 @@ window.__ModuleLoader__.load({
|
|
|
704
743
|
style: {
|
|
705
744
|
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
|
706
745
|
width: '28px', height: '28px', padding: 0, margin: 0, border: 'none',
|
|
707
|
-
|
|
708
|
-
|
|
746
|
+
// 与 dsh 输入栏加号按钮同款(InputBar.module.css .add):全圆 + selector 填充,无阴影
|
|
747
|
+
background: 'var(--dsw-specific-selector, rgba(128,128,128,0.10))',
|
|
748
|
+
color: 'var(--dsw-alias-label-primary, currentColor)',
|
|
749
|
+
cursor: busy ? 'default' : 'pointer', borderRadius: '999px',
|
|
709
750
|
opacity: busy ? 0.45 : 1, transition: 'background 120ms ease, opacity 120ms ease',
|
|
710
751
|
},
|
|
711
|
-
onMouseEnter: (ev) => { if (!busy) ev.currentTarget.style.background = 'rgba(128,128,128,0.16)' },
|
|
712
|
-
onMouseLeave: (ev) => { ev.currentTarget.style.background = '
|
|
752
|
+
onMouseEnter: (ev) => { if (!busy) ev.currentTarget.style.background = 'var(--dsw-alias-interactive-bg-hover-solid, rgba(128,128,128,0.16))' },
|
|
753
|
+
onMouseLeave: (ev) => { ev.currentTarget.style.background = 'var(--dsw-specific-selector, rgba(128,128,128,0.10))' },
|
|
713
754
|
},
|
|
714
755
|
react.createElement('svg', {
|
|
715
|
-
|
|
756
|
+
// 回形针按用户反馈取 12px(比加号 14px 小 2px);描边 2/24×12≈1px
|
|
757
|
+
width: 12, height: 12, viewBox: '0 0 24 24', fill: 'none',
|
|
716
758
|
'aria-hidden': true, style: { display: 'block' },
|
|
717
759
|
},
|
|
718
760
|
react.createElement('path', {
|
|
719
|
-
d: '
|
|
720
|
-
stroke: 'currentColor', strokeWidth:
|
|
761
|
+
d: 'm21.44 12.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48',
|
|
762
|
+
stroke: 'currentColor', strokeWidth: 2,
|
|
721
763
|
strokeLinecap: 'round', strokeLinejoin: 'round',
|
|
722
764
|
})
|
|
723
765
|
)
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @wszhoho/dsh-file-attachment Host 半:webServer HTTP
|
|
1
|
+
// @wszhoho/dsh-file-attachment Host 半:webServer HTTP 路由方案。
|
|
2
2
|
// Client 半读取文件全文(base64)后 POST /dsh-file-attachment/save 到这里,
|
|
3
3
|
// 写入 <会话工作区根>/.dsh-file-attachment/<日期>/<文件名>(日期作子目录,文件名保留原始名)并返回 { path, dir, name, size }。
|
|
4
4
|
// 项目根解析:Client 传 sessionId → sessions.get(sessionId).header.cwd →
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wszhoho/dsh-file-attachment",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "文件附件:输入框上传按钮 + 拖拽/粘贴文件(支持多文件);图片走 dsh 原生图片草稿机制(addImages,不缩放/不落盘),文档落盘到 .dsh-file-attachment 并以芯片 @短名 引用(发送时还原 @绝对路径);文档/代码/配置文件可上传类型可在设置页配置;支持 PC 与移动端浏览器",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|