@lyhue1991/dsh-soup 0.2.0 → 0.4.0
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 +98 -104
- package/lib/client.js +477 -82
- package/lib/index.js +78 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -53,8 +53,9 @@ window.__ModuleLoader__.load({
|
|
|
53
53
|
'.expl-head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 14px;border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;}' +
|
|
54
54
|
'.expl-title{font-weight:600;color:var(--dsw-alias-label-primary);white-space:nowrap;font-size:14px;}' +
|
|
55
55
|
'.expl-head-btns{display:flex;gap:2px;flex-wrap:nowrap;}' +
|
|
56
|
-
'.expl-btn{background:transparent;border:none;color:var(--dsw-alias-label-secondary);font-size:15px;cursor:pointer;padding:
|
|
56
|
+
'.expl-btn{width:26px;height:26px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;color:var(--dsw-alias-label-secondary);font-size:15px;cursor:pointer;padding:0;border-radius:8px;flex:none;}' +
|
|
57
57
|
'.expl-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}' +
|
|
58
|
+
'.expl-btn svg{width:16px;height:16px;display:block;}' +
|
|
58
59
|
'.expl-path{font-size:11px;color:var(--dsw-alias-label-secondary);padding:7px 14px;border-bottom:1px solid var(--dsw-alias-border-l1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;text-align:left;flex:none;cursor:default;}' +
|
|
59
60
|
'.expl-error{color:var(--dsw-alias-state-error-primary);padding:6px 14px;font-size:12px;border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;}' +
|
|
60
61
|
'.expl-notice{color:var(--dsw-alias-state-success-primary);padding:6px 14px;font-size:12px;border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;}' +
|
|
@@ -78,7 +79,8 @@ window.__ModuleLoader__.load({
|
|
|
78
79
|
'.expl-muted{color:var(--dsw-alias-label-tertiary);padding:4px 14px;font-size:12px;}' +
|
|
79
80
|
'.expl-menu-mask{position:fixed;inset:0;z-index:1980;}' +
|
|
80
81
|
'.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;}' +
|
|
81
|
-
'.expl-menu-item{display:
|
|
82
|
+
'.expl-menu-item{display:flex;align-items:center;gap:7px;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;}' +
|
|
83
|
+
'.expl-menu-ico{flex:none;display:inline-flex;align-items:center;}.expl-menu-ico svg{width:14px;height:14px;display:block;}' +
|
|
82
84
|
'.expl-menu-item:hover{background:var(--dsw-alias-interactive-bg-hover);}' +
|
|
83
85
|
'.expl-danger{color:var(--dsw-alias-state-error-primary);} .expl-menu-item.expl-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger);}' +
|
|
84
86
|
'.expl-menu-sep{height:1px;margin:4px 6px;background:var(--dsw-alias-border-l1);}' +
|
|
@@ -97,6 +99,14 @@ window.__ModuleLoader__.load({
|
|
|
97
99
|
document.head.appendChild(tag)
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
// ------------------------------------------------------------------
|
|
103
|
+
// 工具栏图标:取自 JupyterLab ui-components(BSD-3-Clause,与文件类型
|
|
104
|
+
// 图标同源的引用方式),fill 改为 currentColor 以跟随 DSH 主题 token。
|
|
105
|
+
// 注意:只引入刷新图标——ICON_CLOSE / ICON_UPLOAD 与 GoalBar 段的
|
|
106
|
+
// 同名 React 元素常量冲突(var 提升覆盖),字符样式反而更稳。
|
|
107
|
+
// ------------------------------------------------------------------
|
|
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
|
+
|
|
100
110
|
// GoalBar 多行版:沿用 DSH 原生 GoalBar 的结构和主题,仅放开单行截断。
|
|
101
111
|
// 布局分两行:head 行(图标 + 相位标签 + 操作按钮),body 行(objective 多行)。
|
|
102
112
|
var GOAL_CSS =
|
|
@@ -137,8 +147,9 @@ window.__ModuleLoader__.load({
|
|
|
137
147
|
'.dfv-body{flex:1;min-height:0;display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-1);}' +
|
|
138
148
|
'.dfv-toolbar{display:flex;align-items:center;gap:8px;flex:none;padding:6px 12px;border-bottom:1px solid var(--dsw-alias-border-l1);font-size:11px;color:var(--dsw-alias-label-secondary);}' +
|
|
139
149
|
'.dfv-toolbar-path{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;direction:rtl;text-align:left;font-family:var(--ds-font-family-code);}' +
|
|
140
|
-
'.dfv-btn{flex:none;background:transparent;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);font-size:
|
|
150
|
+
'.dfv-btn{flex:none;display:inline-flex;align-items:center;gap:5px;height:26px;box-sizing:border-box;background:transparent;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);font-size:12px;line-height:1;padding:0 10px;border-radius:7px;cursor:pointer;}' +
|
|
141
151
|
'.dfv-btn:hover{background:var(--dsw-alias-interactive-bg-hover);}' +
|
|
152
|
+
'.dfv-btn svg{width:14px;height:14px;display:block;flex:none;}' +
|
|
142
153
|
'.dfv-error{padding:8px 14px;font-size:12px;color:var(--dsw-alias-state-error-primary);flex:none;}' +
|
|
143
154
|
'.dfv-muted{padding:24px 14px;font-size:13px;color:var(--dsw-alias-label-tertiary);text-align:center;}' +
|
|
144
155
|
'.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;}' +
|
|
@@ -169,7 +180,13 @@ window.__ModuleLoader__.load({
|
|
|
169
180
|
'.dfv-table-wrap{flex:1;min-height:0;overflow:auto;}' +
|
|
170
181
|
'.dfv-table{border-collapse:collapse;font-size:12px;font-family:var(--ds-font-family-code);}' +
|
|
171
182
|
'.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;}' +
|
|
172
|
-
'.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;}'
|
|
183
|
+
'.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;}' +
|
|
184
|
+
// 纯阅读模式:预览 tab 激活时隐藏整个输入区(composerSeat:输入框 +
|
|
185
|
+
// GoalBar + 速度徽标所在的 input.dock)与轮次统计行(StatsLine)。
|
|
186
|
+
// class 来自 DSH 客户端编译产物(CSS module hash),升级后若失效仅
|
|
187
|
+
// 表现为「预览页仍显示输入区」,不影响其它功能——属可接受的降级。
|
|
188
|
+
'[data-dsh-soup-preview="1"] .wSkVaW_composerSeat{display:none !important;}' +
|
|
189
|
+
'[data-dsh-soup-preview="1"] .FJxK0a_root{display:none !important;}'
|
|
173
190
|
if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="@lyhue1991/dsh-soup/fileview.css"]') === null) {
|
|
174
191
|
var fileviewTag = document.createElement('style')
|
|
175
192
|
fileviewTag.dataset.plugin = '@lyhue1991/dsh-soup'
|
|
@@ -178,8 +195,146 @@ window.__ModuleLoader__.load({
|
|
|
178
195
|
document.head.appendChild(fileviewTag)
|
|
179
196
|
}
|
|
180
197
|
|
|
181
|
-
/** 需要的客户端服务:slots、layout、timer、sessions
|
|
182
|
-
var inject = ['slots', 'layout', 'timer', 'sessions', 'remote', 'remote.goals']
|
|
198
|
+
/** 需要的客户端服务:slots、layout、timer、sessions、locale(i18n 词典)+ remote.goals(goal 动作动词)。 */
|
|
199
|
+
var inject = ['slots', 'layout', 'timer', 'sessions', 'locale', 'remote', 'remote.goals']
|
|
200
|
+
|
|
201
|
+
// ------------------------------------------------------------------
|
|
202
|
+
// i18n:走 DSH 官方插件词典机制(ui-cordis / ui-conversation 同款)——
|
|
203
|
+
// ctx.locale.register(NS, DICT) 注册;槽位配置加 locale: NS 后组件 props
|
|
204
|
+
// 获得 t(语言切换铸造新 t 引用,靠浅比较自动重渲染整棵子树);组件外
|
|
205
|
+
// 回调(菜单 label、错误横幅)用模块级 T(bind 的 t 每次调用解析当前语言)。
|
|
206
|
+
// ------------------------------------------------------------------
|
|
207
|
+
var NS = 'dsh-soup'
|
|
208
|
+
var DICT = {
|
|
209
|
+
zh: {
|
|
210
|
+
'explorer.title': '📁 资源管理器',
|
|
211
|
+
'explorer.label': '资源管理器',
|
|
212
|
+
'explorer.refresh': '刷新',
|
|
213
|
+
'explorer.refreshTitle': '刷新(保持已展开目录)',
|
|
214
|
+
'explorer.upload': '上传文件',
|
|
215
|
+
'explorer.close': '关闭',
|
|
216
|
+
'explorer.closePanel': '关闭资源管理器',
|
|
217
|
+
'explorer.emptyDir': '(空目录)',
|
|
218
|
+
'explorer.bulkSelected': '已选 {n} 项 · 拖到文件夹可移动',
|
|
219
|
+
'explorer.readFail': '读取失败',
|
|
220
|
+
'explorer.actionFail': '失败',
|
|
221
|
+
'explorer.uploading': '上传中 ',
|
|
222
|
+
'explorer.downloading': '下载中 ',
|
|
223
|
+
'explorer.uploaded': '已上传 {n} 个文件',
|
|
224
|
+
'explorer.filesCount': '{n} 个文件',
|
|
225
|
+
'explorer.filesCountPartial': '{ok}/{total} 个文件',
|
|
226
|
+
'explorer.downloaded': '已下载 {n} 个文件',
|
|
227
|
+
'explorer.uploadFail': '上传失败',
|
|
228
|
+
'explorer.downloadFail': '下载失败',
|
|
229
|
+
'explorer.downloadFailWith': '下载失败: {reason}',
|
|
230
|
+
'explorer.downloadStart': '已开始下载 {name}(大文件,由浏览器接管)',
|
|
231
|
+
'explorer.renamed': '已复制路径',
|
|
232
|
+
'explorer.renameFail': '重命名失败',
|
|
233
|
+
'explorer.createFail': '创建失败',
|
|
234
|
+
'explorer.moveIntoSelf': '不能把文件夹移入自身: {path}',
|
|
235
|
+
'explorer.hostUnreachable': '宿主不可达: {reason}',
|
|
236
|
+
'explorer.hostBadResponse': '宿主响应解析失败',
|
|
237
|
+
'menu.preview': '👁 预览',
|
|
238
|
+
'menu.open': '🖥 用系统打开',
|
|
239
|
+
'menu.rename': '✎ 重命名',
|
|
240
|
+
'menu.trash': '🗑 移到废纸篓',
|
|
241
|
+
'menu.trashMulti': '🗑 移到废纸篓 ({n} 项)',
|
|
242
|
+
'menu.newFile': '📄 新建文件',
|
|
243
|
+
'menu.newFolder': '📁 新建文件夹',
|
|
244
|
+
'menu.copyPath': '⧉ 复制路径',
|
|
245
|
+
'menu.download': '⬇ 下载',
|
|
246
|
+
'menu.deselect': '取消选择',
|
|
247
|
+
'files.tab': '预览',
|
|
248
|
+
'files.emptyHint': '在右侧资源管理器中双击或右键「预览」文件',
|
|
249
|
+
'files.loading': '加载中…',
|
|
250
|
+
'files.readFail': '读取文件失败',
|
|
251
|
+
'files.reload': '重载',
|
|
252
|
+
'files.reloadTitle': '从磁盘重新读取(刷新预览)',
|
|
253
|
+
'files.binary': '二进制文件({size}),请在资源管理器中用系统打开',
|
|
254
|
+
'files.imageTooLarge': '图片过大({size},上限 {limit})',
|
|
255
|
+
'files.pdfTooLarge': 'PDF 过大({size},上限 {limit}),请用系统打开',
|
|
256
|
+
'files.truncated': '文件超过 2 MB,仅预览前 2 MB;完整内容请用系统编辑器打开。',
|
|
257
|
+
'files.jsonFail': 'JSON 解析失败,按纯文本显示',
|
|
258
|
+
'files.nbFail': 'ipynb 解析失败,按纯文本显示',
|
|
259
|
+
'files.nbEmpty': '(空笔记本)',
|
|
260
|
+
'files.nbCells': '共 {n} 个单元格',
|
|
261
|
+
'files.csvTruncated': '共 {total} 行,仅预览前 {n} 行',
|
|
262
|
+
'files.htmlFrame': 'HTML 预览',
|
|
263
|
+
'files.pdfFrame': 'PDF 预览',
|
|
264
|
+
'files.pdfLoading': 'PDF 加载中…',
|
|
265
|
+
'files.pdfFail': 'PDF 预览失败: {reason}',
|
|
266
|
+
'files.pdfUnsupported': '当前浏览器无法内嵌预览 PDF,请右键用系统打开。',
|
|
267
|
+
'files.nbHtmlOut': 'HTML 输出',
|
|
268
|
+
'speed.label': '速度徽标',
|
|
269
|
+
'speed.waiting': '正在等待模型',
|
|
270
|
+
'speed.operateFail': '操作失败',
|
|
271
|
+
},
|
|
272
|
+
en: {
|
|
273
|
+
'explorer.title': '📁 Explorer',
|
|
274
|
+
'explorer.label': 'Explorer',
|
|
275
|
+
'explorer.refresh': 'Refresh',
|
|
276
|
+
'explorer.refreshTitle': 'Refresh (keeps expanded folders)',
|
|
277
|
+
'explorer.upload': 'Upload files',
|
|
278
|
+
'explorer.close': 'Close',
|
|
279
|
+
'explorer.closePanel': 'Close explorer',
|
|
280
|
+
'explorer.emptyDir': '(empty)',
|
|
281
|
+
'explorer.bulkSelected': '{n} selected · drag onto a folder to move',
|
|
282
|
+
'explorer.readFail': 'Failed to read',
|
|
283
|
+
'explorer.actionFail': 'Failed',
|
|
284
|
+
'explorer.uploading': 'Uploading ',
|
|
285
|
+
'explorer.downloading': 'Downloading ',
|
|
286
|
+
'explorer.uploaded': 'Uploaded {n} file(s)',
|
|
287
|
+
'explorer.filesCount': '{n} file(s)',
|
|
288
|
+
'explorer.filesCountPartial': '{ok}/{total} file(s)',
|
|
289
|
+
'explorer.downloaded': 'Downloaded {n} file(s)',
|
|
290
|
+
'explorer.uploadFail': 'Upload failed',
|
|
291
|
+
'explorer.downloadFail': 'Download failed',
|
|
292
|
+
'explorer.downloadFailWith': 'Download failed: {reason}',
|
|
293
|
+
'explorer.downloadStart': 'Downloading {name} (large file, handled by the browser)',
|
|
294
|
+
'explorer.renamed': 'Path copied',
|
|
295
|
+
'explorer.renameFail': 'Rename failed',
|
|
296
|
+
'explorer.createFail': 'Create failed',
|
|
297
|
+
'explorer.moveIntoSelf': 'Cannot move a folder into itself: {path}',
|
|
298
|
+
'explorer.hostUnreachable': 'Host unreachable: {reason}',
|
|
299
|
+
'explorer.hostBadResponse': 'Failed to parse host response',
|
|
300
|
+
'menu.preview': '👁 Preview',
|
|
301
|
+
'menu.open': '🖥 Open with system',
|
|
302
|
+
'menu.rename': '✎ Rename',
|
|
303
|
+
'menu.trash': '🗑 Move to trash',
|
|
304
|
+
'menu.trashMulti': '🗑 Move to trash ({n})',
|
|
305
|
+
'menu.newFile': '📄 New file',
|
|
306
|
+
'menu.newFolder': '📁 New folder',
|
|
307
|
+
'menu.copyPath': '⧉ Copy path',
|
|
308
|
+
'menu.download': '⬇ Download',
|
|
309
|
+
'menu.deselect': 'Clear selection',
|
|
310
|
+
'files.tab': 'Preview',
|
|
311
|
+
'files.emptyHint': 'Double-click a file (or right-click → Preview) in the explorer',
|
|
312
|
+
'files.loading': 'Loading…',
|
|
313
|
+
'files.readFail': 'Failed to read file',
|
|
314
|
+
'files.reload': 'Reload',
|
|
315
|
+
'files.reloadTitle': 'Re-read from disk (refresh preview)',
|
|
316
|
+
'files.binary': 'Binary file ({size}) — open with the system explorer',
|
|
317
|
+
'files.imageTooLarge': 'Image too large ({size}, limit {limit})',
|
|
318
|
+
'files.pdfTooLarge': 'PDF too large ({size}, limit {limit}) — open with the system viewer',
|
|
319
|
+
'files.truncated': 'File exceeds 2 MB — showing the first 2 MB; open in a system editor for the full content.',
|
|
320
|
+
'files.jsonFail': 'JSON parse failed — shown as plain text',
|
|
321
|
+
'files.nbFail': 'ipynb parse failed — shown as plain text',
|
|
322
|
+
'files.nbEmpty': '(empty notebook)',
|
|
323
|
+
'files.nbCells': '{n} cells',
|
|
324
|
+
'files.csvTruncated': '{total} rows — showing the first {n}',
|
|
325
|
+
'files.htmlFrame': 'HTML preview',
|
|
326
|
+
'files.pdfFrame': 'PDF preview',
|
|
327
|
+
'files.pdfLoading': 'Loading PDF…',
|
|
328
|
+
'files.pdfFail': 'PDF preview failed: {reason}',
|
|
329
|
+
'files.pdfUnsupported': 'This browser cannot embed PDF previews — right-click and open with the system viewer.',
|
|
330
|
+
'files.nbHtmlOut': 'HTML output',
|
|
331
|
+
'speed.label': 'Speed badge',
|
|
332
|
+
'speed.waiting': 'Waiting for the model',
|
|
333
|
+
'speed.operateFail': 'Action failed',
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
/** 模块级翻译函数(apply 里由 ctx.locale.bind 填充)。 */
|
|
337
|
+
var T = function (key, params) { return key }
|
|
183
338
|
|
|
184
339
|
// ------------------------------------------------------------------
|
|
185
340
|
// 状态存储(模块级,供 Panel 与 HeaderAction 共享)
|
|
@@ -243,9 +398,9 @@ window.__ModuleLoader__.load({
|
|
|
243
398
|
headers: { 'content-type': 'application/json', 'x-dsh-soup': '1' },
|
|
244
399
|
body: JSON.stringify({ action: action, args: args || {} }),
|
|
245
400
|
}).then(function (res) {
|
|
246
|
-
return res.json().catch(function () { return { ok: false, error: '
|
|
401
|
+
return res.json().catch(function () { return { ok: false, error: T('explorer.hostBadResponse') } })
|
|
247
402
|
}).catch(function (err) {
|
|
248
|
-
return { ok: false, error: '
|
|
403
|
+
return { ok: false, error: T('explorer.hostUnreachable', { reason: String((err && err.message) || err) }) }
|
|
249
404
|
})
|
|
250
405
|
}
|
|
251
406
|
|
|
@@ -257,7 +412,7 @@ window.__ModuleLoader__.load({
|
|
|
257
412
|
return loadDir(path, (attempt || 0) + 1)
|
|
258
413
|
}
|
|
259
414
|
if (!res || !res.ok) {
|
|
260
|
-
setState({ error: (res && res.error) || '
|
|
415
|
+
setState({ error: (res && res.error) || T('explorer.readFail') })
|
|
261
416
|
return null
|
|
262
417
|
}
|
|
263
418
|
return res.entries || []
|
|
@@ -281,6 +436,7 @@ window.__ModuleLoader__.load({
|
|
|
281
436
|
var cwd = await defaultCwd(sessionId, cwdHint)
|
|
282
437
|
// 会话切换:预览列表按新 cwd 裁剪(范围外自动关闭),并清掉上一路径的旧错误横幅
|
|
283
438
|
var files = pruneFilesToScope(state.files, cwd)
|
|
439
|
+
lastMtimes = {} // 换了目录,旧 mtime 基线全部作废(首轮 tick 重建基线)
|
|
284
440
|
setState({ cwd: cwd, selected: new Set(), renaming: null, newItem: null, menu: null, error: '', notice: '', files: files })
|
|
285
441
|
var items = await loadDir(cwd)
|
|
286
442
|
if (items) setState({ error: '', tree: items })
|
|
@@ -292,6 +448,150 @@ window.__ModuleLoader__.load({
|
|
|
292
448
|
if (items) setState({ error: '', tree: items })
|
|
293
449
|
}
|
|
294
450
|
|
|
451
|
+
// ------------------------------------------------------------------
|
|
452
|
+
// 自动刷新:低频轮询宿主 mtime 探测(3s 一次),签名变了才真正重拉。
|
|
453
|
+
// watch 范围 = 当前 cwd + 已展开目录 + 打开的预览文件;面板关闭且无
|
|
454
|
+
// 预览时整个 tick 直接返回(零请求)。不做文件 watcher——fs.watch
|
|
455
|
+
// 递归语义跨平台不一致且大工作区开销高,stat 几个路径便宜得多。
|
|
456
|
+
// ------------------------------------------------------------------
|
|
457
|
+
var AUTO_REFRESH_MS = 3000
|
|
458
|
+
var AUTO_WATCH_MAX = 64
|
|
459
|
+
var lastMtimes = {} // path -> {m,s,d}|null;undefined = 尚无基线(首轮只建基线不触发)
|
|
460
|
+
var autoTicking = false
|
|
461
|
+
|
|
462
|
+
function collectWatchPaths() {
|
|
463
|
+
var paths = []
|
|
464
|
+
if (state.cwd) paths.push(state.cwd)
|
|
465
|
+
var walk = function (nodes) {
|
|
466
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
467
|
+
var n = nodes[i]
|
|
468
|
+
if (n.type === 'directory' && n.children != null) {
|
|
469
|
+
paths.push(n.path)
|
|
470
|
+
if (n.open && n.children.length) walk(n.children)
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
walk(state.tree)
|
|
475
|
+
for (var j = 0; j < state.files.list.length; j++) {
|
|
476
|
+
var e = state.files.list[j]
|
|
477
|
+
if (e.path && paths.indexOf(e.path) < 0) paths.push(e.path)
|
|
478
|
+
}
|
|
479
|
+
return paths.slice(0, AUTO_WATCH_MAX)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** 预览文件在磁盘上变了:静默重读该 entry(不动激活状态、不闪 tab)。 */
|
|
483
|
+
async function silentReloadEntry(entry) {
|
|
484
|
+
var res = await rpc('read', { path: entry.path, sessionId: activeSessionId })
|
|
485
|
+
if (!res || !res.ok) return
|
|
486
|
+
entry.kind = res.kind
|
|
487
|
+
entry.size = res.size || 0
|
|
488
|
+
if (res.kind === 'text') { entry.content = res.content; entry.truncated = !!res.truncated }
|
|
489
|
+
else if (res.kind === 'image') { entry.dataUrl = 'data:' + res.mime + ';base64,' + res.data }
|
|
490
|
+
else if (res.kind === 'pdf') { entry.data = res.data }
|
|
491
|
+
setFiles({ list: state.files.list.slice() })
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/** 已展开目录的子项变了:重拉该目录(保持展开态)。 */
|
|
495
|
+
async function reloadDirNode(node) {
|
|
496
|
+
var items = await loadDir(node.path)
|
|
497
|
+
if (items) items.forEach(function (c) { c.parent = node })
|
|
498
|
+
node.children = items || []
|
|
499
|
+
node.open = true
|
|
500
|
+
setState({ tree: state.tree })
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* 探一轮。返回 'idle'(本轮没发请求)/ true(探测成功)/ false(请求失败)。
|
|
505
|
+
* 只有真正发出去且失败的请求才累积退避;空闲不累积——面板重开时本就有
|
|
506
|
+
* 一次全量 loadDir,无需为"关着的时候宿主是否恢复"操心。
|
|
507
|
+
*/
|
|
508
|
+
async function autoRefreshTick() {
|
|
509
|
+
if (autoTicking) return 'idle'
|
|
510
|
+
if (!state.open && state.files.list.length === 0) return 'idle'
|
|
511
|
+
// 用户正在操作(重命名/新建/右键菜单)时跳过本轮,避免打断输入
|
|
512
|
+
if (state.renaming || state.newItem || state.menu) return 'idle'
|
|
513
|
+
var paths = collectWatchPaths()
|
|
514
|
+
if (!paths.length) return 'idle'
|
|
515
|
+
autoTicking = true
|
|
516
|
+
try {
|
|
517
|
+
var res = await rpc('mtime', { paths: paths })
|
|
518
|
+
if (!res || !res.ok) return false
|
|
519
|
+
var mtimes = res.mtimes || {}
|
|
520
|
+
var dirtyDirs = []
|
|
521
|
+
var dirtyFiles = []
|
|
522
|
+
for (var i = 0; i < paths.length; i++) {
|
|
523
|
+
var p = paths[i]
|
|
524
|
+
var now = Object.prototype.hasOwnProperty.call(mtimes, p) ? mtimes[p] : null
|
|
525
|
+
var prev = Object.prototype.hasOwnProperty.call(lastMtimes, p) ? lastMtimes[p] : undefined
|
|
526
|
+
lastMtimes[p] = now
|
|
527
|
+
if (prev === undefined || prev === null || now === null) continue
|
|
528
|
+
if (now.m === prev.m && now.s === prev.s) continue
|
|
529
|
+
if (now.d) dirtyDirs.push(p)
|
|
530
|
+
else dirtyFiles.push(p)
|
|
531
|
+
}
|
|
532
|
+
for (var d = 0; d < dirtyDirs.length; d++) {
|
|
533
|
+
var dp = dirtyDirs[d]
|
|
534
|
+
if (dp === state.cwd) { await refresh(); continue }
|
|
535
|
+
var node = findNode(state.tree, dp)
|
|
536
|
+
if (node && node.children != null) await reloadDirNode(node)
|
|
537
|
+
}
|
|
538
|
+
for (var f = 0; f < dirtyFiles.length; f++) {
|
|
539
|
+
var entry = findFileEntry(dirtyFiles[f])
|
|
540
|
+
if (entry && entry.loaded) await silentReloadEntry(entry)
|
|
541
|
+
}
|
|
542
|
+
return true
|
|
543
|
+
} finally {
|
|
544
|
+
autoTicking = false
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 失败退避:宿主不可达(DSH 重启中 / 插件重载 / 断连)时别每 3s 硬打——
|
|
549
|
+
// 指数退避 3s→6s→12s→24s→48s,封顶 60s;一旦成功回到 3s。
|
|
550
|
+
// (JupyterLab FileBrowserModel 的 Poll 同思路:backoff + max 封顶。)
|
|
551
|
+
// 实现:1s 固定心跳 + 「到期才探测」,而不是变间隔定时器——宿主提供的
|
|
552
|
+
// timer 服务可 dispose,测试环境(interval 为 no-op mock)也不会挂住进程。
|
|
553
|
+
var AUTO_HEARTBEAT_MS = 1000
|
|
554
|
+
var AUTO_BACKOFF_MAX_MS = 60000
|
|
555
|
+
var autoFailCount = 0
|
|
556
|
+
var autoLastProbeAt = 0
|
|
557
|
+
|
|
558
|
+
function autoDelayMs() {
|
|
559
|
+
if (autoFailCount <= 0) return AUTO_REFRESH_MS
|
|
560
|
+
return Math.min(AUTO_REFRESH_MS * Math.pow(2, autoFailCount), AUTO_BACKOFF_MAX_MS)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function autoHeartbeat() {
|
|
564
|
+
var now = Date.now()
|
|
565
|
+
if (now - autoLastProbeAt < autoDelayMs()) return
|
|
566
|
+
autoLastProbeAt = now
|
|
567
|
+
autoRefreshTick().then(function (outcome) {
|
|
568
|
+
if (outcome === false) autoFailCount++
|
|
569
|
+
else autoFailCount = 0
|
|
570
|
+
})
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** 手动刷新:重拉根目录,已展开的子目录逐个重拉、保持展开不收起。 */
|
|
574
|
+
async function refreshAll() {
|
|
575
|
+
if (!state.cwd) return
|
|
576
|
+
var expanded = []
|
|
577
|
+
var walk = function (nodes) {
|
|
578
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
579
|
+
var n = nodes[i]
|
|
580
|
+
if (n.type === 'directory' && n.children != null) {
|
|
581
|
+
if (n.path !== state.cwd) expanded.push({ path: n.path, open: !!n.open })
|
|
582
|
+
if (n.open && n.children.length) walk(n.children)
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
walk(state.tree)
|
|
587
|
+
await refresh()
|
|
588
|
+
for (var j = 0; j < expanded.length; j++) {
|
|
589
|
+
if (!expanded[j].open) continue
|
|
590
|
+
var node = findNode(state.tree, expanded[j].path)
|
|
591
|
+
if (node) await reloadDirNode(node)
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
295
595
|
async function toggleNode(node) {
|
|
296
596
|
if (node.type !== 'directory') return
|
|
297
597
|
if (node.children == null) {
|
|
@@ -443,7 +743,7 @@ window.__ModuleLoader__.load({
|
|
|
443
743
|
var errors = []
|
|
444
744
|
for (var i = 0; i < paths.length; i++) {
|
|
445
745
|
var res = await rpc('trash', { path: paths[i] })
|
|
446
|
-
if (!res || !res.ok) errors.push(baseName(paths[i]) + ': ' + ((res && res.error) || '
|
|
746
|
+
if (!res || !res.ok) errors.push(baseName(paths[i]) + ': ' + ((res && res.error) || T('explorer.actionFail')) + ((res && res.hint) ? '(' + res.hint + ')' : ''))
|
|
447
747
|
}
|
|
448
748
|
if (errors.length) setState({ error: errors.join(';') })
|
|
449
749
|
setState({ selected: new Set(), lastIndex: null })
|
|
@@ -464,7 +764,7 @@ window.__ModuleLoader__.load({
|
|
|
464
764
|
setState({ renaming: null, selected: sel })
|
|
465
765
|
;(async function () {
|
|
466
766
|
var res = await rpc('move', { from: path, to: dest })
|
|
467
|
-
if (!res || !res.ok) setState({ error: (res && res.error) || '
|
|
767
|
+
if (!res || !res.ok) setState({ error: (res && res.error) || T('explorer.renameFail') })
|
|
468
768
|
await refresh()
|
|
469
769
|
})()
|
|
470
770
|
}
|
|
@@ -485,7 +785,7 @@ window.__ModuleLoader__.load({
|
|
|
485
785
|
setState({ newItem: null })
|
|
486
786
|
;(async function () {
|
|
487
787
|
var res = await rpc('create', { dir: parent, name: name, isDir: isDir })
|
|
488
|
-
if (!res || !res.ok) setState({ error: (res && res.error) || '
|
|
788
|
+
if (!res || !res.ok) setState({ error: (res && res.error) || T('explorer.createFail') })
|
|
489
789
|
await refresh()
|
|
490
790
|
})()
|
|
491
791
|
}
|
|
@@ -505,7 +805,7 @@ window.__ModuleLoader__.load({
|
|
|
505
805
|
setState({ menu: null })
|
|
506
806
|
try {
|
|
507
807
|
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
|
|
508
|
-
navigator.clipboard.writeText(p).then(function () { showNotice('
|
|
808
|
+
navigator.clipboard.writeText(p).then(function () { showNotice(T('explorer.renamed')) }).catch(function () {})
|
|
509
809
|
}
|
|
510
810
|
} catch (err) {}
|
|
511
811
|
}
|
|
@@ -547,9 +847,9 @@ window.__ModuleLoader__.load({
|
|
|
547
847
|
var name = baseName(p)
|
|
548
848
|
var dest = pathJoin(targetDir, name)
|
|
549
849
|
if (dest === p) continue
|
|
550
|
-
if (targetDir === p || targetDir.startsWith(p + '/')) { errors.push('
|
|
850
|
+
if (targetDir === p || targetDir.startsWith(p + '/')) { errors.push(T('explorer.moveIntoSelf', { path: name })); continue }
|
|
551
851
|
var res = await rpc('move', { from: p, to: dest })
|
|
552
|
-
if (!res || !res.ok) errors.push(name + ': ' + ((res && res.error) || '
|
|
852
|
+
if (!res || !res.ok) errors.push(name + ': ' + ((res && res.error) || T('explorer.actionFail')))
|
|
553
853
|
}
|
|
554
854
|
setState({ dragPaths: null })
|
|
555
855
|
if (errors.length) setState({ error: errors.join(';') })
|
|
@@ -560,7 +860,7 @@ window.__ModuleLoader__.load({
|
|
|
560
860
|
return new Promise(function (resolve, reject) {
|
|
561
861
|
var r = new FileReader()
|
|
562
862
|
r.onload = function () { var s = String(r.result || ''); var i = s.indexOf(','); resolve(i >= 0 ? s.slice(i + 1) : s) }
|
|
563
|
-
r.onerror = function () { reject(new Error('
|
|
863
|
+
r.onerror = function () { reject(new Error(T('files.readFail'))) }
|
|
564
864
|
r.readAsDataURL(file)
|
|
565
865
|
})
|
|
566
866
|
}
|
|
@@ -583,9 +883,9 @@ window.__ModuleLoader__.load({
|
|
|
583
883
|
}
|
|
584
884
|
|
|
585
885
|
function finishUploads(count, okCount, failedName, down) {
|
|
586
|
-
var verb = down ? '
|
|
886
|
+
var verb = down ? T('explorer.downloaded') : T('explorer.uploaded')
|
|
587
887
|
setUploadEntry({
|
|
588
|
-
name: '✓ ' + verb + ' ' + (okCount === count ?
|
|
888
|
+
name: '✓ ' + verb + ' ' + (okCount === count ? T('explorer.filesCount', { n: count }) : T('explorer.filesCountPartial', { ok: okCount, total: count }) + (failedName ? '(' + failedName + ')' : '')),
|
|
589
889
|
loaded: 0, total: 0, idx: 0, totalFiles: 0, done: true, down: !!down,
|
|
590
890
|
})
|
|
591
891
|
window.setTimeout(function () { setState({ uploads: [] }) }, 1600)
|
|
@@ -604,7 +904,7 @@ window.__ModuleLoader__.load({
|
|
|
604
904
|
dir: dir, name: f.name, data: b64,
|
|
605
905
|
chunk: chunked ? c + 1 : undefined,
|
|
606
906
|
})
|
|
607
|
-
if (!res || !res.ok) throw new Error((res && res.error) || '
|
|
907
|
+
if (!res || !res.ok) throw new Error((res && res.error) || T('explorer.uploadFail'))
|
|
608
908
|
updateUpload(f.name, Math.min(f.size, start + UPLOAD_CHUNK_SIZE), f.size, idx, totalFiles)
|
|
609
909
|
}
|
|
610
910
|
}
|
|
@@ -638,7 +938,7 @@ window.__ModuleLoader__.load({
|
|
|
638
938
|
res = await rpc('download', { path: path, sessionId: activeSessionId })
|
|
639
939
|
}
|
|
640
940
|
if (!res || !res.ok) {
|
|
641
|
-
setState({ error: (res && res.error) || '
|
|
941
|
+
setState({ error: (res && res.error) || T('explorer.downloadFail') })
|
|
642
942
|
return
|
|
643
943
|
}
|
|
644
944
|
var name = res.name || baseName(path)
|
|
@@ -651,7 +951,7 @@ window.__ModuleLoader__.load({
|
|
|
651
951
|
document.body.appendChild(a)
|
|
652
952
|
a.click()
|
|
653
953
|
a.remove()
|
|
654
|
-
showNotice('
|
|
954
|
+
showNotice(T('explorer.downloadStart', { name: name }))
|
|
655
955
|
return
|
|
656
956
|
}
|
|
657
957
|
// 中小文件:fetch 流式读字节计进度,完成后 Blob 另存(峰值≈文件体积)
|
|
@@ -680,7 +980,7 @@ window.__ModuleLoader__.load({
|
|
|
680
980
|
window.setTimeout(function () { URL.revokeObjectURL(blobUrl) }, 5000)
|
|
681
981
|
finishUploads(1, 1, '', true)
|
|
682
982
|
} catch (err) {
|
|
683
|
-
setState({ error: '
|
|
983
|
+
setState({ error: T('explorer.downloadFailWith', { reason: String((err && err.message) || err) }), uploads: [] })
|
|
684
984
|
}
|
|
685
985
|
}
|
|
686
986
|
|
|
@@ -696,7 +996,7 @@ window.__ModuleLoader__.load({
|
|
|
696
996
|
// markdown/html/json/csv 有专属渲染器;图片预览;其余按纯文本。
|
|
697
997
|
// 状态存模块级 store,切走 tab 再切回来内容不丢。
|
|
698
998
|
// ------------------------------------------------------------------
|
|
699
|
-
var FILES_TAB_LABEL = '
|
|
999
|
+
var FILES_TAB_LABEL = function () { return T('files.tab') }
|
|
700
1000
|
|
|
701
1001
|
/** 当前活跃会话:list/read/download 携带给宿主,供围栏直查该会话 cwd。 */
|
|
702
1002
|
var activeSessionId = null
|
|
@@ -745,7 +1045,7 @@ window.__ModuleLoader__.load({
|
|
|
745
1045
|
try {
|
|
746
1046
|
var tabs = document.querySelectorAll('[role="tab"]')
|
|
747
1047
|
for (var i = 0; i < tabs.length; i++) {
|
|
748
|
-
if (tabs[i].textContent === FILES_TAB_LABEL) { tabs[i].click(); return true }
|
|
1048
|
+
if (tabs[i].textContent === FILES_TAB_LABEL()) { tabs[i].click(); return true }
|
|
749
1049
|
}
|
|
750
1050
|
} catch (err) {}
|
|
751
1051
|
return false
|
|
@@ -783,7 +1083,7 @@ window.__ModuleLoader__.load({
|
|
|
783
1083
|
if (!cur) return
|
|
784
1084
|
cur.loading = false
|
|
785
1085
|
if (!res || !res.ok) {
|
|
786
|
-
cur.error = (res && res.error) || '
|
|
1086
|
+
cur.error = (res && res.error) || T('explorer.readFail')
|
|
787
1087
|
} else {
|
|
788
1088
|
cur.error = ''
|
|
789
1089
|
cur.loaded = true
|
|
@@ -915,7 +1215,7 @@ window.__ModuleLoader__.load({
|
|
|
915
1215
|
}
|
|
916
1216
|
if (isDir && node.open) {
|
|
917
1217
|
if (node.loading) {
|
|
918
|
-
children.push(create('div', { key: '__loading__', className: 'expl-muted', style: { paddingLeft: 8 + (depth + 1) * 16 } }, '
|
|
1218
|
+
children.push(create('div', { key: '__loading__', className: 'expl-muted', style: { paddingLeft: 8 + (depth + 1) * 16 } }, T('files.loading')))
|
|
919
1219
|
} else {
|
|
920
1220
|
;(node.children || []).forEach(function (c) { children.push(create(Row, { key: c.path, node: c, depth: depth + 1 })) })
|
|
921
1221
|
}
|
|
@@ -930,29 +1230,29 @@ window.__ModuleLoader__.load({
|
|
|
930
1230
|
if (node) {
|
|
931
1231
|
if (!multi) {
|
|
932
1232
|
if (node.type !== 'directory') {
|
|
933
|
-
items.push({ key: 'open-tab', label: '
|
|
934
|
-
items.push({ key: 'open', label: '
|
|
1233
|
+
items.push({ key: 'open-tab', label: T('menu.preview'), onClick: function () { openFileInTab(node.path) } })
|
|
1234
|
+
items.push({ key: 'open', label: T('menu.open'), onClick: openSelection, separatorAfter: true })
|
|
935
1235
|
} else {
|
|
936
|
-
items.push({ key: 'open', label: '
|
|
1236
|
+
items.push({ key: 'open', label: T('menu.open'), onClick: openSelection, separatorAfter: true })
|
|
937
1237
|
}
|
|
938
|
-
items.push({ key: 'rename', label: '
|
|
939
|
-
items.push({ key: 'trash', label: '
|
|
1238
|
+
items.push({ key: 'rename', label: T('menu.rename'), onClick: function () { startRename(node) } })
|
|
1239
|
+
items.push({ key: 'trash', label: T('menu.trash'), danger: true, onClick: trashSelection, separatorAfter: true })
|
|
940
1240
|
} else {
|
|
941
|
-
items.push({ key: 'trash', label: '
|
|
1241
|
+
items.push({ key: 'trash', label: T('menu.trashMulti', { n: state.selected.size }), danger: true, onClick: trashSelection, separatorAfter: true })
|
|
942
1242
|
}
|
|
943
1243
|
if (node.type === 'directory') {
|
|
944
|
-
items.push({ key: 'newfile', label: '
|
|
945
|
-
items.push({ key: 'newfolder', label: '
|
|
1244
|
+
items.push({ key: 'newfile', label: T('menu.newFile'), onClick: function () { startNew(node.path, false) } })
|
|
1245
|
+
items.push({ key: 'newfolder', label: T('menu.newFolder'), onClick: function () { startNew(node.path, true) }, separatorAfter: true })
|
|
946
1246
|
}
|
|
947
|
-
items.push({ key: 'copy', label: '
|
|
1247
|
+
items.push({ key: 'copy', label: T('menu.copyPath'), onClick: function () { copyPath(node) } })
|
|
948
1248
|
if (node.type !== 'directory') {
|
|
949
|
-
items.push({ key: 'download', label: '
|
|
1249
|
+
items.push({ key: 'download', label: T('menu.download'), onClick: function () { downloadFile(node.path) } })
|
|
950
1250
|
}
|
|
951
1251
|
} else {
|
|
952
|
-
items.push({ key: 'newfile', label: '
|
|
953
|
-
items.push({ key: 'newfolder', label: '
|
|
954
|
-
items.push({ key: 'refresh', label: '
|
|
955
|
-
if (state.selected.size > 0) items.push({ key: 'none', label: '
|
|
1252
|
+
items.push({ key: 'newfile', label: T('menu.newFile'), onClick: function () { startNew(state.cwd, false) } })
|
|
1253
|
+
items.push({ key: 'newfolder', label: T('menu.newFolder'), onClick: function () { startNew(state.cwd, true) } })
|
|
1254
|
+
items.push({ key: 'refresh', label: [create('span', { className: 'expl-menu-ico', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }), T('explorer.refresh')], onClick: refresh, separatorAfter: true })
|
|
1255
|
+
if (state.selected.size > 0) items.push({ key: 'none', label: T('menu.deselect'), onClick: function () { setState({ selected: new Set(), lastIndex: null }) } })
|
|
956
1256
|
}
|
|
957
1257
|
return items
|
|
958
1258
|
}
|
|
@@ -1100,13 +1400,46 @@ window.__ModuleLoader__.load({
|
|
|
1100
1400
|
var CSV_MAX_ROWS = 500
|
|
1101
1401
|
var CSV_MAX_COLS = 64
|
|
1102
1402
|
|
|
1403
|
+
// ------------------------------------------------------------------
|
|
1404
|
+
// Markdown 相对图片 → dsh-soup 同源 img 端点绝对 URL。
|
|
1405
|
+
// 预览 markdown 时,DSH 的 MarkdownText 渲染器「禁相对链接/相对资源」,
|
|
1406
|
+
// `` 这类相对路径图片会被直接丢弃。因此把 src 改写成
|
|
1407
|
+
// 本插件同名端点(GET /api/dsh-soup/img)的同源绝对地址,交给 <img>
|
|
1408
|
+
// 从磁盘内联加载。绝对(http/https/data 等)与根相对路径放行不处理。
|
|
1409
|
+
// ------------------------------------------------------------------
|
|
1410
|
+
var MD_IMG_RE = /!\[([^\]]*)\]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/g
|
|
1411
|
+
function posixResolve(dir, rel) {
|
|
1412
|
+
var combined = (String(dir || '/').replace(/\/+$/, '') || '/') + '/' + String(rel)
|
|
1413
|
+
var out = []
|
|
1414
|
+
combined.split('/').forEach(function (seg) {
|
|
1415
|
+
if (seg === '' || seg === '.') return
|
|
1416
|
+
if (seg === '..') { if (out.length) out.pop(); return }
|
|
1417
|
+
out.push(seg)
|
|
1418
|
+
})
|
|
1419
|
+
return '/' + out.join('/')
|
|
1420
|
+
}
|
|
1421
|
+
function imgUrl(absPath) {
|
|
1422
|
+
return hostBase() + '/api/dsh-soup/img?p=' + encodeURIComponent(absPath)
|
|
1423
|
+
}
|
|
1424
|
+
function absolutizeMarkdownImages(text, baseDir) {
|
|
1425
|
+
if (!text) return text
|
|
1426
|
+
var dir = baseDir || '/'
|
|
1427
|
+
return text.replace(MD_IMG_RE, function (m0, alt, src) {
|
|
1428
|
+
var s = String(src || '').trim()
|
|
1429
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|#|\/)/i.test(s)) return m0
|
|
1430
|
+
return ') + ')'
|
|
1431
|
+
})
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1103
1434
|
function MarkdownPreview(props) {
|
|
1435
|
+
var text = props.text
|
|
1104
1436
|
if (MarkdownText) {
|
|
1437
|
+
var md = props.baseDir ? absolutizeMarkdownImages(text, props.baseDir) : text
|
|
1105
1438
|
return create('div', { className: 'dfv-md-wrap' },
|
|
1106
|
-
create('div', { className: 'dfv-md' }, create(MarkdownText, { text:
|
|
1439
|
+
create('div', { className: 'dfv-md' }, create(MarkdownText, { text: md })))
|
|
1107
1440
|
}
|
|
1108
1441
|
// 回退:宿主缺 ui-primitives 时按纯文本展示
|
|
1109
|
-
return create('pre', { className: 'dfv-code' },
|
|
1442
|
+
return create('pre', { className: 'dfv-code' }, text)
|
|
1110
1443
|
}
|
|
1111
1444
|
|
|
1112
1445
|
function HtmlPreview(props) {
|
|
@@ -1117,7 +1450,7 @@ window.__ModuleLoader__.load({
|
|
|
1117
1450
|
className: 'dfv-frame',
|
|
1118
1451
|
sandbox: 'allow-scripts allow-popups allow-forms allow-modals',
|
|
1119
1452
|
referrerPolicy: 'no-referrer',
|
|
1120
|
-
title: props.title || '
|
|
1453
|
+
title: props.title || T('files.htmlFrame'),
|
|
1121
1454
|
srcDoc: props.text,
|
|
1122
1455
|
})
|
|
1123
1456
|
}
|
|
@@ -1146,14 +1479,14 @@ window.__ModuleLoader__.load({
|
|
|
1146
1479
|
if (made) URL.revokeObjectURL(made)
|
|
1147
1480
|
}
|
|
1148
1481
|
}, [props.data])
|
|
1149
|
-
if (state.error) return create('div', { className: 'dfv-error' }, '
|
|
1150
|
-
if (!state.url) return create('div', { className: 'dfv-empty' }, '
|
|
1482
|
+
if (state.error) return create('div', { className: 'dfv-error' }, T('files.pdfFail', { reason: state.error }))
|
|
1483
|
+
if (!state.url) return create('div', { className: 'dfv-empty' }, T('files.pdfLoading'))
|
|
1151
1484
|
return create('object', {
|
|
1152
1485
|
className: 'dfv-frame dfv-pdf',
|
|
1153
1486
|
type: 'application/pdf',
|
|
1154
1487
|
data: state.url,
|
|
1155
|
-
'aria-label': '
|
|
1156
|
-
}, '
|
|
1488
|
+
'aria-label': T('files.pdfFrame'),
|
|
1489
|
+
}, T('files.pdfUnsupported'))
|
|
1157
1490
|
}
|
|
1158
1491
|
|
|
1159
1492
|
// ------------------------------------------------------------------
|
|
@@ -1206,7 +1539,7 @@ window.__ModuleLoader__.load({
|
|
|
1206
1539
|
return create('img', { className: 'dfv-nb-img', src: 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(nbSource(out.data[mime])))), alt: 'notebook output svg' })
|
|
1207
1540
|
}
|
|
1208
1541
|
if (mime === 'text/html') {
|
|
1209
|
-
return create('iframe', { className: 'dfv-nb-html', sandbox: '', title: '
|
|
1542
|
+
return create('iframe', { className: 'dfv-nb-html', sandbox: '', title: T('files.nbHtmlOut'), srcDoc: nbSource(out.data[mime]) })
|
|
1210
1543
|
}
|
|
1211
1544
|
if (mime === 'text/markdown' && MarkdownText) {
|
|
1212
1545
|
return create('div', { className: 'dfv-md-wrap dfv-nb-md' }, create(MarkdownText, { text: nbSource(out.data[mime]) }))
|
|
@@ -1222,7 +1555,7 @@ window.__ModuleLoader__.load({
|
|
|
1222
1555
|
try { nb = JSON.parse(props.text) } catch (err) { nb = undefined }
|
|
1223
1556
|
if (nb === undefined || typeof nb !== 'object' || nb === null) {
|
|
1224
1557
|
return create(React.Fragment, null,
|
|
1225
|
-
create('div', { className: 'dfv-cap' }, '
|
|
1558
|
+
create('div', { className: 'dfv-cap' }, T('files.nbFail')),
|
|
1226
1559
|
create('pre', { className: 'dfv-code' }, props.text))
|
|
1227
1560
|
}
|
|
1228
1561
|
var cells = Array.isArray(nb.cells) ? nb.cells : []
|
|
@@ -1269,8 +1602,8 @@ window.__ModuleLoader__.load({
|
|
|
1269
1602
|
|
|
1270
1603
|
return create('div', { className: 'dfv-nb' },
|
|
1271
1604
|
create('div', { className: 'dfv-cap' },
|
|
1272
|
-
'Jupyter Notebook' + (kernelName ? ' · ' + kernelName : '') + ' · ' + cells.length
|
|
1273
|
-
cellEls.length ? cellEls : create('div', { className: 'dfv-muted' }, '
|
|
1605
|
+
'Jupyter Notebook' + (kernelName ? ' · ' + kernelName : '') + ' · ' + T('files.nbCells', { n: cells.length })),
|
|
1606
|
+
cellEls.length ? cellEls : create('div', { className: 'dfv-muted' }, T('files.nbEmpty')))
|
|
1274
1607
|
}
|
|
1275
1608
|
|
|
1276
1609
|
function JsonPreview(props) {
|
|
@@ -1278,7 +1611,7 @@ window.__ModuleLoader__.load({
|
|
|
1278
1611
|
try { JSON.parse(props.text) } catch (err) { failed = true }
|
|
1279
1612
|
if (failed) {
|
|
1280
1613
|
return create(React.Fragment, null,
|
|
1281
|
-
create('div', { className: 'dfv-cap' }, '
|
|
1614
|
+
create('div', { className: 'dfv-cap' }, T('files.jsonFail')),
|
|
1282
1615
|
create('pre', { className: 'dfv-code' }, props.text))
|
|
1283
1616
|
}
|
|
1284
1617
|
return create('pre', { className: 'dfv-json dfv-code' }, jsonColorNodes(JSON.stringify(JSON.parse(props.text), null, 2)))
|
|
@@ -1295,7 +1628,7 @@ window.__ModuleLoader__.load({
|
|
|
1295
1628
|
cols = Math.max(cols, Math.min(rows[r].length, CSV_MAX_COLS))
|
|
1296
1629
|
}
|
|
1297
1630
|
var caps = []
|
|
1298
|
-
if (totalRows > CSV_MAX_ROWS + 1) caps.push('
|
|
1631
|
+
if (totalRows > CSV_MAX_ROWS + 1) caps.push(T('files.csvTruncated', { total: totalRows, n: CSV_MAX_ROWS }))
|
|
1299
1632
|
var trs = []
|
|
1300
1633
|
for (var ri = 1; ri <= Math.min(totalRows - 1, CSV_MAX_ROWS); ri++) {
|
|
1301
1634
|
var cells = rows[ri]
|
|
@@ -1331,18 +1664,18 @@ window.__ModuleLoader__.load({
|
|
|
1331
1664
|
function FileContent(props) {
|
|
1332
1665
|
var entry = props.entry
|
|
1333
1666
|
if (entry.loading) {
|
|
1334
|
-
return create('div', { className: 'dfv-empty' }, '
|
|
1667
|
+
return create('div', { className: 'dfv-empty' }, T('files.loading'))
|
|
1335
1668
|
}
|
|
1336
1669
|
if (entry.error) {
|
|
1337
1670
|
return create('div', { className: 'dfv-error' }, entry.error)
|
|
1338
1671
|
}
|
|
1339
1672
|
if (entry.kind === 'binary') {
|
|
1340
1673
|
return create('div', { className: 'dfv-muted' },
|
|
1341
|
-
'
|
|
1674
|
+
T('files.binary', { size: formatBytes(entry.size) }))
|
|
1342
1675
|
}
|
|
1343
1676
|
if (entry.kind === 'image-too-large') {
|
|
1344
1677
|
return create('div', { className: 'dfv-muted' },
|
|
1345
|
-
'
|
|
1678
|
+
T('files.imageTooLarge', { size: formatBytes(entry.size), limit: formatBytes(entry.limit) }))
|
|
1346
1679
|
}
|
|
1347
1680
|
if (entry.kind === 'image') {
|
|
1348
1681
|
return create('div', { className: 'dfv-image-wrap' },
|
|
@@ -1350,7 +1683,7 @@ window.__ModuleLoader__.load({
|
|
|
1350
1683
|
}
|
|
1351
1684
|
if (entry.kind === 'pdf-too-large') {
|
|
1352
1685
|
return create('div', { className: 'dfv-muted' },
|
|
1353
|
-
'
|
|
1686
|
+
T('files.pdfTooLarge', { size: formatBytes(entry.size), limit: formatBytes(entry.limit) }))
|
|
1354
1687
|
}
|
|
1355
1688
|
if (entry.kind === 'pdf') {
|
|
1356
1689
|
return create(PdfPreview, { data: entry.data })
|
|
@@ -1358,7 +1691,7 @@ window.__ModuleLoader__.load({
|
|
|
1358
1691
|
// 文本类:按扩展名选预览渲染器(纯只读,无编辑)
|
|
1359
1692
|
var pk = previewKind(entry.path)
|
|
1360
1693
|
var view = null
|
|
1361
|
-
if (pk === 'markdown') view = create(MarkdownPreview, { text: entry.content })
|
|
1694
|
+
if (pk === 'markdown') view = create(MarkdownPreview, { text: entry.content, baseDir: parentOf(entry.path) })
|
|
1362
1695
|
else if (pk === 'html') view = create(HtmlPreview, { text: entry.content, title: entry.name })
|
|
1363
1696
|
else if (pk === 'json') view = create(JsonPreview, { text: entry.content })
|
|
1364
1697
|
else if (pk === 'notebook') view = create(NotebookPreview, { text: entry.content })
|
|
@@ -1376,15 +1709,51 @@ window.__ModuleLoader__.load({
|
|
|
1376
1709
|
}
|
|
1377
1710
|
return create(React.Fragment, null,
|
|
1378
1711
|
entry.truncated ? create('div', { className: 'dfv-cap' },
|
|
1379
|
-
'
|
|
1712
|
+
T('files.truncated')) : null,
|
|
1380
1713
|
view,
|
|
1381
1714
|
)
|
|
1382
1715
|
}
|
|
1383
1716
|
|
|
1717
|
+
/**
|
|
1718
|
+
* 纯阅读模式标记:在会话根(含 composerSeat 的最近祖先,即 DSH
|
|
1719
|
+
* ConversationRoot 的 root 节点)上打 data-dsh-soup-preview,由上面
|
|
1720
|
+
* 注入的 CSS 隐藏输入区与轮次统计行。DSH 重渲染可能重建该节点——
|
|
1721
|
+
* MutationObserver 在 FilesView 存活期间持续确保标记存在。
|
|
1722
|
+
*/
|
|
1723
|
+
function usePreviewReadingMode(rootRef) {
|
|
1724
|
+
React.useEffect(function () {
|
|
1725
|
+
var observer = null
|
|
1726
|
+
var marked = null
|
|
1727
|
+
function apply() {
|
|
1728
|
+
var el = rootRef.current
|
|
1729
|
+
if (!el) return
|
|
1730
|
+
if (marked && marked.contains(el) && marked.getAttribute('data-dsh-soup-preview') === '1') return
|
|
1731
|
+
var host = el.parentElement
|
|
1732
|
+
while (host) {
|
|
1733
|
+
if (host.querySelector && host.querySelector('.wSkVaW_composerSeat')) break
|
|
1734
|
+
host = host.parentElement
|
|
1735
|
+
}
|
|
1736
|
+
if (!host) return
|
|
1737
|
+
host.setAttribute('data-dsh-soup-preview', '1')
|
|
1738
|
+
marked = host
|
|
1739
|
+
}
|
|
1740
|
+
apply()
|
|
1741
|
+
observer = new MutationObserver(function () { apply() })
|
|
1742
|
+
observer.observe(document.body, { childList: true, subtree: true })
|
|
1743
|
+
return function () {
|
|
1744
|
+
if (observer) observer.disconnect()
|
|
1745
|
+
if (marked) marked.removeAttribute('data-dsh-soup-preview')
|
|
1746
|
+
marked = null
|
|
1747
|
+
}
|
|
1748
|
+
}, [])
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1384
1751
|
function FilesView(props) {
|
|
1385
1752
|
var s = useStore()
|
|
1386
1753
|
var files = s.files
|
|
1387
1754
|
var active = files.active ? findFileEntry(files.active) : null
|
|
1755
|
+
var rootRef = React.useRef(null)
|
|
1756
|
+
usePreviewReadingMode(rootRef)
|
|
1388
1757
|
|
|
1389
1758
|
var tabs = files.list.map(function (entry) {
|
|
1390
1759
|
return create('button', {
|
|
@@ -1398,7 +1767,7 @@ window.__ModuleLoader__.load({
|
|
|
1398
1767
|
create('span', {
|
|
1399
1768
|
className: 'dfv-close',
|
|
1400
1769
|
role: 'button',
|
|
1401
|
-
title: '
|
|
1770
|
+
title: T('explorer.close'),
|
|
1402
1771
|
onClick: function (e) { e.stopPropagation(); closeFileTab(entry.path) },
|
|
1403
1772
|
}, '✕'),
|
|
1404
1773
|
)
|
|
@@ -1408,7 +1777,7 @@ window.__ModuleLoader__.load({
|
|
|
1408
1777
|
if (!active) {
|
|
1409
1778
|
body = create('div', { className: 'dfv-empty' },
|
|
1410
1779
|
create('span', { style: { fontSize: 22 } }, '📄'),
|
|
1411
|
-
'
|
|
1780
|
+
T('files.emptyHint'),
|
|
1412
1781
|
)
|
|
1413
1782
|
} else {
|
|
1414
1783
|
body = create('div', { className: 'dfv-body' },
|
|
@@ -1418,15 +1787,18 @@ window.__ModuleLoader__.load({
|
|
|
1418
1787
|
active.loaded ? create('button', {
|
|
1419
1788
|
type: 'button',
|
|
1420
1789
|
className: 'dfv-btn',
|
|
1421
|
-
title: '
|
|
1790
|
+
title: T('files.reloadTitle'),
|
|
1422
1791
|
onClick: function () { reloadFile(active.path) },
|
|
1423
|
-
},
|
|
1792
|
+
},
|
|
1793
|
+
create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }),
|
|
1794
|
+
T('files.reload'),
|
|
1795
|
+
) : null,
|
|
1424
1796
|
),
|
|
1425
1797
|
create(FileContent, { key: active.path, entry: active }),
|
|
1426
1798
|
)
|
|
1427
1799
|
}
|
|
1428
1800
|
|
|
1429
|
-
return create('div', { className: 'dfv-root' },
|
|
1801
|
+
return create('div', { className: 'dfv-root', ref: rootRef },
|
|
1430
1802
|
files.list.length > 0 ? create('div', { className: 'dfv-tabbar' }, tabs) : null,
|
|
1431
1803
|
body,
|
|
1432
1804
|
)
|
|
@@ -1566,7 +1938,7 @@ window.__ModuleLoader__.load({
|
|
|
1566
1938
|
))
|
|
1567
1939
|
}
|
|
1568
1940
|
if (s.tree.length === 0 && !(state.newItem && state.newItem.parent === state.cwd)) {
|
|
1569
|
-
bodyChildren.push(create('div', { key: '__empty__', className: 'expl-muted' }, '
|
|
1941
|
+
bodyChildren.push(create('div', { key: '__empty__', className: 'expl-muted' }, T('explorer.emptyDir')))
|
|
1570
1942
|
} else {
|
|
1571
1943
|
s.tree.forEach(function (node) { bodyChildren.push(create(Row, { key: node.path, node: node, depth: 0 })) })
|
|
1572
1944
|
}
|
|
@@ -1583,17 +1955,29 @@ window.__ModuleLoader__.load({
|
|
|
1583
1955
|
return create('div', { ref: panelRef, className: 'expl-panel' },
|
|
1584
1956
|
create('div', { className: 'expl-resize', onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onPointerCancel: onResizeUp }),
|
|
1585
1957
|
create('div', { className: 'expl-head' },
|
|
1586
|
-
create('span', { className: 'expl-title' }, '
|
|
1958
|
+
create('span', { className: 'expl-title' }, T('explorer.title')),
|
|
1587
1959
|
create('div', { className: 'expl-head-btns' },
|
|
1588
|
-
create('button', { className: 'expl-btn', onClick: function () { if (uploadInputEl) uploadInputEl.click() }, title: '上传文件' }, '⬆'),
|
|
1589
1960
|
create('button', {
|
|
1590
1961
|
className: 'expl-btn',
|
|
1962
|
+
onClick: function () { refreshAll() },
|
|
1963
|
+
title: T('explorer.refreshTitle'),
|
|
1964
|
+
'aria-label': T('explorer.refresh'),
|
|
1965
|
+
}, create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } })),
|
|
1966
|
+
create('button', {
|
|
1967
|
+
className: 'expl-btn',
|
|
1968
|
+
onClick: function () { if (uploadInputEl) uploadInputEl.click() },
|
|
1969
|
+
title: T('explorer.upload'),
|
|
1970
|
+
'aria-label': T('explorer.upload'),
|
|
1971
|
+
}, '⬆'),
|
|
1972
|
+
create('button', {
|
|
1973
|
+
className: 'expl-btn',
|
|
1974
|
+
style: { fontSize: '18px' },
|
|
1591
1975
|
onClick: function (event) {
|
|
1592
1976
|
try { layout.closeDetails() } catch (e) {}
|
|
1593
1977
|
setHeroDetailsWidth(event.currentTarget, 0)
|
|
1594
1978
|
},
|
|
1595
|
-
title: '
|
|
1596
|
-
'aria-label': '
|
|
1979
|
+
title: T('explorer.close'),
|
|
1980
|
+
'aria-label': T('explorer.closePanel'),
|
|
1597
1981
|
}, '✕'),
|
|
1598
1982
|
),
|
|
1599
1983
|
),
|
|
@@ -1605,7 +1989,7 @@ window.__ModuleLoader__.load({
|
|
|
1605
1989
|
create('span', { className: 'expl-upload-glyph' }, u.done ? '✓' : (u.down ? '⬇' : '⬆')),
|
|
1606
1990
|
create('span', { className: 'expl-upload-text' },
|
|
1607
1991
|
u.done ? u.name
|
|
1608
|
-
: (u.down ? '
|
|
1992
|
+
: (u.down ? T('explorer.downloading') : T('explorer.uploading')) + (u.totalFiles > 1 ? '(' + u.idx + '/' + u.totalFiles + ') ' : '') + u.name,
|
|
1609
1993
|
),
|
|
1610
1994
|
!u.done && u.total ? create('span', { className: 'expl-upload-pct' }, pct + '%') : null,
|
|
1611
1995
|
)
|
|
@@ -1615,7 +1999,7 @@ window.__ModuleLoader__.load({
|
|
|
1615
1999
|
s.error ? create('div', { className: 'expl-error' }, s.error) : null,
|
|
1616
2000
|
s.notice ? create('div', { className: 'expl-notice' }, s.notice) : null,
|
|
1617
2001
|
s.selected.size > 1
|
|
1618
|
-
? create('div', { className: 'expl-bulk' }, '
|
|
2002
|
+
? create('div', { className: 'expl-bulk' }, T('explorer.bulkSelected', { n: s.selected.size }))
|
|
1619
2003
|
: null,
|
|
1620
2004
|
container,
|
|
1621
2005
|
s.menu ? create('div', { className: 'expl-menu-mask', onMouseDown: function () { setState({ menu: null }) }, onContextMenu: function (e) { e.preventDefault(); setState({ menu: null }) } }) : null,
|
|
@@ -1637,8 +2021,8 @@ window.__ModuleLoader__.load({
|
|
|
1637
2021
|
type: 'button',
|
|
1638
2022
|
className: 'expl-toggle expl-tool' + (s.open ? ' expl-active' : ''),
|
|
1639
2023
|
onClick: function () { try { if (s.open) layout.closeDetails(); else layout.openDetails() } catch (e) {} },
|
|
1640
|
-
title: '
|
|
1641
|
-
'aria-label': '
|
|
2024
|
+
title: T('explorer.label'),
|
|
2025
|
+
'aria-label': T('explorer.label'),
|
|
1642
2026
|
}, '📁')
|
|
1643
2027
|
}
|
|
1644
2028
|
|
|
@@ -1666,8 +2050,8 @@ window.__ModuleLoader__.load({
|
|
|
1666
2050
|
try { layout.openDetails() } catch (e) {}
|
|
1667
2051
|
}
|
|
1668
2052
|
},
|
|
1669
|
-
title: '
|
|
1670
|
-
'aria-label': '
|
|
2053
|
+
title: T('explorer.label'),
|
|
2054
|
+
'aria-label': T('explorer.label'),
|
|
1671
2055
|
}, '📁'))
|
|
1672
2056
|
}
|
|
1673
2057
|
|
|
@@ -1734,7 +2118,7 @@ window.__ModuleLoader__.load({
|
|
|
1734
2118
|
el.style.cssText = 'display:inline-flex;align-items:center;margin-left:10px;font-weight:400;font-size:13px;color:var(--dsw-alias-label-caption);'
|
|
1735
2119
|
el.style.setProperty('color', 'var(--dsw-alias-label-caption)', 'important')
|
|
1736
2120
|
el.style.setProperty('-webkit-text-fill-color', 'var(--dsw-alias-label-caption)', 'important')
|
|
1737
|
-
el.textContent = '
|
|
2121
|
+
el.textContent = T('speed.waiting') + new Array(dotsRef.current + 1).join('.')
|
|
1738
2122
|
return
|
|
1739
2123
|
}
|
|
1740
2124
|
// 流式阶段:token 计数立即显示;瞬时速率窗口未满(tps=0)时先不显示徽标,
|
|
@@ -1908,7 +2292,7 @@ window.__ModuleLoader__.load({
|
|
|
1908
2292
|
pendingRef.current = false
|
|
1909
2293
|
pending[1](false)
|
|
1910
2294
|
if (!result || !result.ok) {
|
|
1911
|
-
var err = result && result.error ? (result.error.message + ' (' + result.error.code + ')') : '
|
|
2295
|
+
var err = result && result.error ? (result.error.message + ' (' + result.error.code + ')') : T('speed.operateFail')
|
|
1912
2296
|
actionError[1](err)
|
|
1913
2297
|
}
|
|
1914
2298
|
return result
|
|
@@ -2026,9 +2410,20 @@ window.__ModuleLoader__.load({
|
|
|
2026
2410
|
var sessions = ctx.sessions
|
|
2027
2411
|
var remoteGoals = ctx.remote.goals
|
|
2028
2412
|
var disposers = []
|
|
2413
|
+
// i18n:注册双语词典;模块级 T 供组件外回调(菜单/错误横幅)取当前语言文案。
|
|
2414
|
+
if (ctx.locale && typeof ctx.locale.register === 'function') {
|
|
2415
|
+
ctx.effect(function () { return ctx.locale.register(NS, DICT) }, 'dsh-soup: dictionaries')
|
|
2416
|
+
if (typeof ctx.locale.bind === 'function') T = ctx.locale.bind(NS)
|
|
2417
|
+
}
|
|
2418
|
+
// 自动刷新心跳:1s 固定心跳 + 到期才探测(基础 3s;宿主不可达时指数退避,封顶 60s)。
|
|
2419
|
+
// 面板关且无预览时 tick 内直接返回(零请求);首个 tick 只建基线。
|
|
2420
|
+
if (timerRef && typeof timerRef.interval === 'function') {
|
|
2421
|
+
var stopAuto = timerRef.interval(function () { autoHeartbeat() }, AUTO_HEARTBEAT_MS)
|
|
2422
|
+
disposers.push(function () { try { stopAuto() } catch (e) {} })
|
|
2423
|
+
}
|
|
2029
2424
|
slots.inject('conversation.session.header.utilities', function () {
|
|
2030
2425
|
disposers.push(slots.register(
|
|
2031
|
-
{ name: 'conversation.session.header.utilities', id: 'dsh-soup-toggle', order: 10, label: '
|
|
2426
|
+
{ name: 'conversation.session.header.utilities', id: 'dsh-soup-toggle', order: 10, locale: NS, label: function () { return T('explorer.label') } },
|
|
2032
2427
|
function (props) { return create(HeaderAction, props) },
|
|
2033
2428
|
))
|
|
2034
2429
|
})
|
|
@@ -2036,7 +2431,7 @@ window.__ModuleLoader__.load({
|
|
|
2036
2431
|
disposers.push(slots.register(
|
|
2037
2432
|
// `details` is a single slot occupied by the shell at priority 0.
|
|
2038
2433
|
// Lower priorities render first, so this intentionally shadows it.
|
|
2039
|
-
{ name: 'details', priority: -1 },
|
|
2434
|
+
{ name: 'details', priority: -1, locale: NS },
|
|
2040
2435
|
function (props) { return create(Panel, props) },
|
|
2041
2436
|
))
|
|
2042
2437
|
})
|
|
@@ -2044,7 +2439,7 @@ window.__ModuleLoader__.load({
|
|
|
2044
2439
|
// 槽位 entry 是静态的,多文件由 FilesView 内部子 tab 条管理。
|
|
2045
2440
|
slots.inject('conversation.view', function () {
|
|
2046
2441
|
disposers.push(slots.register(
|
|
2047
|
-
{ name: 'conversation.view', id: 'dsh-soup-files', order: 20, label:
|
|
2442
|
+
{ name: 'conversation.view', id: 'dsh-soup-files', order: 20, locale: NS, label: function () { return T('files.tab') } },
|
|
2048
2443
|
function (props) { return create(FilesView, props) },
|
|
2049
2444
|
))
|
|
2050
2445
|
})
|
|
@@ -2064,7 +2459,7 @@ window.__ModuleLoader__.load({
|
|
|
2064
2459
|
}
|
|
2065
2460
|
slots.inject('conversation.input.dock', function () {
|
|
2066
2461
|
disposers.push(slots.register(
|
|
2067
|
-
{ name: 'conversation.input.dock', id: 'dsh-soup-hero-toggle', order: -10 },
|
|
2462
|
+
{ name: 'conversation.input.dock', id: 'dsh-soup-hero-toggle', order: -10, locale: NS },
|
|
2068
2463
|
function (props) { return create(HeroAction, props) },
|
|
2069
2464
|
))
|
|
2070
2465
|
// GoalBar 多行版:同 id 'goal'、更低 priority (-1) -> 遮蔽默认单行实现。
|
|
@@ -2100,7 +2495,7 @@ window.__ModuleLoader__.load({
|
|
|
2100
2495
|
function (props) { return create(GoalDock, props) },
|
|
2101
2496
|
))
|
|
2102
2497
|
disposers.push(slots.register(
|
|
2103
|
-
{ name: 'conversation.input.dock', id: 'dsh-speed-badge', order: 30, label: '
|
|
2498
|
+
{ name: 'conversation.input.dock', id: 'dsh-speed-badge', order: 30, locale: NS, label: function () { return T('speed.label') } },
|
|
2104
2499
|
function (props) { return create(SpeedBadge, props) },
|
|
2105
2500
|
))
|
|
2106
2501
|
})
|