@lyhue1991/dsh-soup 0.4.3 → 0.4.4

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 多行)。
@@ -181,6 +183,12 @@ window.__ModuleLoader__.load({
181
183
  '.dfv-table{border-collapse:collapse;font-size:12px;font-family:var(--ds-font-family-code);}' +
182
184
  '.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
185
  '.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;}' +
186
+ // 兜底浮层:空会话时原生 tab 条不存在,预览以模态浮层展示
187
+ '.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));}' +
188
+ '.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);}' +
189
+ '.dfv-overlay-panel.dfv-overlay-max{width:100vw;height:100vh;max-width:none;border-radius:0;border:none;}' +
190
+ '.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;}' +
191
+ '.dfv-overlay-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}' +
184
192
  // 纯阅读模式:预览 tab 激活时隐藏整个输入区(composerSeat:输入框 +
185
193
  // GoalBar + 速度徽标所在的 input.dock)与轮次统计行(StatsLine)。
186
194
  // class 来自 DSH 客户端编译产物(CSS module hash),升级后若失效仅
@@ -257,7 +265,7 @@ window.__ModuleLoader__.load({
257
265
  'files.binary': '二进制文件({size}),请在资源管理器中用系统打开',
258
266
  'files.imageTooLarge': '图片过大({size},上限 {limit})',
259
267
  'files.pdfTooLarge': 'PDF 过大({size},上限 {limit}),请用系统打开',
260
- 'files.truncated': '文件超过 2 MB,仅预览前 2 MB;完整内容请用系统编辑器打开。',
268
+ 'files.truncated': '文件超过 8 MB,仅预览前 8 MB;完整内容请用系统编辑器打开。',
261
269
  'files.jsonFail': 'JSON 解析失败,按纯文本显示',
262
270
  'files.nbFail': 'ipynb 解析失败,按纯文本显示',
263
271
  'files.nbEmpty': '(空笔记本)',
@@ -320,7 +328,7 @@ window.__ModuleLoader__.load({
320
328
  'files.binary': 'Binary file ({size}) — open with the system explorer',
321
329
  'files.imageTooLarge': 'Image too large ({size}, limit {limit})',
322
330
  '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.',
331
+ 'files.truncated': 'File exceeds 8 MB — showing the first 8 MB; open in a system editor for the full content.',
324
332
  'files.jsonFail': 'JSON parse failed — shown as plain text',
325
333
  'files.nbFail': 'ipynb parse failed — shown as plain text',
326
334
  'files.nbEmpty': '(empty notebook)',
@@ -348,7 +356,7 @@ window.__ModuleLoader__.load({
348
356
  selected: new Set(), lastIndex: null,
349
357
  renaming: null, newItem: null,
350
358
  dropTarget: null, dragPaths: null,
351
- files: { list: [], active: null },
359
+ files: { list: [], active: null, overlay: false, overlayMax: false, overlayReturn: null },
352
360
  uploads: [],
353
361
  }
354
362
  var listeners = new Set()
@@ -1627,12 +1635,13 @@ window.__ModuleLoader__.load({
1627
1635
  setFiles({ list: list, active: path })
1628
1636
  } else if (entry.loaded) {
1629
1637
  setFiles({ active: path })
1630
- activateFilesView()
1638
+ if (activateFilesView()) return
1639
+ setFiles({ overlay: true, overlayMax: false, overlayReturn: null })
1631
1640
  return
1632
1641
  } else {
1633
1642
  setFiles({ active: path })
1634
1643
  }
1635
- activateFilesView()
1644
+ if (!activateFilesView()) setFiles({ overlay: true, overlayMax: false, overlayReturn: null })
1636
1645
  var res = await rpc('read', { path: path, sessionId: activeSessionId })
1637
1646
  if (!res.ok && /超出允许范围/.test(res.error || '')) {
1638
1647
  await new Promise(function (r) { setTimeout(r, 600) })
@@ -1831,12 +1840,25 @@ window.__ModuleLoader__.load({
1831
1840
  // 可交互预览:给 allow-scripts/popups/forms/modals(对齐 JupyterLab
1832
1841
  // trusted 档),但坚持不给 allow-same-origin——脚本跑在 opaque origin,
1833
1842
  // 触不到 GUI 的 DOM/存储/Cookie;srcdoc 注入 + no-referrer 收敛外链。
1843
+ // 微信文章兼容:正文 #js_content 初始 visibility:hidden 靠 JS 展开,
1844
+ // 图片用 data-src 懒加载靠 JS 换 src——沙箱里两者都跑不起来。
1845
+ // 注入一段 CSS 强制显示正文,并预先把 data-src 落到 src。
1846
+ var text = props.text || ''
1847
+ if (text.indexOf('js_content') !== -1 || text.indexOf('data-src=') !== -1) {
1848
+ var inject = '<style>#js_content{visibility:visible!important;opacity:1!important;}</style>'
1849
+ if (/<\/head>/i.test(text)) {
1850
+ text = text.replace(/<\/head>/i, inject + '</head>')
1851
+ } else {
1852
+ text = inject + text
1853
+ }
1854
+ text = text.replace(/(<img\b[^>]*?)\sdata-src=/gi, '$1 src=')
1855
+ }
1834
1856
  return create('iframe', {
1835
1857
  className: 'dfv-frame',
1836
1858
  sandbox: 'allow-scripts allow-popups allow-forms allow-modals',
1837
1859
  referrerPolicy: 'no-referrer',
1838
1860
  title: props.title || T('files.htmlFrame'),
1839
- srcDoc: props.text,
1861
+ srcDoc: text,
1840
1862
  })
1841
1863
  }
1842
1864
 
@@ -2178,6 +2200,13 @@ window.__ModuleLoader__.load({
2178
2200
  create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }),
2179
2201
  T('files.reload'),
2180
2202
  ) : null,
2203
+ create('button', {
2204
+ type: 'button',
2205
+ className: 'dfv-btn',
2206
+ title: '最大化', 'aria-label': '最大化',
2207
+ onClick: function () { setFiles({ overlay: true, overlayMax: true, overlayReturn: 'tab' }) },
2208
+ dangerouslySetInnerHTML: { __html: EXPAND_SVG },
2209
+ }),
2181
2210
  ),
2182
2211
  create(FileContent, { key: active.path, entry: active }),
2183
2212
  )
@@ -2189,6 +2218,61 @@ window.__ModuleLoader__.load({
2189
2218
  )
2190
2219
  }
2191
2220
 
2221
+ /** 空会话兜底浮层:原生 tab 条不存在时以模态渲染 FileContent。 */
2222
+ function PreviewOverlay() {
2223
+ var s = useStore()
2224
+ var files = s.files
2225
+ if (!files.overlay || !files.active) return null
2226
+ var active = findFileEntry(files.active)
2227
+ if (!active) return null
2228
+ var close = function () { setFiles({ overlay: false, overlayMax: false, overlayReturn: null }) }
2229
+ var closeAndCloseTab = function () {
2230
+ close()
2231
+ closeFileTab(active.path)
2232
+ }
2233
+ var toggleMax = function () {
2234
+ if (files.overlayMax && files.overlayReturn === 'tab') {
2235
+ // 还原 → 回到 tab 模式
2236
+ close()
2237
+ } else {
2238
+ setFiles({ overlayMax: !files.overlayMax })
2239
+ }
2240
+ }
2241
+ return create('div', {
2242
+ className: 'dfv-overlay',
2243
+ onClick: function (e) { if (e.target === e.currentTarget) close() },
2244
+ },
2245
+ create('div', { className: 'dfv-overlay-panel' + (files.overlayMax ? ' dfv-overlay-max' : '') },
2246
+ create('div', { className: 'dfv-toolbar' },
2247
+ create('span', { className: 'dfv-toolbar-path', title: active.path }, active.path),
2248
+ typeof active.size === 'number' && active.size > 0 ? create('span', null, formatBytes(active.size)) : null,
2249
+ active.loaded ? create('button', {
2250
+ type: 'button',
2251
+ className: 'dfv-btn',
2252
+ title: T('files.reloadTitle'),
2253
+ onClick: function () { reloadFile(active.path) },
2254
+ },
2255
+ create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }),
2256
+ T('files.reload'),
2257
+ ) : null,
2258
+ create('button', {
2259
+ type: 'button', className: 'dfv-overlay-close',
2260
+ title: files.overlayMax ? '还原' : '最大化', 'aria-label': files.overlayMax ? '还原' : '最大化',
2261
+ onClick: toggleMax,
2262
+ dangerouslySetInnerHTML: { __html: files.overlayMax ? SHRINK_SVG : EXPAND_SVG },
2263
+ }),
2264
+ create('button', {
2265
+ type: 'button', className: 'dfv-overlay-close',
2266
+ title: files.overlayReturn === 'tab' ? '关闭预览 tab' : '关闭',
2267
+ 'aria-label': files.overlayReturn === 'tab' ? '关闭预览 tab' : '关闭',
2268
+ onClick: closeAndCloseTab,
2269
+ }, '✕'),
2270
+ ),
2271
+ create(FileContent, { key: active.path, entry: active }),
2272
+ ),
2273
+ )
2274
+ }
2275
+
2192
2276
  function findFrameEl(startEl) {
2193
2277
  var cur = startEl && startEl.parentElement
2194
2278
  while (cur) {
@@ -2605,7 +2689,12 @@ window.__ModuleLoader__.load({
2605
2689
  // `details` is a single slot occupied by the shell at priority 0.
2606
2690
  // Lower priorities render first, so this intentionally shadows it.
2607
2691
  { name: 'details', priority: -1, locale: NS },
2608
- function (props) { return create(Panel, props) },
2692
+ function (props) {
2693
+ return create(React.Fragment, null,
2694
+ create(Panel, props),
2695
+ create(PreviewOverlay),
2696
+ )
2697
+ },
2609
2698
  ))
2610
2699
  })
2611
2700
  // 文件标签页:与 对话/轨迹 同级的原生 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 内联上限。 */
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.4",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {