@lyhue1991/dsh-soup 0.4.3 → 0.4.5

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 CHANGED
@@ -97,26 +97,10 @@ dsh plugin add @lyhue1991/dsh-soup
97
97
 
98
98
  ## 更新日志
99
99
 
100
- - **0.4.1(当前)** — **浏览器半区内部整理**:`lib/client.js` 按四大功能分区重排并加统一标题注释(i18n 基座 → 资源管理器 → 会话内预览 → 速率徽标 → 多行 GoalBar → apply 注册),纯重排无逻辑变更、无行为差异。
101
- - **0.4.0** —
102
- - **语言适配(i18n)**:dsh-soup 全部文案(资源管理器、预览页、右键菜单、上传/下载、速度徽标、GoalBar)跟随 DSH 通用设置的语言切换(zh/en),切换即时生效。
103
- - **预览 tab 纯阅读模式**:激活预览时隐藏底部输入框、「N 轮 · M 步」统计行、GoalBar 与速度徽标——整屏干净内容区,切回对话/轨迹恢复。
104
- - **0.3.0** —
105
- - **Markdown 预览渲染相对路径图片**:`![x](view.jpg)` 经同源 `/api/dsh-soup/img` 端点内联加载(confine 围栏 + 仅图片类型 + 拒跨站)。
106
- - **自动刷新**:资源管理器与预览页每 3 秒轻量探测目录/文件签名(mtime),变了才重拉;面板关闭且无预览时零开销;宿主不可达时指数退避(封顶 60s)。
107
- - **顶部 ⟳ 刷新按钮**:重拉整棵树并保持已展开子目录不收起;空白右键「刷新」项同步升级为同一枚图标。
108
- - **工具栏按钮升级**:头部 ⟳/⬆/✕ 与预览「重载」对齐 JupyterLab 设计(26px 触达区 + SVG 图标,BSD-3 引用);修复关闭按钮渲染异常。
109
- - **0.2.0** — 改名 `dsh-tree → dsh-soup`(npm 包 `@lyhue1991/dsh-soup`)。
110
- - **0.1.18** — 下载进度行 + 完成通知自动消失。
111
- - **0.1.17** — 大文件流式下载(一次性票据 URL + 流式发送),替代 base64-over-JSON,任意大小零内存放大。
112
- - **0.1.16** — 下载菜单项 + 复制完整路径修复 + 目录展开态图标。
113
- - **0.1.15** — 只读文件预览系统:会话区新增「预览」tab(md/html/json/csv·tsv 渲染 + 图片/PDF/Notebook/代码高亮 + 纯文本兜底);宿主新增 `read` 动作;路由安全加固。
114
- - **0.1.12** — 吞吐徽标状态修复:流式即显 token 计数、滑动窗口瞬时 `t/s`、停顿检测回等待态。
115
- - **0.1.11** — 空白新会话入口(输入框上方右端 📁)。
116
- - **0.1.10** — GoalBar 改用原生 goal 工具 + 多行展示(textarea)。
117
- - **0.1.7** — 速度徽标稳定化(贴附 Deep diving 状态行)。
118
- - **0.1.5** — 跨平台:文件核心走 `node:fs`;新增模型吞吐徽标。
119
- - **0.1.3** — 修复 details 插槽优先级与布局实例引用。
100
+ - **0.4.x** — Seti 图标字体(60 种字形覆盖 122 种扩展名)+ 空会话预览浮层(最大化/还原)+ 微信文章兼容 + 文本上限 8 MB + 关闭按钮尺寸修复 + 预览路径 rtl 修复 + i18n 语言适配 + 预览纯阅读模式 + 内部分区重排。
101
+ - **0.3.x** — Markdown 相对路径图片内联渲染 + 自动刷新(3s mtime 探测 + 指数退避)+ JupyterLab 风格工具栏图标。
102
+ - **0.2.0** — 改名 `dsh-tree` → `dsh-soup`。
103
+ - **0.1.x** — 只读文件预览系统(md/html/json/csv/pdf/notebook/图片/代码高亮)、大文件流式下载、多行 GoalBar、模型吞吐徽标、空白新会话入口、跨平台 node:fs 文件核心。
120
104
 
121
105
  ## 开发
122
106
 
