@lyhue1991/dsh-soup 0.4.9 → 0.4.11
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 +2 -2
- package/lib/client.js +111 -48
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ DSH(DeepSeek Harness)功能很强:会话、工具、轨迹、Goal 一应
|
|
|
32
32
|
|
|
33
33
|
### 🧅 右侧文件树(主打 · 葱 · 提鲜点缀)
|
|
34
34
|
|
|
35
|
-
一个真正和左侧工作区并列的文件浏览器——不悬浮、不遮挡,配色与宽度都与左侧一致(默认
|
|
35
|
+
一个真正和左侧工作区并列的文件浏览器——不悬浮、不遮挡,配色与宽度都与左侧一致(默认 360px,拖左缘在 300–520px 间调整)。
|
|
36
36
|
|
|
37
37
|
- **跟随会话**:自动展示当前会话的工作目录,切会话/工作区时跟着走。
|
|
38
38
|
- **右键菜单**:预览、用系统打开、重命名、移到废纸篓、复制完整路径、下载到本机。
|
|
@@ -97,7 +97,7 @@ dsh plugin add @lyhue1991/dsh-soup
|
|
|
97
97
|
|
|
98
98
|
## 更新日志
|
|
99
99
|
|
|
100
|
-
- **0.4.x** — Seti 图标字体(60 种字形覆盖 122 种扩展名)+ 空会话预览浮层(最大化/还原)+ 微信文章兼容 + 文本上限 8 MB + 关闭按钮尺寸修复 + 预览路径 rtl 修复 + i18n 语言适配 + 预览纯阅读模式 +
|
|
100
|
+
- **0.4.x** — Seti 图标字体(60 种字形覆盖 122 种扩展名)+ 空会话预览浮层(最大化/还原)+ 微信文章兼容 + 文本上限 8 MB + 关闭按钮尺寸修复 + 预览路径 rtl 修复 + i18n 语言适配 + 预览纯阅读模式 + 内部分区重排 + 预览头部冻结 + details 宽度对齐宿主合同。
|
|
101
101
|
- **0.3.x** — Markdown 相对路径图片内联渲染 + 自动刷新(3s mtime 探测 + 指数退避)+ JupyterLab 风格工具栏图标。
|
|
102
102
|
- **0.2.0** — 改名 `dsh-tree` → `dsh-soup`。
|
|
103
103
|
- **0.1.x** — 只读文件预览系统(md/html/json/csv/pdf/notebook/图片/代码高亮)、大文件流式下载、多行 GoalBar、模型吞吐徽标、空白新会话入口、跨平台 node:fs 文件核心。
|
package/lib/client.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 布局:注册进 shell 原生的 `details` 右列(并列网格,非悬浮),
|
|
5
5
|
* 有 Session log 时在它右侧放 📁;空白新会话则在输入框上方、
|
|
6
6
|
* workspace/preset 控件行的右端提供同一入口。
|
|
7
|
-
*
|
|
7
|
+
* 宽度对齐宿主 details 合同:默认 360px、可拖 300–520px(直接改写 shell 网格
|
|
8
8
|
* 的 details 轨,并用 MutationObserver 在 shell 重渲染后维持宽度)。
|
|
9
9
|
*
|
|
10
10
|
* 与宿主通信:走同源 HTTP 路由 `/api/dsh-soup`(POST JSON),
|
|
@@ -151,6 +151,7 @@ window.__ModuleLoader__.load({
|
|
|
151
151
|
// 纯预览(无编辑):markdown/html/json/csv 各有专属渲染器。
|
|
152
152
|
var FILEVIEW_CSS =
|
|
153
153
|
'.dfv-root{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--dsw-specific-sidebar-fill);color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);user-select:text;-webkit-user-select:text;}' +
|
|
154
|
+
'.dfv-header{position:sticky;top:0;z-index:2;flex:none;background:var(--dsw-specific-sidebar-fill);}' +
|
|
154
155
|
'.dfv-tabbar{display:flex;align-items:center;gap:2px;flex:none;overflow-x:auto;padding:6px 10px 0;border-bottom:1px solid var(--dsw-alias-border-l1);scrollbar-width:none;}' +
|
|
155
156
|
'.dfv-tabbar::-webkit-scrollbar{display:none;}' +
|
|
156
157
|
'.dfv-tab{display:inline-flex;align-items:center;gap:5px;padding:5px 8px 5px 10px;border:none;border-radius:8px 8px 0 0;background:transparent;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.4;cursor:pointer;white-space:nowrap;max-width:220px;}' +
|
|
@@ -392,6 +393,31 @@ window.__ModuleLoader__.load({
|
|
|
392
393
|
// while a blank conversation is mounted. Keep late async responses from an
|
|
393
394
|
// older caller from overwriting the current session's shared store.
|
|
394
395
|
var sessionTrackGeneration = 0
|
|
396
|
+
|
|
397
|
+
// 目录展开状态按 cwd 隔离。刷新/删除/改名/移动后先重拉根目录,
|
|
398
|
+
// 再按这些路径递归重拉仍存在的已展开目录;切换项目时才清空。
|
|
399
|
+
var expandedDirs = new Set()
|
|
400
|
+
var expandedDirsCwd = null
|
|
401
|
+
|
|
402
|
+
function forgetExpandedPaths(paths) {
|
|
403
|
+
var dropped = paths.map(function (p) { return [p, p + '/'] })
|
|
404
|
+
expandedDirs = new Set(Array.from(expandedDirs).filter(function (p) {
|
|
405
|
+
return !dropped.some(function (pair) { return p === pair[0] || p.startsWith(pair[1]) })
|
|
406
|
+
}))
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function rekeyExpandedPaths(from, to) {
|
|
410
|
+
var moved = Array.from(expandedDirs).filter(function (p) {
|
|
411
|
+
return p === from || p.startsWith(from + '/')
|
|
412
|
+
}).sort(function (a, b) { return a.length - b.length })
|
|
413
|
+
if (!moved.length) return
|
|
414
|
+
var next = new Set(Array.from(expandedDirs).filter(function (p) {
|
|
415
|
+
return p !== from && !p.startsWith(from + '/')
|
|
416
|
+
}))
|
|
417
|
+
moved.forEach(function (p) { next.add(to + p.slice(from.length)) })
|
|
418
|
+
expandedDirs = next
|
|
419
|
+
}
|
|
420
|
+
|
|
395
421
|
function setState(patch) {
|
|
396
422
|
state = Object.assign({}, state, patch)
|
|
397
423
|
listeners.forEach(function (fn) { fn() })
|
|
@@ -414,9 +440,9 @@ window.__ModuleLoader__.load({
|
|
|
414
440
|
var uploadInputEl = null
|
|
415
441
|
var layout = null
|
|
416
442
|
|
|
417
|
-
var DETAILS_MIN =
|
|
418
|
-
var DETAILS_MAX =
|
|
419
|
-
var DETAILS_DEFAULT =
|
|
443
|
+
var DETAILS_MIN = 300
|
|
444
|
+
var DETAILS_MAX = 520
|
|
445
|
+
var DETAILS_DEFAULT = 360
|
|
420
446
|
function clampW(w) { return Math.max(DETAILS_MIN, Math.min(DETAILS_MAX, w)) }
|
|
421
447
|
|
|
422
448
|
function pathJoin(dir, name) { return String(dir || '/').replace(/\/+$/, '') + '/' + name }
|
|
@@ -484,6 +510,11 @@ window.__ModuleLoader__.load({
|
|
|
484
510
|
activeSessionId = sessionId || null
|
|
485
511
|
var cwd = await defaultCwd(sessionId, cwdHint)
|
|
486
512
|
if (generation !== sessionTrackGeneration) return
|
|
513
|
+
// 换项目时展开状态没有跨项目意义;回到同一项目则保留。
|
|
514
|
+
if (expandedDirsCwd !== cwd) {
|
|
515
|
+
expandedDirs.clear()
|
|
516
|
+
expandedDirsCwd = cwd
|
|
517
|
+
}
|
|
487
518
|
// 会话切换:预览列表按新 cwd 裁剪(范围外自动关闭),并清掉上一路径的旧错误横幅
|
|
488
519
|
var files = pruneFilesToScope(state.files, cwd)
|
|
489
520
|
lastMtimes = {} // 换了目录,旧 mtime 基线全部作废(首轮 tick 重建基线)
|
|
@@ -496,7 +527,26 @@ window.__ModuleLoader__.load({
|
|
|
496
527
|
async function refresh() {
|
|
497
528
|
if (!state.cwd) return
|
|
498
529
|
var items = await loadDir(state.cwd)
|
|
499
|
-
if (items)
|
|
530
|
+
if (!items) return
|
|
531
|
+
await restoreExpandedChildren(items)
|
|
532
|
+
setState({ error: '', tree: items })
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/** 根目录刷新后,按记住的路径递归恢复已展开目录。 */
|
|
536
|
+
async function restoreExpandedChildren(nodes) {
|
|
537
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
538
|
+
var node = nodes[i]
|
|
539
|
+
if (node.type !== 'directory' || !expandedDirs.has(node.path)) continue
|
|
540
|
+
var items = await loadDir(node.path)
|
|
541
|
+
if (!items) {
|
|
542
|
+
expandedDirs.delete(node.path)
|
|
543
|
+
continue
|
|
544
|
+
}
|
|
545
|
+
node.open = true
|
|
546
|
+
node.children = items
|
|
547
|
+
items.forEach(function (child) { child.parent = node })
|
|
548
|
+
await restoreExpandedChildren(items)
|
|
549
|
+
}
|
|
500
550
|
}
|
|
501
551
|
|
|
502
552
|
// ------------------------------------------------------------------
|
|
@@ -624,23 +674,7 @@ window.__ModuleLoader__.load({
|
|
|
624
674
|
/** 手动刷新:重拉根目录,已展开的子目录逐个重拉、保持展开不收起。 */
|
|
625
675
|
async function refreshAll() {
|
|
626
676
|
if (!state.cwd) return
|
|
627
|
-
var expanded = []
|
|
628
|
-
var walk = function (nodes) {
|
|
629
|
-
for (var i = 0; i < nodes.length; i++) {
|
|
630
|
-
var n = nodes[i]
|
|
631
|
-
if (n.type === 'directory' && n.children != null) {
|
|
632
|
-
if (n.path !== state.cwd) expanded.push({ path: n.path, open: !!n.open })
|
|
633
|
-
if (n.open && n.children.length) walk(n.children)
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
walk(state.tree)
|
|
638
677
|
await refresh()
|
|
639
|
-
for (var j = 0; j < expanded.length; j++) {
|
|
640
|
-
if (!expanded[j].open) continue
|
|
641
|
-
var node = findNode(state.tree, expanded[j].path)
|
|
642
|
-
if (node) await reloadDirNode(node)
|
|
643
|
-
}
|
|
644
678
|
}
|
|
645
679
|
|
|
646
680
|
async function toggleNode(node) {
|
|
@@ -652,10 +686,16 @@ window.__ModuleLoader__.load({
|
|
|
652
686
|
node.loading = false
|
|
653
687
|
if (items) items.forEach(function (c) { c.parent = node })
|
|
654
688
|
node.children = items || []
|
|
655
|
-
node.open =
|
|
689
|
+
node.open = !!items
|
|
690
|
+
if (items) expandedDirs.add(node.path)
|
|
691
|
+
// 拖拽可能把一个已展开目录移进此前收起的父目录。父目录首次展开时,
|
|
692
|
+
// 也要立即恢复其中被记住的子目录展开状态。
|
|
693
|
+
if (items) await restoreExpandedChildren(items)
|
|
656
694
|
setState({ tree: state.tree })
|
|
657
695
|
} else {
|
|
658
696
|
node.open = !node.open
|
|
697
|
+
if (node.open) expandedDirs.add(node.path)
|
|
698
|
+
else expandedDirs.delete(node.path)
|
|
659
699
|
setState({ tree: state.tree })
|
|
660
700
|
}
|
|
661
701
|
}
|
|
@@ -940,11 +980,14 @@ window.__ModuleLoader__.load({
|
|
|
940
980
|
var paths = Array.from(state.selected)
|
|
941
981
|
setState({ menu: null })
|
|
942
982
|
var errors = []
|
|
983
|
+
var trashed = []
|
|
943
984
|
for (var i = 0; i < paths.length; i++) {
|
|
944
985
|
var res = await rpc('trash', { path: paths[i] })
|
|
945
|
-
if (
|
|
986
|
+
if (res && res.ok) trashed.push(paths[i])
|
|
987
|
+
else errors.push(baseName(paths[i]) + ': ' + ((res && res.error) || T('explorer.actionFail')) + ((res && res.hint) ? '(' + res.hint + ')' : ''))
|
|
946
988
|
}
|
|
947
989
|
if (errors.length) setState({ error: errors.join(';') })
|
|
990
|
+
forgetExpandedPaths(trashed)
|
|
948
991
|
setState({ selected: new Set(), lastIndex: null })
|
|
949
992
|
await refresh()
|
|
950
993
|
}
|
|
@@ -958,12 +1001,18 @@ window.__ModuleLoader__.load({
|
|
|
958
1001
|
}
|
|
959
1002
|
var dir = parentOf(path)
|
|
960
1003
|
var dest = pathJoin(dir, name)
|
|
1004
|
+
var node = findNode(state.tree, path)
|
|
1005
|
+
var isDir = node && node.type === 'directory'
|
|
961
1006
|
var sel = new Set(state.selected)
|
|
962
1007
|
if (sel.has(path)) { sel.delete(path); sel.add(dest) }
|
|
963
1008
|
setState({ renaming: null, selected: sel })
|
|
964
1009
|
;(async function () {
|
|
965
1010
|
var res = await rpc('move', { from: path, to: dest })
|
|
966
|
-
if (
|
|
1011
|
+
if (res && res.ok) {
|
|
1012
|
+
if (isDir) rekeyExpandedPaths(path, dest)
|
|
1013
|
+
} else {
|
|
1014
|
+
setState({ error: (res && res.error) || T('explorer.renameFail') })
|
|
1015
|
+
}
|
|
967
1016
|
await refresh()
|
|
968
1017
|
})()
|
|
969
1018
|
}
|
|
@@ -1040,6 +1089,7 @@ window.__ModuleLoader__.load({
|
|
|
1040
1089
|
|
|
1041
1090
|
async function movePaths(paths, targetDir) {
|
|
1042
1091
|
var errors = []
|
|
1092
|
+
var moved = []
|
|
1043
1093
|
for (var i = 0; i < paths.length; i++) {
|
|
1044
1094
|
var p = paths[i]
|
|
1045
1095
|
if (p === '/' || !p) continue
|
|
@@ -1047,10 +1097,18 @@ window.__ModuleLoader__.load({
|
|
|
1047
1097
|
var dest = pathJoin(targetDir, name)
|
|
1048
1098
|
if (dest === p) continue
|
|
1049
1099
|
if (targetDir === p || targetDir.startsWith(p + '/')) { errors.push(T('explorer.moveIntoSelf', { path: name })); continue }
|
|
1100
|
+
var node = findNode(state.tree, p)
|
|
1050
1101
|
var res = await rpc('move', { from: p, to: dest })
|
|
1051
|
-
if (
|
|
1102
|
+
if (res && res.ok) {
|
|
1103
|
+
moved.push({ from: p, to: dest, isDir: node && node.type === 'directory' })
|
|
1104
|
+
} else {
|
|
1105
|
+
errors.push(name + ': ' + ((res && res.error) || T('explorer.actionFail')))
|
|
1106
|
+
}
|
|
1052
1107
|
}
|
|
1053
1108
|
setState({ dragPaths: null })
|
|
1109
|
+
moved.forEach(function (item) {
|
|
1110
|
+
if (item.isDir) rekeyExpandedPaths(item.from, item.to)
|
|
1111
|
+
})
|
|
1054
1112
|
if (errors.length) setState({ error: errors.join(';') })
|
|
1055
1113
|
await refresh()
|
|
1056
1114
|
}
|
|
@@ -1435,8 +1493,9 @@ window.__ModuleLoader__.load({
|
|
|
1435
1493
|
if (isNaN(last) || last <= 1) { setState({ open: false }); return }
|
|
1436
1494
|
setState({ open: true })
|
|
1437
1495
|
if (Math.round(last) !== Math.round(widthRef.current)) {
|
|
1438
|
-
|
|
1439
|
-
|
|
1496
|
+
// 宿主是宽度偏好的权威(/native drag/窗口求解都可能改它)。
|
|
1497
|
+
// 插件只同步基准,不能把宿主拖出来的宽度强行拉回旧默认。
|
|
1498
|
+
widthRef.current = last
|
|
1440
1499
|
}
|
|
1441
1500
|
} catch (e) {}
|
|
1442
1501
|
}
|
|
@@ -2420,6 +2479,7 @@ window.__ModuleLoader__.load({
|
|
|
2420
2479
|
)
|
|
2421
2480
|
})
|
|
2422
2481
|
|
|
2482
|
+
var toolbar = null
|
|
2423
2483
|
var body = null
|
|
2424
2484
|
if (!active) {
|
|
2425
2485
|
body = create('div', { className: 'dfv-empty' },
|
|
@@ -2427,33 +2487,36 @@ window.__ModuleLoader__.load({
|
|
|
2427
2487
|
T('files.emptyHint'),
|
|
2428
2488
|
)
|
|
2429
2489
|
} else {
|
|
2490
|
+
toolbar = create('div', { className: 'dfv-toolbar' },
|
|
2491
|
+
create('span', { className: 'dfv-toolbar-path', title: active.path }, active.path),
|
|
2492
|
+
typeof active.size === 'number' && active.size > 0 ? create('span', null, formatBytes(active.size)) : null,
|
|
2493
|
+
active.loaded ? create('button', {
|
|
2494
|
+
type: 'button',
|
|
2495
|
+
className: 'dfv-btn',
|
|
2496
|
+
title: T('files.reloadTitle'),
|
|
2497
|
+
onClick: function () { reloadFile(active.path) },
|
|
2498
|
+
},
|
|
2499
|
+
create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }),
|
|
2500
|
+
T('files.reload'),
|
|
2501
|
+
) : null,
|
|
2502
|
+
create('button', {
|
|
2503
|
+
type: 'button',
|
|
2504
|
+
className: 'dfv-btn',
|
|
2505
|
+
title: '最大化', 'aria-label': '最大化',
|
|
2506
|
+
onClick: function () { setFiles({ overlay: true, overlayMax: true, overlayReturn: 'tab' }) },
|
|
2507
|
+
dangerouslySetInnerHTML: { __html: EXPAND_SVG },
|
|
2508
|
+
}),
|
|
2509
|
+
)
|
|
2430
2510
|
body = create('div', { className: 'dfv-body' },
|
|
2431
|
-
create('div', { className: 'dfv-toolbar' },
|
|
2432
|
-
create('span', { className: 'dfv-toolbar-path', title: active.path }, active.path),
|
|
2433
|
-
typeof active.size === 'number' && active.size > 0 ? create('span', null, formatBytes(active.size)) : null,
|
|
2434
|
-
active.loaded ? create('button', {
|
|
2435
|
-
type: 'button',
|
|
2436
|
-
className: 'dfv-btn',
|
|
2437
|
-
title: T('files.reloadTitle'),
|
|
2438
|
-
onClick: function () { reloadFile(active.path) },
|
|
2439
|
-
},
|
|
2440
|
-
create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }),
|
|
2441
|
-
T('files.reload'),
|
|
2442
|
-
) : null,
|
|
2443
|
-
create('button', {
|
|
2444
|
-
type: 'button',
|
|
2445
|
-
className: 'dfv-btn',
|
|
2446
|
-
title: '最大化', 'aria-label': '最大化',
|
|
2447
|
-
onClick: function () { setFiles({ overlay: true, overlayMax: true, overlayReturn: 'tab' }) },
|
|
2448
|
-
dangerouslySetInnerHTML: { __html: EXPAND_SVG },
|
|
2449
|
-
}),
|
|
2450
|
-
),
|
|
2451
2511
|
create(FileContent, { key: active.path, entry: active }),
|
|
2452
2512
|
)
|
|
2453
2513
|
}
|
|
2454
2514
|
|
|
2455
2515
|
return create('div', { className: 'dfv-root', ref: rootRef },
|
|
2456
|
-
files.list.length > 0 ? create('div', { className: 'dfv-
|
|
2516
|
+
files.list.length > 0 ? create('div', { className: 'dfv-header' },
|
|
2517
|
+
create('div', { className: 'dfv-tabbar' }, tabs),
|
|
2518
|
+
toolbar,
|
|
2519
|
+
) : null,
|
|
2457
2520
|
body,
|
|
2458
2521
|
)
|
|
2459
2522
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lyhue1991/dsh-soup",
|
|
3
|
-
"description": "DSH 项目资源管理器:右侧 details
|
|
4
|
-
"version": "0.4.
|
|
3
|
+
"description": "DSH 项目资源管理器:右侧 details 列文件树(并列网格、宽度对齐宿主 details 合同 300-520px),有 Session log 时在其右侧、空白新会话在输入框上方右端提供 📁 切换按钮;支持多选、双击展开/打开、右键菜单(打开/系统打开/重命名/废纸篓/新建/复制路径)、拖拽移动、上传。文件「打开」在会话区新增「文件」标签页(原生 conversation.view,与对话/轨迹同级):文本可编辑 ⌘S 保存、图片预览、二进制提示。附带模型吞吐徽标:输入框正上方显示「正在等待模型...」与彩色 t/s 徽标(数据来自 llm/stream 真实流)。附带 GoalBar 多行展示:把原生单行截断 GoalBar 放开为多行完整显示,编辑用 textarea,工具与数据仍走原生 goal。",
|
|
4
|
+
"version": "0.4.11",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|