@wszhoho/dsh-file-attachment 0.5.4 → 0.5.6
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/lib/client.js +89 -16
- package/lib/index.js +47 -0
- package/package.json +3 -2
package/lib/client.js
CHANGED
|
@@ -1197,18 +1197,18 @@ window.__ModuleLoader__.load({
|
|
|
1197
1197
|
react.createElement(FaExtGroup, { title: t('doc.title'), list: doc, setList: setDoc, input: docIn, setInput: setDocIn }),
|
|
1198
1198
|
react.createElement(FaExtGroup, { title: t('code.title'), list: code, setList: setCode, input: codeIn, setInput: setCodeIn }),
|
|
1199
1199
|
react.createElement(FaExtGroup, { title: t('config.title'), list: config, setList: setConfig, input: cfgIn, setInput: setCfgIn }),
|
|
1200
|
-
react.createElement('div', { style: { margin: '16px 0', padding: '12px', borderRadius: '8px', background: 'rgba(128,128,128,0.08)', border: '1px solid rgba(128,128,128,0.15)' } },
|
|
1201
|
-
react.createElement('div', { style: { marginBottom: '8px', fontSize: '13px', fontWeight: '600' } }, t('vlm.title')),
|
|
1202
|
-
react.createElement('div', { style: { marginBottom: '10px', fontSize: '12px', color: 'rgba(128,128,128,0.9)' } }, t('vlm.hint')),
|
|
1200
|
+
react.createElement('div', { style: { margin: '16px 0', padding: '12px', borderRadius: '8px', background: 'var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.08))', border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.15))' } },
|
|
1201
|
+
react.createElement('div', { style: { marginBottom: '8px', fontSize: '13px', fontWeight: '600', color: 'var(--dsw-alias-label-primary, inherit)' } }, t('vlm.title')),
|
|
1202
|
+
react.createElement('div', { style: { marginBottom: '10px', fontSize: '12px', color: 'var(--dsw-alias-label-tertiary, rgba(128,128,128,0.9))' } }, t('vlm.hint')),
|
|
1203
1203
|
react.createElement('div', { style: { display: 'grid', gridTemplateColumns: '110px 1fr', gap: '8px 10px', alignItems: 'center' } },
|
|
1204
1204
|
react.createElement('span', { style: { fontSize: '12px' } }, t('vlm.baseURL')),
|
|
1205
|
-
react.createElement('input', { value: vlmBaseURL, onChange: (e) => setVlmBaseURL(e.target.value), placeholder: 'https://api.xiaomimimo.com/v1', style: { padding: '6px 8px', borderRadius: '6px', border: '1px solid rgba(128,128,128,0.3)', background: 'transparent', color: 'inherit', fontSize: '12px' } }),
|
|
1205
|
+
react.createElement('input', { value: vlmBaseURL, onChange: (e) => setVlmBaseURL(e.target.value), placeholder: 'https://api.xiaomimimo.com/v1', style: { padding: '6px 8px', borderRadius: '6px', border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.3))', background: 'transparent', color: 'inherit', fontSize: '12px' } }),
|
|
1206
1206
|
react.createElement('span', { style: { fontSize: '12px' } }, t('vlm.apiKey')),
|
|
1207
|
-
react.createElement('input', { value: vlmApiKey, onChange: (e) => setVlmApiKey(e.target.value), type: 'password', placeholder: 'sk-...', style: { padding: '6px 8px', borderRadius: '6px', border: '1px solid rgba(128,128,128,0.3)', background: 'transparent', color: 'inherit', fontSize: '12px' } }),
|
|
1207
|
+
react.createElement('input', { value: vlmApiKey, onChange: (e) => setVlmApiKey(e.target.value), type: 'password', placeholder: 'sk-...', style: { padding: '6px 8px', borderRadius: '6px', border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.3))', background: 'transparent', color: 'inherit', fontSize: '12px' } }),
|
|
1208
1208
|
react.createElement('span', { style: { fontSize: '12px' } }, t('vlm.model')),
|
|
1209
|
-
react.createElement('input', { value: vlmModel, onChange: (e) => setVlmModel(e.target.value), placeholder: 'mimo-v2.5', style: { padding: '6px 8px', borderRadius: '6px', border: '1px solid rgba(128,128,128,0.3)', background: 'transparent', color: 'inherit', fontSize: '12px' } }),
|
|
1209
|
+
react.createElement('input', { value: vlmModel, onChange: (e) => setVlmModel(e.target.value), placeholder: 'mimo-v2.5', style: { padding: '6px 8px', borderRadius: '6px', border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.3))', background: 'transparent', color: 'inherit', fontSize: '12px' } }),
|
|
1210
1210
|
react.createElement('span', { style: { fontSize: '12px' } }, t('vlm.timeout')),
|
|
1211
|
-
react.createElement('input', { value: vlmTimeout, onChange: (e) => setVlmTimeout(e.target.value), placeholder: '60', title: t('vlm.timeoutHint'), style: { padding: '6px 8px', borderRadius: '6px', border: '1px solid rgba(128,128,128,0.3)', background: 'transparent', color: 'inherit', fontSize: '12px' } }),
|
|
1211
|
+
react.createElement('input', { value: vlmTimeout, onChange: (e) => setVlmTimeout(e.target.value), placeholder: '60', title: t('vlm.timeoutHint'), style: { padding: '6px 8px', borderRadius: '6px', border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.3))', background: 'transparent', color: 'inherit', fontSize: '12px' } }),
|
|
1212
1212
|
react.createElement('span', { style: { fontSize: '12px' } }),
|
|
1213
1213
|
react.createElement('label', { style: { display: 'inline-flex', alignItems: 'center', gap: '6px', fontSize: '12px' } },
|
|
1214
1214
|
react.createElement('input', { type: 'checkbox', checked: vlmThinking, onChange: (e) => setVlmThinking(e.target.checked) }),
|
|
@@ -1230,6 +1230,73 @@ window.__ModuleLoader__.load({
|
|
|
1230
1230
|
)
|
|
1231
1231
|
}
|
|
1232
1232
|
|
|
1233
|
+
// 插件配置 tab 卡片:自渲染标题 + 描述 + 展开/收起,展开后承载原有设置表单(FaSettingsPage)。
|
|
1234
|
+
// 「插件配置」tab 只派发 settings.plugin.item 槽位,卡片外观完全由插件自绘
|
|
1235
|
+
//(与自动继续/插件市场同形);表单内部仍走 /config 接口读写真实配置。
|
|
1236
|
+
function FaSettingsCard() {
|
|
1237
|
+
const [open, setOpen] = react.useState(false)
|
|
1238
|
+
const [hover, setHover] = react.useState(false)
|
|
1239
|
+
const [version, setVersion] = react.useState(null)
|
|
1240
|
+
react.useEffect(() => {
|
|
1241
|
+
fetch('/dsh-file-attachment/version').then(r => r.json()).then(d => {
|
|
1242
|
+
if (d.ok && d.version) setVersion(d.version)
|
|
1243
|
+
}).catch(() => { /* ignore */ })
|
|
1244
|
+
}, [])
|
|
1245
|
+
function t(key) {
|
|
1246
|
+
if (ctxRef !== null && ctxRef.locale !== void 0 && typeof ctxRef.locale.bind === 'function') {
|
|
1247
|
+
try { return ctxRef.locale.bind('dsh-file-attachment')(key) } catch (err) { /* 回退 key */ }
|
|
1248
|
+
}
|
|
1249
|
+
return key
|
|
1250
|
+
}
|
|
1251
|
+
const borderActive = open || hover
|
|
1252
|
+
return react.createElement('div', {
|
|
1253
|
+
onMouseEnter: () => setHover(true),
|
|
1254
|
+
onMouseLeave: () => setHover(false),
|
|
1255
|
+
style: {
|
|
1256
|
+
borderRadius: '16px',
|
|
1257
|
+
border: `0.5px solid ${borderActive ? 'var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l4))' : 'var(--dsw-alias-border-l4)'}`,
|
|
1258
|
+
background: open ? 'var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.06))' : 'var(--dsw-alias-bg-layer-3, rgba(128,128,128,0.04))',
|
|
1259
|
+
overflow: 'hidden',
|
|
1260
|
+
transition: 'border-color 0.16s, background 0.16s',
|
|
1261
|
+
},
|
|
1262
|
+
},
|
|
1263
|
+
react.createElement('button', {
|
|
1264
|
+
type: 'button',
|
|
1265
|
+
'aria-expanded': open,
|
|
1266
|
+
onClick: () => setOpen(!open),
|
|
1267
|
+
style: {
|
|
1268
|
+
display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '12px',
|
|
1269
|
+
width: '100%', padding: '14px 16px',
|
|
1270
|
+
background: 'none', border: 'none', cursor: 'pointer',
|
|
1271
|
+
color: 'inherit', textAlign: 'left', boxSizing: 'border-box',
|
|
1272
|
+
},
|
|
1273
|
+
},
|
|
1274
|
+
react.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '4px', minWidth: 0, flex: 1 } },
|
|
1275
|
+
react.createElement('div', { style: { fontSize: '15px', fontWeight: 600, lineHeight: '1.4', color: 'var(--dsw-alias-label-primary, inherit)' } },
|
|
1276
|
+
t('card.title'),
|
|
1277
|
+
version != null ? react.createElement('span', { style: { marginLeft: '1em', fontSize: '11px', fontWeight: 400, color: 'var(--dsw-alias-label-quaternary, rgba(128,128,128,0.55))', verticalAlign: 'middle' } }, 'v' + version) : null,
|
|
1278
|
+
),
|
|
1279
|
+
react.createElement('div', { style: { fontSize: '13px', lineHeight: '1.5', color: 'var(--dsw-alias-label-tertiary, rgba(128,128,128,0.85))' } }, t('card.description')),
|
|
1280
|
+
),
|
|
1281
|
+
react.createElement('span', {
|
|
1282
|
+
style: {
|
|
1283
|
+
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
|
1284
|
+
color: 'var(--dsw-alias-label-tertiary, rgba(128,128,128,0.7))',
|
|
1285
|
+
flex: 'none',
|
|
1286
|
+
transition: 'transform 0.16s',
|
|
1287
|
+
transform: open ? 'rotate(180deg)' : 'none',
|
|
1288
|
+
},
|
|
1289
|
+
},
|
|
1290
|
+
react.createElement('svg', { width: 14, height: 14, viewBox: '0 0 14 14', 'aria-hidden': true },
|
|
1291
|
+
react.createElement('path', { d: 'M3.5 5.5l3.5 3.5 3.5-3.5', fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round' })),
|
|
1292
|
+
),
|
|
1293
|
+
),
|
|
1294
|
+
open ? react.createElement('div', { style: { borderTop: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.14))', margin: '0 16px', paddingBottom: '8px' } },
|
|
1295
|
+
react.createElement(FaSettingsPage, {}),
|
|
1296
|
+
) : null,
|
|
1297
|
+
)
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1233
1300
|
function apply(ctx) {
|
|
1234
1301
|
ctxRef = ctx
|
|
1235
1302
|
if (typeof console !== 'undefined' && console.log) console.log('[dsh-file-attachment] apply', { hasSlots: ctx.get('slots') !== undefined, hasConversation: ctx.get('conversation') !== undefined })
|
|
@@ -1238,6 +1305,8 @@ window.__ModuleLoader__.load({
|
|
|
1238
1305
|
// 注册设置页文案(zh/en)
|
|
1239
1306
|
ctx.effect(() => ctx.locale.register('dsh-file-attachment', {
|
|
1240
1307
|
zh: {
|
|
1308
|
+
'card.title': '文件附件',
|
|
1309
|
+
'card.description': '拖拽、粘贴或上传文件与图片,可配置文件类型与多模态识别参数。',
|
|
1241
1310
|
'settings.title': '文件附件',
|
|
1242
1311
|
'settings.hint': '用于设置聊天中可发送的文件类型。图片恒可发送;以下三类按扩展名校验文档/代码/配置文件(扩展名不带点、小写,如 pdf、py)。',
|
|
1243
1312
|
'doc.title': '文档',
|
|
@@ -1256,6 +1325,8 @@ window.__ModuleLoader__.load({
|
|
|
1256
1325
|
'saved': '已保存',
|
|
1257
1326
|
},
|
|
1258
1327
|
en: {
|
|
1328
|
+
'card.title': 'File Attachments',
|
|
1329
|
+
'card.description': 'Drag, paste or upload files and images. Configure file types and multimodal recognition.',
|
|
1259
1330
|
'settings.title': 'File Attachments',
|
|
1260
1331
|
'settings.hint': 'Set which file types can be sent in chat. Images are always allowed; the lists below gate documents / code / config files by extension (no dot, lowercase, e.g. pdf, py).',
|
|
1261
1332
|
'doc.title': 'Documents',
|
|
@@ -1312,15 +1383,17 @@ window.__ModuleLoader__.load({
|
|
|
1312
1383
|
{ name: 'shell.overlay', id: 'dsh-file-attachment-lightbox', order: 200 },
|
|
1313
1384
|
FaLightbox
|
|
1314
1385
|
))
|
|
1315
|
-
//
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1386
|
+
// 插件配置 tab 卡片:settings.plugin.item(key = settings 命名空间 'dsh-file-attachment')。
|
|
1387
|
+
// 卡片自渲染标题+描述+展开(见 FaSettingsCard),展开后承载原有设置表单。
|
|
1388
|
+
// 嵌套 inject settingsScope:老版本 host(< 0.1.0-rc.7)无该服务时卡片不出现,
|
|
1389
|
+
// 但不卸载整个插件(graceful degradation,与插件市场同形)。
|
|
1390
|
+
ctx.inject(['settingsScope'], (scoped) => {
|
|
1391
|
+
scoped.slots.inject('settings.plugin.item', () => scoped.slots.register({
|
|
1392
|
+
name: 'settings.plugin.item',
|
|
1393
|
+
key: 'dsh-file-attachment',
|
|
1394
|
+
locale: 'dsh-file-attachment',
|
|
1395
|
+
}, FaSettingsCard))
|
|
1396
|
+
})
|
|
1324
1397
|
}
|
|
1325
1398
|
bridge.conversation = ctx.get('conversation')
|
|
1326
1399
|
// 劫持 dsh 原生📎:让上传入口位于与 dsh 本体一致的位置(hero/composer 两模式同一按钮),
|
package/lib/index.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
import { access, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
8
8
|
import { homedir } from 'node:os'
|
|
9
9
|
import { join, resolve } from 'node:path'
|
|
10
|
+
import { fileURLToPath } from 'node:url'
|
|
10
11
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
12
|
+
import z from '@deepseek-ai/schemastery'
|
|
11
13
|
|
|
12
14
|
/** 本包声明依赖的 Host 服务。 */
|
|
13
15
|
export const name = 'dsh-file-attachment'
|
|
@@ -146,6 +148,32 @@ const VLM_DEFAULTS = {
|
|
|
146
148
|
timeout: 60, // 识别超时(秒)。实测 VLM 延迟波动大(12~73s),30s 会误杀多数请求
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
// 「插件配置」tab 卡片所绑定的 settings 命名空间(kebab-case,匹配 /dsh-file-attachment)。
|
|
152
|
+
const SETTINGS_NS = 'dsh-file-attachment'
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* settings 命名空间的 wire schema:字段自带默认值,描述「可上传类型」与多模态参数结构。
|
|
156
|
+
*
|
|
157
|
+
* 作用:向 host 的 `settings` 服务登记该命名空间,使浏览器半侧的插件配置卡片
|
|
158
|
+
* (settings.plugin.item,key = 本命名空间)被「插件配置」tab 派发渲染。
|
|
159
|
+
*
|
|
160
|
+
* 注意:实际配置仍持久化在 ~/.dsh/file-attachment.json(/config 路由读写),此 schema
|
|
161
|
+
* 仅用于登记命名空间结构(与自动继续插件的 settings.register 同形);卡片内部表单
|
|
162
|
+
* 继续走 /config 接口读写真实配置。
|
|
163
|
+
*/
|
|
164
|
+
const FaAttachmentSchema = z.object({
|
|
165
|
+
doc: z.array(z.string()).default(DEFAULT_TYPES.doc),
|
|
166
|
+
code: z.array(z.string()).default(DEFAULT_TYPES.code),
|
|
167
|
+
config: z.array(z.string()).default(DEFAULT_TYPES.config),
|
|
168
|
+
vlm: z.object({
|
|
169
|
+
baseURL: z.string().default(VLM_DEFAULTS.baseURL),
|
|
170
|
+
apiKey: z.string().default(VLM_DEFAULTS.apiKey),
|
|
171
|
+
model: z.string().default(VLM_DEFAULTS.model),
|
|
172
|
+
thinkingType: z.string().default(VLM_DEFAULTS.thinkingType),
|
|
173
|
+
timeout: z.natural().default(VLM_DEFAULTS.timeout),
|
|
174
|
+
}),
|
|
175
|
+
})
|
|
176
|
+
|
|
149
177
|
/** 规范化多模态参数(缺省/空值回退默认,trim 去空白)。 */
|
|
150
178
|
function normalizeVlm(raw) {
|
|
151
179
|
const r = (raw && typeof raw === 'object') ? raw : {}
|
|
@@ -340,6 +368,18 @@ function registerRoutes(ctx) {
|
|
|
340
368
|
return
|
|
341
369
|
}
|
|
342
370
|
}
|
|
371
|
+
if (req.method === 'GET' && pathname === '/dsh-file-attachment/version') {
|
|
372
|
+
try {
|
|
373
|
+
const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url))
|
|
374
|
+
const raw = await readFile(pkgPath, 'utf8')
|
|
375
|
+
const pkg = JSON.parse(raw)
|
|
376
|
+
json(res, { ok: true, version: pkg.version || '0.0.0' })
|
|
377
|
+
return
|
|
378
|
+
} catch {
|
|
379
|
+
json(res, { ok: false, version: 'unknown' }, 500)
|
|
380
|
+
return
|
|
381
|
+
}
|
|
382
|
+
}
|
|
343
383
|
if (req.method === 'GET' && pathname === '/dsh-file-attachment/config') {
|
|
344
384
|
json(res, { ok: true, value: await readConfig() })
|
|
345
385
|
return
|
|
@@ -482,5 +522,12 @@ function registerTools(ctx) {
|
|
|
482
522
|
export function apply(ctx, config = {}) {
|
|
483
523
|
registerTools(ctx)
|
|
484
524
|
registerRoutes(ctx)
|
|
525
|
+
// 登记「插件配置」tab 的 settings 命名空间:让浏览器半侧的插件配置卡片
|
|
526
|
+
// (settings.plugin.item,key = 本命名空间)被 tab 派发渲染。
|
|
527
|
+
// 老版本 host(无 settings 服务,< 0.1.0-rc.7)此回调不运行,命名空间不登记,
|
|
528
|
+
// 卡片不出现但不卸载整个插件(graceful degradation,与自动继续/插件市场同形)。
|
|
529
|
+
ctx.inject(['settings'], (scopedCtx) => {
|
|
530
|
+
scopedCtx.settings.register(SETTINGS_NS, FaAttachmentSchema, {})
|
|
531
|
+
})
|
|
485
532
|
console.log('[dsh-file-attachment] host loaded (webServer routes + describe_image tool)')
|
|
486
533
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wszhoho/dsh-file-attachment",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "文件附件:拖拽/粘贴/上传文件(支持多文件);图片与文档统一落盘到 .dsh-file-attachment 并以 @绝对路径 引用发送(文本模型可正常使用);输入框内联显示图片缩略图预览与文件条目,聊天区图片同样渲染为可点击放大的缩略图、文件保持芯片样式;非多模态模型下图片自动调用可配置 VLM 识别生成中文描述回填草稿;文档/代码/配置文件可上传类型可在设置页配置;支持 PC 与移动端浏览器",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"node": ">=18"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@deepseek-ai/dsh": ">=0.1.0-rc.1 <0.1.1-0 || >=0.1.1-rc.1 <0.1.2-0 || >=0.1.2-alpha.1 <0.2.0-0"
|
|
37
|
+
"@deepseek-ai/dsh": ">=0.1.0-rc.1 <0.1.1-0 || >=0.1.1-rc.1 <0.1.2-0 || >=0.1.2-alpha.1 <0.2.0-0",
|
|
38
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
38
39
|
},
|
|
39
40
|
"publishConfig": {
|
|
40
41
|
"registry": "https://registry.npmjs.org/"
|