package/lib/client.js CHANGED
@@ -106,6 +106,8 @@ window.__ModuleLoader__.load({
106
106
  // 同名 React 元素常量冲突(var 提升覆盖),字符样式反而更稳。
107
107
  // ------------------------------------------------------------------
108
108
  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>'
109
+ 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>'
110
+ 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>'
109
111
 
110
112
  // GoalBar 多行版:沿用 DSH 原生 GoalBar 的结构和主题,仅放开单行截断。
111
113
  // 布局分两行:head 行(图标 + 相位标签 + 操作按钮),body 行(objective 多行)。
@@ -135,7 +137,7 @@ window.__ModuleLoader__.load({
135
137
  // 文件标签页视图(conversation.view 'dsh-soup-files'):子 tab 条 + 预览区。
136
138
  // 纯预览(无编辑):markdown/html/json/csv 各有专属渲染器。
137
139
  var FILEVIEW_CSS =
138
- '.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);}' +
140
+ '.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;}' +
139
141
  '.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;}' +
140
142
  '.dfv-tabbar::-webkit-scrollbar{display:none;}' +
141
143
  '.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;}' +
@@ -155,11 +157,22 @@ window.__ModuleLoader__.load({
155
157
  '.dfv-cap{padding:5px 14px;font-size:11px;color:var(--dsw-alias-label-caption);border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;}' +
156
158
  '.dfv-code{flex:1;min-height:0;width:100%;box-sizing:border-box;margin:0;padding:10px 14px;overflow:auto;background:transparent;color:var(--dsw-alias-label-primary);font-family:var(--ds-font-family-code);font-size:12.5px;line-height:1.55;white-space:pre;tab-size:2;user-select:text;}' +
157
159
  '.dfv-code-wrap{flex:1;min-height:0;overflow:auto;padding:8px 10px;}' +
160
+ '.dfv-code-lines{flex:1;min-height:0;display:flex;overflow:auto;}' +
161
+ '.dfv-code-gutter{position:sticky;left:0;z-index:1;flex:none;padding:10px 8px 10px 12px;text-align:right;color:var(--dsw-alias-label-caption);font-family:var(--ds-font-family-code);font-size:12.5px;line-height:1.55;user-select:none;background:var(--dsw-alias-bg-layer-1);border-right:1px solid var(--dsw-alias-border-l1);}' +
162
+ '.dfv-code-main{flex:1;min-width:0;}' +
163
+ '.dfv-code-main .dfv-code{overflow:visible;width:auto;}' +
164
+ // CodeBlock(md-code-block)在行号布局内的适配:
165
+ // 隐藏 banner(js 标签 + 复制按钮),统一 padding/字体与 gutter 对齐。
166
+ '.dfv-code-main .md-code-block{margin:0;border-radius:0;background:transparent;}' +
167
+ '.dfv-code-main .md-code-block>div:first-child{display:none!important;}' +
168
+ '.dfv-code-main .md-code-block pre{padding:10px 14px!important;margin:0!important;font:12.5px/1.55 var(--ds-font-family-code)!important;background:transparent!important;border-radius:0!important;white-space:pre;word-break:normal;}' +
158
169
  '.dfv-nb{flex:1;min-height:0;overflow:auto;background:var(--dsw-alias-bg-base);}' +
159
170
  '.dfv-nb-cell{display:flex;border-bottom:1px solid var(--dsw-alias-border-l1);}' +
160
171
  '.dfv-nb-gutter{flex:none;width:44px;padding:8px 6px;text-align:right;font-family:var(--ds-font-family-code);font-size:11px;color:var(--dsw-alias-label-caption);border-right:1px solid var(--dsw-alias-border-l1);user-select:none;background:var(--dsw-specific-sidebar-fill);}' +
161
172
  '.dfv-nb-cellbody{flex:1;min-width:0;padding:4px 0;}' +
162
173
  '.dfv-nb-cellbody .md-code-block,.dfv-nb-cellbody .dfv-code{margin:4px 10px;border-radius:8px;overflow:hidden;}' +
174
+ '.dfv-nb-cellbody .dfv-code-lines{border-radius:8px;overflow:hidden;}' +
175
+ '.dfv-nb-cellbody .dfv-code-gutter{padding:8px 6px 8px 10px;font-size:12px;}' +
163
176
  '.dfv-nb-cell.dfv-nb-markdown .dfv-md-wrap{padding:2px 14px;}' +
164
177
  '.dfv-nb-raw{color:var(--dsw-alias-label-secondary);}' +
165
178
  '.dfv-nb-outputs{border-top:1px dashed var(--dsw-alias-border-l1);padding:6px 10px;display:flex;flex-direction:column;gap:8px;}' +
@@ -181,6 +194,12 @@ window.__ModuleLoader__.load({
181
194
  '.dfv-table{border-collapse:collapse;font-size:12px;font-family:var(--ds-font-family-code);}' +
182
195
  '.dfv-th{position:sticky;top:0;z-index:1;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-weight:600;text-align:left;padding:5px 10px;border-bottom:1px solid var(--dsw-alias-border-l2);border-right:1px solid var(--dsw-alias-border-l1);white-space:nowrap;}' +
183
196
  '.dfv-td{padding:4px 10px;border-bottom:1px solid var(--dsw-alias-border-l1);border-right:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere;max-width:360px;}' +
197
+ // 兜底浮层:空会话时原生 tab 条不存在,预览以模态浮层展示
198
+ '.dfv-overlay{position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.24));backdrop-filter:var(--dsw-mask-blur,blur(2px));}' +
199
+ '.dfv-overlay-panel{display:flex;flex-direction:column;width:800px;height:min(800px,calc(100vh - 48px));max-width:calc(100vw - 48px);border-radius:24px;overflow:hidden;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv3);user-select:text;-webkit-user-select:text;}' +
200
+ '.dfv-overlay-panel.dfv-overlay-max{width:100vw;height:100vh;max-width:none;border-radius:0;border:none;}' +
201
+ '.dfv-overlay-close{flex:none;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);font-size:14px;line-height:1;cursor:pointer;padding:0;}' +
202
+ '.dfv-overlay-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}' +
184
203
  // 纯阅读模式:预览 tab 激活时隐藏整个输入区(composerSeat:输入框 +
185
204
  // GoalBar + 速度徽标所在的 input.dock)与轮次统计行(StatsLine)。
186
205
  // class 来自 DSH 客户端编译产物(CSS module hash),升级后若失效仅
@@ -257,7 +276,7 @@ window.__ModuleLoader__.load({
257
276
  'files.binary': '二进制文件({size}),请在资源管理器中用系统打开',
258
277
  'files.imageTooLarge': '图片过大({size},上限 {limit})',
259
278
  'files.pdfTooLarge': 'PDF 过大({size},上限 {limit}),请用系统打开',
260
- 'files.truncated': '文件超过 2 MB,仅预览前 2 MB;完整内容请用系统编辑器打开。',
279
+ 'files.truncated': '文件超过 8 MB,仅预览前 8 MB;完整内容请用系统编辑器打开。',
261
280
  'files.jsonFail': 'JSON 解析失败,按纯文本显示',
262
281
  'files.nbFail': 'ipynb 解析失败,按纯文本显示',
263
282
  'files.nbEmpty': '(空笔记本)',
@@ -320,7 +339,7 @@ window.__ModuleLoader__.load({
320
339
  'files.binary': 'Binary file ({size}) — open with the system explorer',
321
340
  'files.imageTooLarge': 'Image too large ({size}, limit {limit})',
322
341
  'files.pdfTooLarge': 'PDF too large ({size}, limit {limit}) — open with the system viewer',
323
- 'files.truncated': 'File exceeds 2 MB — showing the first 2 MB; open in a system editor for the full content.',
342
+ 'files.truncated': 'File exceeds 8 MB — showing the first 8 MB; open in a system editor for the full content.',
324
343
  'files.jsonFail': 'JSON parse failed — shown as plain text',
325
344
  'files.nbFail': 'ipynb parse failed — shown as plain text',
326
345
  'files.nbEmpty': '(empty notebook)',
@@ -348,7 +367,7 @@ window.__ModuleLoader__.load({
348
367
  selected: new Set(), lastIndex: null,
349
368
  renaming: null, newItem: null,
350
369
  dropTarget: null, dragPaths: null,
351
- files: { list: [], active: null },
370
+ files: { list: [], active: null, overlay: false, overlayMax: false, overlayReturn: null },
352
371
  uploads: [],
353
372
  }
354
373
  var listeners = new Set()
@@ -1627,12 +1646,13 @@ window.__ModuleLoader__.load({
1627
1646
  setFiles({ list: list, active: path })
1628
1647
  } else if (entry.loaded) {
1629
1648
  setFiles({ active: path })
1630
- activateFilesView()
1649
+ if (activateFilesView()) return
1650
+ setFiles({ overlay: true, overlayMax: false, overlayReturn: null })
1631
1651
  return
1632
1652
  } else {
1633
1653
  setFiles({ active: path })
1634
1654
  }
1635
- activateFilesView()
1655
+ if (!activateFilesView()) setFiles({ overlay: true, overlayMax: false, overlayReturn: null })
1636
1656
  var res = await rpc('read', { path: path, sessionId: activeSessionId })
1637
1657
  if (!res.ok && /超出允许范围/.test(res.error || '')) {
1638
1658
  await new Promise(function (r) { setTimeout(r, 600) })
@@ -1831,12 +1851,25 @@ window.__ModuleLoader__.load({
1831
1851
  // 可交互预览:给 allow-scripts/popups/forms/modals(对齐 JupyterLab
1832
1852
  // trusted 档),但坚持不给 allow-same-origin——脚本跑在 opaque origin,
1833
1853
  // 触不到 GUI 的 DOM/存储/Cookie;srcdoc 注入 + no-referrer 收敛外链。
1854
+ // 微信文章兼容:正文 #js_content 初始 visibility:hidden 靠 JS 展开,
1855
+ // 图片用 data-src 懒加载靠 JS 换 src——沙箱里两者都跑不起来。
1856
+ // 注入一段 CSS 强制显示正文,并预先把 data-src 落到 src。
1857
+ var text = props.text || ''
1858
+ if (text.indexOf('js_content') !== -1 || text.indexOf('data-src=') !== -1) {
1859
+ var inject = '<style>#js_content{visibility:visible!important;opacity:1!important;}</style>'
1860
+ if (/<\/head>/i.test(text)) {
1861
+ text = text.replace(/<\/head>/i, inject + '</head>')
1862
+ } else {
1863
+ text = inject + text
1864
+ }
1865
+ text = text.replace(/(<img\b[^>]*?)\sdata-src=/gi, '$1 src=')
1866
+ }
1834
1867
  return create('iframe', {
1835
1868
  className: 'dfv-frame',
1836
1869
  sandbox: 'allow-scripts allow-popups allow-forms allow-modals',
1837
1870
  referrerPolicy: 'no-referrer',
1838
1871
  title: props.title || T('files.htmlFrame'),
1839
- srcDoc: props.text,
1872
+ srcDoc: text,
1840
1873
  })
1841
1874
  }
1842
1875
 
@@ -1966,9 +1999,10 @@ window.__ModuleLoader__.load({
1966
1999
  })
1967
2000
  var codeSrc = nbSource(cell.source)
1968
2001
  body = create('div', { className: 'dfv-nb-cellbody' },
1969
- CodeBlock
1970
- ? create(CodeBlock, { code: codeSrc, lang: codeLang })
1971
- : create('pre', { className: 'dfv-code' }, codeSrc),
2002
+ create(CodeWithLines, { text: codeSrc },
2003
+ CodeBlock
2004
+ ? create(CodeBlock, { code: codeSrc, lang: codeLang })
2005
+ : create('pre', { className: 'dfv-code' }, codeSrc)),
1972
2006
  outputs.length ? create('div', { className: 'dfv-nb-outputs' }, outputs) : null)
1973
2007
  } else if (ct === 'markdown') {
1974
2008
  body = MarkdownText
@@ -2046,6 +2080,18 @@ window.__ModuleLoader__.load({
2046
2080
  return (n / 1073741824).toFixed(1) + ' GB'
2047
2081
  }
2048
2082
 
2083
+ /** 代码内容 + 左侧行号列(源码文件 / notebook code cell 用)。 */
2084
+ function CodeWithLines(props) {
2085
+ var text = props.text || ''
2086
+ var lineCount = text.split('\n').length
2087
+ var nums = []
2088
+ for (var i = 1; i <= lineCount; i++) nums.push(create('div', { key: i }, String(i)))
2089
+ return create('div', { className: 'dfv-code-lines' },
2090
+ create('div', { className: 'dfv-code-gutter', 'aria-hidden': true }, nums),
2091
+ create('div', { className: 'dfv-code-main' }, props.children),
2092
+ )
2093
+ }
2094
+
2049
2095
  function FileContent(props) {
2050
2096
  var entry = props.entry
2051
2097
  if (entry.loading) {
@@ -2083,14 +2129,14 @@ window.__ModuleLoader__.load({
2083
2129
  else if (pk === 'csv' || pk === 'tsv') view = create(DelimitedPreview, { text: entry.content, delim: pk === 'tsv' ? '\t' : ',' })
2084
2130
  else {
2085
2131
  // 代码文件:DSH CodeBlock(Shiki 高亮,语法包懒加载);无高亮条件回退纯文本
2132
+ // 都包在 CodeWithLines 里加左侧行号
2086
2133
  var lang = Object.prototype.hasOwnProperty.call(CODE_LANG_BY_EXT, extOfName(entry.path))
2087
2134
  ? CODE_LANG_BY_EXT[extOfName(entry.path)]
2088
2135
  : undefined
2089
- if (CodeBlock && lang && entry.content.length <= CODE_HIGHLIGHT_MAX_CHARS) {
2090
- view = create('div', { className: 'dfv-code-wrap' }, create(CodeBlock, { code: entry.content, lang: lang }))
2091
- } else {
2092
- view = create('pre', { className: 'dfv-code' }, entry.content)
2093
- }
2136
+ var codeEl = (CodeBlock && lang && entry.content.length <= CODE_HIGHLIGHT_MAX_CHARS)
2137
+ ? create(CodeBlock, { code: entry.content, lang: lang })
2138
+ : create('pre', { className: 'dfv-code' }, entry.content)
2139
+ view = create(CodeWithLines, { text: entry.content }, codeEl)
2094
2140
  }
2095
2141
  return create(React.Fragment, null,
2096
2142
  entry.truncated ? create('div', { className: 'dfv-cap' },
@@ -2178,6 +2224,13 @@ window.__ModuleLoader__.load({
2178
2224
  create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }),
2179
2225
  T('files.reload'),
2180
2226
  ) : null,
2227
+ create('button', {
2228
+ type: 'button',
2229
+ className: 'dfv-btn',
2230
+ title: '最大化', 'aria-label': '最大化',
2231
+ onClick: function () { setFiles({ overlay: true, overlayMax: true, overlayReturn: 'tab' }) },
2232
+ dangerouslySetInnerHTML: { __html: EXPAND_SVG },
2233
+ }),
2181
2234
  ),
2182
2235
  create(FileContent, { key: active.path, entry: active }),
2183
2236
  )
@@ -2189,6 +2242,61 @@ window.__ModuleLoader__.load({
2189
2242
  )
2190
2243
  }
2191
2244
 
2245
+ /** 空会话兜底浮层:原生 tab 条不存在时以模态渲染 FileContent。 */
2246
+ function PreviewOverlay() {
2247
+ var s = useStore()
2248
+ var files = s.files
2249
+ if (!files.overlay || !files.active) return null
2250
+ var active = findFileEntry(files.active)
2251
+ if (!active) return null
2252
+ var close = function () { setFiles({ overlay: false, overlayMax: false, overlayReturn: null }) }
2253
+ var closeAndCloseTab = function () {
2254
+ close()
2255
+ closeFileTab(active.path)
2256
+ }
2257
+ var toggleMax = function () {
2258
+ if (files.overlayMax && files.overlayReturn === 'tab') {
2259
+ // 还原 → 回到 tab 模式
2260
+ close()
2261
+ } else {
2262
+ setFiles({ overlayMax: !files.overlayMax })
2263
+ }
2264
+ }
2265
+ return create('div', {
2266
+ className: 'dfv-overlay',
2267
+ onClick: function (e) { if (e.target === e.currentTarget) close() },
2268
+ },
2269
+ create('div', { className: 'dfv-overlay-panel' + (files.overlayMax ? ' dfv-overlay-max' : '') },
2270
+ create('div', { className: 'dfv-toolbar' },
2271
+ create('span', { className: 'dfv-toolbar-path', title: active.path }, active.path),
2272
+ typeof active.size === 'number' && active.size > 0 ? create('span', null, formatBytes(active.size)) : null,
2273
+ active.loaded ? create('button', {
2274
+ type: 'button',
2275
+ className: 'dfv-btn',
2276
+ title: T('files.reloadTitle'),
2277
+ onClick: function () { reloadFile(active.path) },
2278
+ },
2279
+ create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }),
2280
+ T('files.reload'),
2281
+ ) : null,
2282
+ create('button', {
2283
+ type: 'button', className: 'dfv-overlay-close',
2284
+ title: files.overlayMax ? '还原' : '最大化', 'aria-label': files.overlayMax ? '还原' : '最大化',
2285
+ onClick: toggleMax,
2286
+ dangerouslySetInnerHTML: { __html: files.overlayMax ? SHRINK_SVG : EXPAND_SVG },
2287
+ }),
2288
+ create('button', {
2289
+ type: 'button', className: 'dfv-overlay-close',
2290
+ title: files.overlayReturn === 'tab' ? '关闭预览 tab' : '关闭',
2291
+ 'aria-label': files.overlayReturn === 'tab' ? '关闭预览 tab' : '关闭',
2292
+ onClick: closeAndCloseTab,
2293
+ }, '✕'),
2294
+ ),
2295
+ create(FileContent, { key: active.path, entry: active }),
2296
+ ),
2297
+ )
2298
+ }
2299
+
2192
2300
  function findFrameEl(startEl) {
2193
2301
  var cur = startEl && startEl.parentElement
2194
2302
  while (cur) {
@@ -2605,7 +2713,12 @@ window.__ModuleLoader__.load({
2605
2713
  // `details` is a single slot occupied by the shell at priority 0.
2606
2714
  // Lower priorities render first, so this intentionally shadows it.
2607
2715
  { name: 'details', priority: -1, locale: NS },
2608
- function (props) { return create(Panel, props) },
2716
+ function (props) {
2717
+ return create(React.Fragment, null,
2718
+ create(Panel, props),
2719
+ create(PreviewOverlay),
2720
+ )
2721
+ },
2609
2722
  ))
2610
2723
  })
2611
2724
  // 文件标签页:与 对话/轨迹 同级的原生 view tab(ui-trajectory 同款注册方式)。
package/lib/index.js CHANGED
@@ -392,8 +392,8 @@ export function apply(ctx) {
392
392
  // ------------------------------------------------------------------
393
393
  // 文件只读预览:read(文本/图片/二进制三态);无 write——预览-only 不提供保存
394
394
  // ------------------------------------------------------------------
395
- /** 文本读取上限(超过则截断标记 truncated)。 */
396
- const TEXT_MAX_BYTES = 2 * 1024 * 1024
395
+ /** 文本读取上限(超过则截断标记 truncated)。8 MB——HTML 常含内联 base64 图片,2 MB 太紧。 */
396
+ const TEXT_MAX_BYTES = 8 * 1024 * 1024
397
397
  /** 图片内联上限。 */
398
398
  const IMAGE_MAX_BYTES = 8 * 1024 * 1024
399
399
  /** PDF 内联上限。 */
@@ -627,6 +627,12 @@ export function apply(ctx) {
627
627
  if (typeof entry.size === 'number' && entry.size >= 0) item.size = entry.size
628
628
  return item
629
629
  })
630
+ // 目录在前、文件在后,各按名称排(与 VS Code / Finder 一致)。
631
+ out.sort((a, b) => {
632
+ const dirDiff = (a.type === 'directory' ? 0 : 1) - (b.type === 'directory' ? 0 : 1)
633
+ if (dirDiff !== 0) return dirDiff
634
+ return a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' })
635
+ })
630
636
  return { ok: true, root, path: basePath, entries: out }
631
637
  }
632
638
  case 'open': {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lyhue1991/dsh-soup",
3
3
  "description": "DSH 项目资源管理器:右侧 details 列文件树(并列网格、宽度与左侧工作区一致 264-420px),有 Session log 时在其右侧、空白新会话在输入框上方右端提供 📁 切换按钮;支持多选、双击展开/打开、右键菜单(打开/系统打开/重命名/废纸篓/新建/复制路径)、拖拽移动、上传。文件「打开」在会话区新增「文件」标签页(原生 conversation.view,与对话/轨迹同级):文本可编辑 ⌘S 保存、图片预览、二进制提示。附带模型吞吐徽标:输入框正上方显示「正在等待模型...」与彩色 t/s 徽标(数据来自 llm/stream 真实流)。附带 GoalBar 多行展示:把原生单行截断 GoalBar 放开为多行完整显示,编辑用 textarea,工具与数据仍走原生 goal。",
4
- "version": "0.4.3",
4
+ "version": "0.4.5",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {