@lyhue1991/dsh-soup 0.5.0 → 0.5.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 +1 -0
- package/lib/client.js +52 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,7 @@ DSH(DeepSeek Harness)功能很强:会话、工具、轨迹、Goal 一应
|
|
|
40
40
|
- **类型图标**:按品牌色区分文件类型(notebook 橙、markdown 紫、json 黄、pdf 红…)。
|
|
41
41
|
- **沙箱围栏**:所有文件操作只能落在你的工作区与会话目录内,越界自动拒绝。
|
|
42
42
|
- **自动刷新**:每 3 秒轻量探测一次目录签名(mtime),有变化才重拉——agent 或终端里的文件增删改无需手动刷新即可出现;面板关闭且无预览时零开销;宿主不可达(如 DSH 重启中)自动指数退避(封顶 60s)。
|
|
43
|
+
- **新建文件夹**:顶部 🗂+ 在当前目录创建文件夹;工具栏顺序为 🗂+ 新建、⬆ 上传、⟳ 刷新、✕ 关闭。
|
|
43
44
|
- **一键刷新**:顶部 ⟳ 重拉整个文件树,已展开的子目录保持展开不收起。
|
|
44
45
|
|
|
45
46
|
### 🫚 会话内文件预览(主打 · 姜 · 去腥增香)
|
package/lib/client.js
CHANGED
|
@@ -98,8 +98,9 @@ window.__ModuleLoader__.load({
|
|
|
98
98
|
'.expl-confirm-title{font-size:14px;font-weight:600;margin-bottom:10px;}.expl-confirm-text{font-size:13px;line-height:20px;overflow-wrap:anywhere;}.expl-confirm-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px;}' +
|
|
99
99
|
'.expl-confirm-btn{height:30px;padding:0 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;font-size:13px;}.expl-confirm-btn:hover{background:var(--dsw-alias-interactive-bg-hover);}.expl-confirm-primary{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-primary);color:#fff;}' +
|
|
100
100
|
'.expl-menu{position:fixed;z-index:1990;min-width:172px;background:var(--dsw-specific-menu);border:1px solid var(--dsw-alias-border-inverted);border-radius:12px;padding:4px;box-shadow:var(--dsw-shadow-lv3);pointer-events:auto;}' +
|
|
101
|
-
'.expl-menu-item{display:flex;align-items:center;
|
|
102
|
-
'.expl-menu-ico{flex:none;display:inline-flex;align-items:center;
|
|
101
|
+
'.expl-menu-item{display:flex;align-items:center;width:100%;text-align:left;background:transparent;border:none;color:var(--dsw-alias-label-primary);font-size:13px;padding:6px 10px;border-radius:8px;cursor:pointer;line-height:1.4;}' +
|
|
102
|
+
'.expl-menu-ico,.expl-menu-ico-ph{flex:none;display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;margin-right:7px;}' +
|
|
103
|
+
'.expl-menu-ico svg{width:14px;height:14px;display:block;}' +
|
|
103
104
|
'.expl-menu-item:hover{background:var(--dsw-alias-interactive-bg-hover);}' +
|
|
104
105
|
'.expl-danger{color:var(--dsw-alias-state-error-primary);} .expl-menu-item.expl-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger);}' +
|
|
105
106
|
'.expl-menu-sep{height:1px;margin:4px 6px;background:var(--dsw-alias-border-l1);}' +
|
|
@@ -119,12 +120,13 @@ window.__ModuleLoader__.load({
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
// ------------------------------------------------------------------
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
123
|
+
// 工具栏图标:刷新图标取自 JupyterLab ui-components(BSD-3-Clause);
|
|
124
|
+
// fill 改为 currentColor 以跟随 DSH 主题 token。
|
|
125
|
+
// 注意:ICON_CLOSE / ICON_UPLOAD 与 GoalBar 段的同名 React 元素常量
|
|
126
|
+
// 冲突(var 提升覆盖),字符样式反而更稳。
|
|
126
127
|
// ------------------------------------------------------------------
|
|
127
128
|
var ICON_REFRESH = '<svg xmlns="http://www.w3.org/2000/svg" width="16" viewBox="0 0 18 18"><path fill="currentColor" d="M9 13.5c-2.49 0-4.5-2.01-4.5-4.5S6.51 4.5 9 4.5c1.24 0 2.36.52 3.17 1.33L10 8h5V3l-1.76 1.76A6 6 0 0 0 9 3C5.69 3 3.01 5.69 3.01 9S5.69 15 9 15a5.98 5.98 0 0 0 5.9-5h-1.52c-.46 2-2.24 3.5-4.38 3.5"/></svg>'
|
|
129
|
+
var ICON_NEW_FOLDER = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-2 7h-3v3h-2v-3h-3v-2h3V8h2v3h3v2z"/></svg>'
|
|
128
130
|
var ICON_MORE = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><circle cx="3" cy="8" r="1.3" fill="currentColor"/><circle cx="8" cy="8" r="1.3" fill="currentColor"/><circle cx="13" cy="8" r="1.3" fill="currentColor"/></svg>'
|
|
129
131
|
var EXPAND_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>'
|
|
130
132
|
var SHRINK_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/></svg>'
|
|
@@ -257,10 +259,13 @@ window.__ModuleLoader__.load({
|
|
|
257
259
|
'explorer.label': '资源管理器',
|
|
258
260
|
'explorer.refresh': '刷新',
|
|
259
261
|
'explorer.refreshTitle': '刷新(保持已展开目录)',
|
|
262
|
+
'explorer.newFolder': '新建文件夹',
|
|
263
|
+
'explorer.newFolderTitle': '在当前目录新建文件夹',
|
|
260
264
|
'explorer.upload': '上传文件',
|
|
261
265
|
'explorer.close': '关闭',
|
|
262
266
|
'explorer.closePanel': '关闭资源管理器',
|
|
263
267
|
'explorer.emptyDir': '(空目录)',
|
|
268
|
+
'explorer.waitCwd': '正在获取会话目录…',
|
|
264
269
|
'explorer.bulkSelected': '已选 {n} 项 · 拖到文件夹可移动',
|
|
265
270
|
'explorer.readFail': '读取失败',
|
|
266
271
|
'explorer.actionFail': '失败',
|
|
@@ -291,7 +296,7 @@ window.__ModuleLoader__.load({
|
|
|
291
296
|
'menu.trash': '🗑 移到废纸篓',
|
|
292
297
|
'menu.trashMulti': '🗑 移到废纸篓 ({n} 项)',
|
|
293
298
|
'menu.newFile': '📄 新建文件',
|
|
294
|
-
'menu.newFolder': '
|
|
299
|
+
'menu.newFolder': '新建文件夹',
|
|
295
300
|
'menu.copyPath': '⧉ 复制路径',
|
|
296
301
|
'menu.download': '⬇ 下载',
|
|
297
302
|
'menu.deselect': '取消选择',
|
|
@@ -325,10 +330,13 @@ window.__ModuleLoader__.load({
|
|
|
325
330
|
'explorer.label': 'Explorer',
|
|
326
331
|
'explorer.refresh': 'Refresh',
|
|
327
332
|
'explorer.refreshTitle': 'Refresh (keeps expanded folders)',
|
|
333
|
+
'explorer.newFolder': 'New folder',
|
|
334
|
+
'explorer.newFolderTitle': 'Create a folder in the current directory',
|
|
328
335
|
'explorer.upload': 'Upload files',
|
|
329
336
|
'explorer.close': 'Close',
|
|
330
337
|
'explorer.closePanel': 'Close explorer',
|
|
331
338
|
'explorer.emptyDir': '(empty)',
|
|
339
|
+
'explorer.waitCwd': 'Waiting for the session directory…',
|
|
332
340
|
'explorer.bulkSelected': '{n} selected · drag onto a folder to move',
|
|
333
341
|
'explorer.readFail': 'Failed to read',
|
|
334
342
|
'explorer.actionFail': 'Failed',
|
|
@@ -359,7 +367,7 @@ window.__ModuleLoader__.load({
|
|
|
359
367
|
'menu.trash': '🗑 Move to trash',
|
|
360
368
|
'menu.trashMulti': '🗑 Move to trash ({n})',
|
|
361
369
|
'menu.newFile': '📄 New file',
|
|
362
|
-
'menu.newFolder': '
|
|
370
|
+
'menu.newFolder': 'New folder',
|
|
363
371
|
'menu.copyPath': '⧉ Copy path',
|
|
364
372
|
'menu.download': '⬇ Download',
|
|
365
373
|
'menu.deselect': 'Clear selection',
|
|
@@ -516,9 +524,15 @@ window.__ModuleLoader__.load({
|
|
|
516
524
|
var res = await rpc('sessionCwd', { sessionId: sessionId })
|
|
517
525
|
if (res && res.ok && res.cwd) return res.cwd
|
|
518
526
|
}
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
|
|
527
|
+
// 已知会话但尚未拿到 cwd 时不要猜路径:猜测 '/' 正好会撞上桌面端
|
|
528
|
+
// 工作区围栏(Desktop 主进程通常从用户主目录启动)。等 useSessions
|
|
529
|
+
// 补上 cwd 后 effect 会重跑。
|
|
530
|
+
if (sessionId) return undefined
|
|
531
|
+
// 无当前会话(例如启动时右栏先挂载)回退到宿主 root;它一定在围栏内,
|
|
532
|
+
// 而文件系统根 '/' 不一定在。
|
|
533
|
+
var rootRes = await rpc('root')
|
|
534
|
+
if (rootRes && rootRes.ok && rootRes.root) return rootRes.root
|
|
535
|
+
return undefined
|
|
522
536
|
}
|
|
523
537
|
|
|
524
538
|
async function trackSession(sessionId, cwdHint) {
|
|
@@ -526,6 +540,10 @@ window.__ModuleLoader__.load({
|
|
|
526
540
|
activeSessionId = sessionId || null
|
|
527
541
|
var cwd = await defaultCwd(sessionId, cwdHint)
|
|
528
542
|
if (generation !== sessionTrackGeneration) return
|
|
543
|
+
if (!cwd) {
|
|
544
|
+
setState({ cwd: '', tree: [], selected: new Set(), renaming: null, newItem: null, menu: null, error: '', notice: '', files: [] })
|
|
545
|
+
return
|
|
546
|
+
}
|
|
529
547
|
// 换项目时展开状态没有跨项目意义;回到同一项目则保留。
|
|
530
548
|
if (expandedDirsCwd !== cwd) {
|
|
531
549
|
expandedDirs.clear()
|
|
@@ -1407,7 +1425,7 @@ window.__ModuleLoader__.load({
|
|
|
1407
1425
|
}
|
|
1408
1426
|
if (node.type === 'directory') {
|
|
1409
1427
|
items.push({ key: 'newfile', label: T('menu.newFile'), onClick: function () { startNew(node.path, false) } })
|
|
1410
|
-
items.push({ key: 'newfolder', label: T('menu.newFolder'), onClick: function () { startNew(node.path, true) }, separatorAfter: true })
|
|
1428
|
+
items.push({ key: 'newfolder', label: [create('span', { className: 'expl-menu-ico', dangerouslySetInnerHTML: { __html: ICON_NEW_FOLDER } }), T('menu.newFolder')], onClick: function () { startNew(node.path, true) }, separatorAfter: true })
|
|
1411
1429
|
}
|
|
1412
1430
|
items.push({ key: 'copy', label: T('menu.copyPath'), onClick: function () { copyPath(node) } })
|
|
1413
1431
|
if (node.type !== 'directory') {
|
|
@@ -1415,7 +1433,7 @@ window.__ModuleLoader__.load({
|
|
|
1415
1433
|
}
|
|
1416
1434
|
} else {
|
|
1417
1435
|
items.push({ key: 'newfile', label: T('menu.newFile'), onClick: function () { startNew(state.cwd, false) } })
|
|
1418
|
-
items.push({ key: 'newfolder', label: T('menu.newFolder'), onClick: function () { startNew(state.cwd, true) } })
|
|
1436
|
+
items.push({ key: 'newfolder', label: [create('span', { className: 'expl-menu-ico', dangerouslySetInnerHTML: { __html: ICON_NEW_FOLDER } }), T('menu.newFolder')], onClick: function () { startNew(state.cwd, true) } })
|
|
1419
1437
|
items.push({ key: 'refresh', label: [create('span', { className: 'expl-menu-ico', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }), T('explorer.refresh')], onClick: refresh, separatorAfter: true })
|
|
1420
1438
|
if (state.selected.size > 0) items.push({ key: 'none', label: T('menu.deselect'), onClick: function () { setState({ selected: new Set(), lastIndex: null }) } })
|
|
1421
1439
|
}
|
|
@@ -1454,11 +1472,16 @@ window.__ModuleLoader__.load({
|
|
|
1454
1472
|
var items = buildMenuItems(menu.node)
|
|
1455
1473
|
var menuChildren = []
|
|
1456
1474
|
items.forEach(function (it) {
|
|
1475
|
+
var children = it.label instanceof Array ? it.label : (function () {
|
|
1476
|
+
var match = it.label.match(/^([\u2190-\u2BFF\u{1F000}-\u{1FAFF}])\s*/u)
|
|
1477
|
+
if (match) return [create('span', { className: 'expl-menu-ico' }, match[1]), it.label.slice(match[0].length)]
|
|
1478
|
+
return [create('span', { className: 'expl-menu-ico-ph' }), it.label]
|
|
1479
|
+
})()
|
|
1457
1480
|
menuChildren.push(create('button', {
|
|
1458
1481
|
key: it.key,
|
|
1459
1482
|
className: 'expl-menu-item' + (it.danger ? ' expl-danger' : ''),
|
|
1460
1483
|
onClick: function () { setState({ menu: null }); it.onClick() },
|
|
1461
|
-
},
|
|
1484
|
+
}, children))
|
|
1462
1485
|
if (it.separatorAfter) menuChildren.push(create('div', { key: it.key + '-sep', className: 'expl-menu-sep' }))
|
|
1463
1486
|
})
|
|
1464
1487
|
return create('div', { ref: menuRef, className: 'expl-menu', style: { left: pos.x, top: pos.y } }, menuChildren)
|
|
@@ -1485,7 +1508,7 @@ window.__ModuleLoader__.load({
|
|
|
1485
1508
|
var widthRef = React.useRef(DETAILS_DEFAULT)
|
|
1486
1509
|
var dragRef = React.useRef({ startX: 0, startW: 0, active: false })
|
|
1487
1510
|
|
|
1488
|
-
React.useEffect(function () { trackSession(sessionId, cwdRef.current) }, [sessionId])
|
|
1511
|
+
React.useEffect(function () { trackSession(sessionId, cwdRef.current) }, [sessionId, liveCwd])
|
|
1489
1512
|
|
|
1490
1513
|
var setFrameWidth = React.useCallback(function (w) {
|
|
1491
1514
|
var frame = frameRef.current
|
|
@@ -1583,7 +1606,8 @@ window.__ModuleLoader__.load({
|
|
|
1583
1606
|
))
|
|
1584
1607
|
}
|
|
1585
1608
|
if (s.tree.length === 0 && !(state.newItem && state.newItem.parent === state.cwd)) {
|
|
1586
|
-
bodyChildren.push(create('div', { key: '__empty__', className: 'expl-muted' },
|
|
1609
|
+
bodyChildren.push(create('div', { key: '__empty__', className: 'expl-muted' },
|
|
1610
|
+
s.cwd ? T('explorer.emptyDir') : T('explorer.waitCwd')))
|
|
1587
1611
|
} else {
|
|
1588
1612
|
s.tree.forEach(function (node) { bodyChildren.push(create(Row, { key: node.path, node: node, depth: 0 })) })
|
|
1589
1613
|
}
|
|
@@ -1604,16 +1628,22 @@ window.__ModuleLoader__.load({
|
|
|
1604
1628
|
create('div', { className: 'expl-head-btns' },
|
|
1605
1629
|
create('button', {
|
|
1606
1630
|
className: 'expl-btn',
|
|
1607
|
-
onClick: function () {
|
|
1608
|
-
title: T('explorer.
|
|
1609
|
-
'aria-label': T('explorer.
|
|
1610
|
-
}, create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html:
|
|
1631
|
+
onClick: function () { startNew(state.cwd, true) },
|
|
1632
|
+
title: T('explorer.newFolderTitle'),
|
|
1633
|
+
'aria-label': T('explorer.newFolder'),
|
|
1634
|
+
}, create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_NEW_FOLDER } })),
|
|
1611
1635
|
create('button', {
|
|
1612
1636
|
className: 'expl-btn',
|
|
1613
1637
|
onClick: function () { if (uploadInputEl) uploadInputEl.click() },
|
|
1614
1638
|
title: T('explorer.upload'),
|
|
1615
1639
|
'aria-label': T('explorer.upload'),
|
|
1616
1640
|
}, '⬆'),
|
|
1641
|
+
create('button', {
|
|
1642
|
+
className: 'expl-btn',
|
|
1643
|
+
onClick: function () { refreshAll() },
|
|
1644
|
+
title: T('explorer.refreshTitle'),
|
|
1645
|
+
'aria-label': T('explorer.refresh'),
|
|
1646
|
+
}, create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } })),
|
|
1617
1647
|
create('button', {
|
|
1618
1648
|
className: 'expl-btn',
|
|
1619
1649
|
onClick: function (event) {
|
|
@@ -1673,7 +1703,7 @@ window.__ModuleLoader__.load({
|
|
|
1673
1703
|
}) : undefined
|
|
1674
1704
|
var cwdRef = React.useRef(liveCwd)
|
|
1675
1705
|
cwdRef.current = liveCwd || cwdRef.current
|
|
1676
|
-
React.useEffect(function () { trackSession(sessionId, cwdRef.current) }, [sessionId])
|
|
1706
|
+
React.useEffect(function () { trackSession(sessionId, cwdRef.current) }, [sessionId, liveCwd])
|
|
1677
1707
|
return create('button', {
|
|
1678
1708
|
type: 'button',
|
|
1679
1709
|
className: 'expl-toggle expl-tool' + (s.open ? ' expl-active' : ''),
|
|
@@ -1707,7 +1737,7 @@ window.__ModuleLoader__.load({
|
|
|
1707
1737
|
}) : undefined
|
|
1708
1738
|
React.useEffect(function () {
|
|
1709
1739
|
if (blank) trackSession(sessionId, liveCwd)
|
|
1710
|
-
}, [blank, sessionId])
|
|
1740
|
+
}, [blank, sessionId, liveCwd])
|
|
1711
1741
|
if (!blank) return null
|
|
1712
1742
|
return create('div', { className: 'expl-hero-dock' },
|
|
1713
1743
|
create('button', {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lyhue1991/dsh-soup",
|
|
3
3
|
"description": "DSH 项目资源管理器:右侧 details 列文件树(并列网格、宽度对齐宿主 details 合同 300-520px),有 Session log 时在其右侧、空白新会话在输入框上方右端提供 📁 切换按钮;支持多选、双击展开/打开、右键菜单(打开/系统打开/重命名/废纸篓/新建/复制路径)、拖拽移动、上传。文件「打开」在会话区新增「文件」标签页(原生 conversation.view,与对话/轨迹同级):文本可编辑 ⌘S 保存、图片预览、二进制提示。附带模型吞吐徽标:输入框正上方显示「正在等待模型...」与彩色 t/s 徽标(数据来自 llm/stream 真实流)。附带 GoalBar 多行展示:把原生单行截断 GoalBar 放开为多行完整显示,编辑用 textarea,工具与数据仍走原生 goal。",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|