@lyhue1991/dsh-soup 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/client/file-address-bridge.js +68 -0
- package/lib/client/preview-renderers.js +16 -0
- package/lib/client.entry.js +1 -0
- package/lib/client.js +174 -76
- package/lib/client.main.js +37 -25
- package/lib/host/speed.js +18 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ DSH(DeepSeek Harness)功能很强:会话、工具、轨迹、Goal 一应
|
|
|
24
24
|
|
|
25
25
|
### 🧅 右侧文件树(主打 · 葱 · 提鲜点缀)
|
|
26
26
|
|
|
27
|
-
一个真正和左侧工作区并列的文件浏览器——不悬浮、不遮挡,配色与宽度都与左侧一致(默认
|
|
27
|
+
一个真正和左侧工作区并列的文件浏览器——不悬浮、不遮挡,配色与宽度都与左侧一致(默认 300px,拖左缘在 300–520px 间调整)。
|
|
28
28
|
|
|
29
29
|
- **跟随会话**:自动展示当前会话的工作目录,切会话/工作区时跟着走。
|
|
30
30
|
- **右键菜单**:预览、用系统打开、重命名、移到废纸篓、复制路径、下载;目录多出新建文件、新建文件夹、上传。
|
|
@@ -50,7 +50,7 @@ DSH(DeepSeek Harness)功能很强:会话、工具、轨迹、Goal 一应
|
|
|
50
50
|
- **Notebook(.ipynb)**:GitHub 风格静态渲染(代码高亮 + 图片/输出内联)。
|
|
51
51
|
- **图片**:png/jpg/gif/webp/svg 等内联展示(≤8MB)。
|
|
52
52
|
- **其余**:二进制给出提示,超大文本截断为纯文本。
|
|
53
|
-
- **文本编辑**:Markdown / Python / JavaScript / TypeScript / JSON / HTML / CSS / Shell 等常见文本文件可点「编辑」,支持 ⌘/Ctrl+S 立即保存、未保存标记与返回预览;Python / JSON 等纯代码文件在编辑态保留与预览一致的左侧行号与语法配色,Markdown 返回预览时展示当前草稿。
|
|
53
|
+
- **文本编辑**:Markdown / Python / JavaScript / TypeScript / JSON / HTML / CSS / Shell,以及 `.gitignore`、`Dockerfile`、`Makefile`、`LICENSE` 等常见文本文件可点「编辑」,支持 ⌘/Ctrl+S 立即保存、未保存标记与返回预览;Python / JSON 等纯代码文件在编辑态保留与预览一致的左侧行号与语法配色,Markdown 返回预览时展示当前草稿。
|
|
54
54
|
- **自动保存**:参考 VS Code `files.autoSaveDelay`,停止输入 5 秒后自动保存;退出编辑时也会保存。`⌘S / Ctrl+S` 保存成功后工具栏会短暂显示“已保存”。编辑态默认覆盖磁盘上的其他更改,工具栏不显示保存/取消按钮。
|
|
55
55
|
- **跟随会话**:切到别的项目时,范围外的预览自动关闭。
|
|
56
56
|
- **自动重读**:正在看的文件被外部(agent / 编辑器)修改时,预览内容几秒内自动更新;若你正在编辑,则保留草稿并提示冲突。
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { fileAddressMatches } from './file-address.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wrap the official Sidebar resource opener so file resources keep working
|
|
5
|
+
* when dsh-soup owns the right-column surface. The service is intentionally
|
|
6
|
+
* passed in by the caller: it can be provided after this plugin's apply()
|
|
7
|
+
* lifecycle has already started, and may be replaced on a host reconnect.
|
|
8
|
+
*/
|
|
9
|
+
export function bridgeFileAddressOpener(service, ctx) {
|
|
10
|
+
if (!service || typeof service.openResource !== 'function') return null
|
|
11
|
+
if (service.__dshSoupFileBridge) return null
|
|
12
|
+
|
|
13
|
+
var originalOpenResource = service.openResource
|
|
14
|
+
var originalOpenResourceIn = service.openResourceIn
|
|
15
|
+
var openFileAddressInSoup = ctx.openFileAddressInSoup
|
|
16
|
+
var getActiveSessionId = ctx.getActiveSessionId
|
|
17
|
+
|
|
18
|
+
function openInSoup(address, sessionIdHint) {
|
|
19
|
+
if (!fileAddressMatches(address, sessionIdHint, getActiveSessionId())) return false
|
|
20
|
+
try {
|
|
21
|
+
var task = openFileAddressInSoup(address, sessionIdHint)
|
|
22
|
+
// Sidebar actions are synchronous. Keep asynchronous preview failures
|
|
23
|
+
// from becoming unhandled rejections in the host click handler.
|
|
24
|
+
if (task && typeof task.catch === 'function') task.catch(function () {})
|
|
25
|
+
return true
|
|
26
|
+
} catch (err) {
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
var bridgedOpenResource = function (address, options) {
|
|
32
|
+
if (openInSoup(address, null)) return undefined
|
|
33
|
+
return originalOpenResource.call(this, address, options)
|
|
34
|
+
}
|
|
35
|
+
var bridgedOpenResourceIn = typeof originalOpenResourceIn === 'function'
|
|
36
|
+
? function (sessionId, address, options) {
|
|
37
|
+
if (openInSoup(address, sessionId)) return undefined
|
|
38
|
+
return originalOpenResourceIn.call(this, sessionId, address, options)
|
|
39
|
+
}
|
|
40
|
+
: null
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
service.openResource = bridgedOpenResource
|
|
44
|
+
if (bridgedOpenResourceIn) service.openResourceIn = bridgedOpenResourceIn
|
|
45
|
+
if (service.openResource !== bridgedOpenResource ||
|
|
46
|
+
(bridgedOpenResourceIn && service.openResourceIn !== bridgedOpenResourceIn)) return null
|
|
47
|
+
try { Object.defineProperty(service, '__dshSoupFileBridge', { value: true, configurable: true }) }
|
|
48
|
+
catch (err) { service.__dshSoupFileBridge = true }
|
|
49
|
+
} catch (err) {
|
|
50
|
+
try {
|
|
51
|
+
if (service.openResource === bridgedOpenResource) service.openResource = originalOpenResource
|
|
52
|
+
if (bridgedOpenResourceIn && service.openResourceIn === bridgedOpenResourceIn) {
|
|
53
|
+
service.openResourceIn = originalOpenResourceIn
|
|
54
|
+
}
|
|
55
|
+
} catch (restoreErr) {}
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return function () {
|
|
60
|
+
if (service.openResource === bridgedOpenResource) service.openResource = originalOpenResource
|
|
61
|
+
if (bridgedOpenResourceIn && service.openResourceIn === bridgedOpenResourceIn) {
|
|
62
|
+
service.openResourceIn = originalOpenResourceIn
|
|
63
|
+
}
|
|
64
|
+
try { delete service.__dshSoupFileBridge } catch (err) {}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof window !== 'undefined') window.__DSH_SOUP_FILE_ADDRESS_BRIDGE__ = { bridgeFileAddressOpener }
|
|
@@ -29,6 +29,21 @@ export function previewKind(path) {
|
|
|
29
29
|
return 'text'
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/** 常见无扩展名/点文件本身就是文本配置,应允许直接编辑。 */
|
|
33
|
+
var EDITABLE_FILE_NAMES = new Set([
|
|
34
|
+
'.gitignore', '.gitattributes', '.gitmodules', '.dockerignore',
|
|
35
|
+
'.editorconfig', '.npmrc', '.yarnrc', '.yarnrc.yml',
|
|
36
|
+
'dockerfile', 'makefile', 'justfile', 'taskfile', 'procfile',
|
|
37
|
+
'vagrantfile', 'gemfile', 'rakefile', 'brewfile',
|
|
38
|
+
'license', 'licence', 'notice', 'authors', 'contributors',
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
function baseNameOf(path) {
|
|
42
|
+
var value = String(path || '').replace(/[\\/]+$/, '')
|
|
43
|
+
var slash = Math.max(value.lastIndexOf('/'), value.lastIndexOf('\\'))
|
|
44
|
+
return value.slice(slash + 1).toLowerCase()
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
/** RFC 4180 风格的分隔符解析(引号感知,"" 转义),返回行数组。 */
|
|
33
48
|
export function parseDelimited(text, delim) {
|
|
34
49
|
var rows = []
|
|
@@ -72,6 +87,7 @@ export function isEditableTextFile(path) {
|
|
|
72
87
|
var kind = previewKind(path)
|
|
73
88
|
var ext = extOfName(path)
|
|
74
89
|
return kind === 'markdown' || kind === 'html' || kind === 'json'
|
|
90
|
+
|| EDITABLE_FILE_NAMES.has(baseNameOf(path))
|
|
75
91
|
|| Object.prototype.hasOwnProperty.call(CODE_LANG_BY_EXT, ext)
|
|
76
92
|
}
|
|
77
93
|
|
package/lib/client.entry.js
CHANGED
package/lib/client.js
CHANGED
|
@@ -30963,6 +30963,110 @@
|
|
|
30963
30963
|
window.__DSH_SOUP_HTML_PREVIEW__ = { createHtmlPreview };
|
|
30964
30964
|
}
|
|
30965
30965
|
|
|
30966
|
+
// lib/client/file-address.js
|
|
30967
|
+
var FILE_ADDRESS_PREFIX = "dsh-resource://file/";
|
|
30968
|
+
function decodeSegmentSafe(segment) {
|
|
30969
|
+
try {
|
|
30970
|
+
return decodeURIComponent(segment);
|
|
30971
|
+
} catch (e) {
|
|
30972
|
+
return null;
|
|
30973
|
+
}
|
|
30974
|
+
}
|
|
30975
|
+
function parseFileAddress(address) {
|
|
30976
|
+
if (typeof address !== "string" || address.indexOf(FILE_ADDRESS_PREFIX) !== 0) return null;
|
|
30977
|
+
var end = address.search(/[?#]/);
|
|
30978
|
+
var body = address.slice(FILE_ADDRESS_PREFIX.length, end === -1 ? void 0 : end);
|
|
30979
|
+
var parts = body.split("/");
|
|
30980
|
+
if (parts[0] === "session") {
|
|
30981
|
+
var sessionId = decodeSegmentSafe(parts[1] || "");
|
|
30982
|
+
if (!sessionId) return null;
|
|
30983
|
+
var segments = [];
|
|
30984
|
+
for (var i = 2; i < parts.length; i++) {
|
|
30985
|
+
var seg = decodeSegmentSafe(parts[i]);
|
|
30986
|
+
if (seg === null) return null;
|
|
30987
|
+
segments.push(seg);
|
|
30988
|
+
}
|
|
30989
|
+
if (!segments.length) return null;
|
|
30990
|
+
return { scope: "session", sessionId, path: segments.join("/") };
|
|
30991
|
+
}
|
|
30992
|
+
if (parts[0] === "absolute") {
|
|
30993
|
+
var absolute = [];
|
|
30994
|
+
for (var j = 1; j < parts.length; j++) {
|
|
30995
|
+
var abs = decodeSegmentSafe(parts[j]);
|
|
30996
|
+
if (abs === null) return null;
|
|
30997
|
+
absolute.push(abs);
|
|
30998
|
+
}
|
|
30999
|
+
if (!absolute.length) return null;
|
|
31000
|
+
return { scope: "absolute", path: "/" + absolute.join("/") };
|
|
31001
|
+
}
|
|
31002
|
+
return null;
|
|
31003
|
+
}
|
|
31004
|
+
function fileAddressMatches(address, sessionIdHint, activeSessionId) {
|
|
31005
|
+
var parsed = parseFileAddress(address);
|
|
31006
|
+
if (!parsed) return false;
|
|
31007
|
+
if (parsed.scope === "session" && (sessionIdHint || parsed.sessionId) !== activeSessionId) return false;
|
|
31008
|
+
return true;
|
|
31009
|
+
}
|
|
31010
|
+
|
|
31011
|
+
// lib/client/file-address-bridge.js
|
|
31012
|
+
function bridgeFileAddressOpener(service, ctx) {
|
|
31013
|
+
if (!service || typeof service.openResource !== "function") return null;
|
|
31014
|
+
if (service.__dshSoupFileBridge) return null;
|
|
31015
|
+
var originalOpenResource = service.openResource;
|
|
31016
|
+
var originalOpenResourceIn = service.openResourceIn;
|
|
31017
|
+
var openFileAddressInSoup = ctx.openFileAddressInSoup;
|
|
31018
|
+
var getActiveSessionId = ctx.getActiveSessionId;
|
|
31019
|
+
function openInSoup(address, sessionIdHint) {
|
|
31020
|
+
if (!fileAddressMatches(address, sessionIdHint, getActiveSessionId())) return false;
|
|
31021
|
+
try {
|
|
31022
|
+
var task = openFileAddressInSoup(address, sessionIdHint);
|
|
31023
|
+
if (task && typeof task.catch === "function") task.catch(function() {
|
|
31024
|
+
});
|
|
31025
|
+
return true;
|
|
31026
|
+
} catch (err) {
|
|
31027
|
+
return false;
|
|
31028
|
+
}
|
|
31029
|
+
}
|
|
31030
|
+
var bridgedOpenResource = function(address, options) {
|
|
31031
|
+
if (openInSoup(address, null)) return void 0;
|
|
31032
|
+
return originalOpenResource.call(this, address, options);
|
|
31033
|
+
};
|
|
31034
|
+
var bridgedOpenResourceIn = typeof originalOpenResourceIn === "function" ? function(sessionId, address, options) {
|
|
31035
|
+
if (openInSoup(address, sessionId)) return void 0;
|
|
31036
|
+
return originalOpenResourceIn.call(this, sessionId, address, options);
|
|
31037
|
+
} : null;
|
|
31038
|
+
try {
|
|
31039
|
+
service.openResource = bridgedOpenResource;
|
|
31040
|
+
if (bridgedOpenResourceIn) service.openResourceIn = bridgedOpenResourceIn;
|
|
31041
|
+
if (service.openResource !== bridgedOpenResource || bridgedOpenResourceIn && service.openResourceIn !== bridgedOpenResourceIn) return null;
|
|
31042
|
+
try {
|
|
31043
|
+
Object.defineProperty(service, "__dshSoupFileBridge", { value: true, configurable: true });
|
|
31044
|
+
} catch (err) {
|
|
31045
|
+
service.__dshSoupFileBridge = true;
|
|
31046
|
+
}
|
|
31047
|
+
} catch (err) {
|
|
31048
|
+
try {
|
|
31049
|
+
if (service.openResource === bridgedOpenResource) service.openResource = originalOpenResource;
|
|
31050
|
+
if (bridgedOpenResourceIn && service.openResourceIn === bridgedOpenResourceIn) {
|
|
31051
|
+
service.openResourceIn = originalOpenResourceIn;
|
|
31052
|
+
}
|
|
31053
|
+
} catch (restoreErr) {
|
|
31054
|
+
}
|
|
31055
|
+
return null;
|
|
31056
|
+
}
|
|
31057
|
+
return function() {
|
|
31058
|
+
if (service.openResource === bridgedOpenResource) service.openResource = originalOpenResource;
|
|
31059
|
+
if (bridgedOpenResourceIn && service.openResourceIn === bridgedOpenResourceIn) {
|
|
31060
|
+
service.openResourceIn = originalOpenResourceIn;
|
|
31061
|
+
}
|
|
31062
|
+
try {
|
|
31063
|
+
delete service.__dshSoupFileBridge;
|
|
31064
|
+
} catch (err) {
|
|
31065
|
+
}
|
|
31066
|
+
};
|
|
31067
|
+
}
|
|
31068
|
+
if (typeof window !== "undefined") window.__DSH_SOUP_FILE_ADDRESS_BRIDGE__ = { bridgeFileAddressOpener };
|
|
31069
|
+
|
|
30966
31070
|
// lib/client/styles.js
|
|
30967
31071
|
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>';
|
|
30968
31072
|
var ICON_NEW_FOLDER = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-2 7h-3v3h-2v-3h-3v-2h3V8h2v3h3v2z"/></svg>';
|
|
@@ -31488,51 +31592,6 @@
|
|
|
31488
31592
|
}
|
|
31489
31593
|
var setiTag;
|
|
31490
31594
|
|
|
31491
|
-
// lib/client/file-address.js
|
|
31492
|
-
var FILE_ADDRESS_PREFIX = "dsh-resource://file/";
|
|
31493
|
-
function decodeSegmentSafe(segment) {
|
|
31494
|
-
try {
|
|
31495
|
-
return decodeURIComponent(segment);
|
|
31496
|
-
} catch (e) {
|
|
31497
|
-
return null;
|
|
31498
|
-
}
|
|
31499
|
-
}
|
|
31500
|
-
function parseFileAddress(address) {
|
|
31501
|
-
if (typeof address !== "string" || address.indexOf(FILE_ADDRESS_PREFIX) !== 0) return null;
|
|
31502
|
-
var end = address.search(/[?#]/);
|
|
31503
|
-
var body = address.slice(FILE_ADDRESS_PREFIX.length, end === -1 ? void 0 : end);
|
|
31504
|
-
var parts = body.split("/");
|
|
31505
|
-
if (parts[0] === "session") {
|
|
31506
|
-
var sessionId = decodeSegmentSafe(parts[1] || "");
|
|
31507
|
-
if (!sessionId) return null;
|
|
31508
|
-
var segments = [];
|
|
31509
|
-
for (var i = 2; i < parts.length; i++) {
|
|
31510
|
-
var seg = decodeSegmentSafe(parts[i]);
|
|
31511
|
-
if (seg === null) return null;
|
|
31512
|
-
segments.push(seg);
|
|
31513
|
-
}
|
|
31514
|
-
if (!segments.length) return null;
|
|
31515
|
-
return { scope: "session", sessionId, path: segments.join("/") };
|
|
31516
|
-
}
|
|
31517
|
-
if (parts[0] === "absolute") {
|
|
31518
|
-
var absolute = [];
|
|
31519
|
-
for (var j = 1; j < parts.length; j++) {
|
|
31520
|
-
var abs = decodeSegmentSafe(parts[j]);
|
|
31521
|
-
if (abs === null) return null;
|
|
31522
|
-
absolute.push(abs);
|
|
31523
|
-
}
|
|
31524
|
-
if (!absolute.length) return null;
|
|
31525
|
-
return { scope: "absolute", path: "/" + absolute.join("/") };
|
|
31526
|
-
}
|
|
31527
|
-
return null;
|
|
31528
|
-
}
|
|
31529
|
-
function fileAddressMatches(address, sessionIdHint, activeSessionId) {
|
|
31530
|
-
var parsed = parseFileAddress(address);
|
|
31531
|
-
if (!parsed) return false;
|
|
31532
|
-
if (parsed.scope === "session" && (sessionIdHint || parsed.sessionId) !== activeSessionId) return false;
|
|
31533
|
-
return true;
|
|
31534
|
-
}
|
|
31535
|
-
|
|
31536
31595
|
// lib/client/preview-renderers.js
|
|
31537
31596
|
function extOfName2(p) {
|
|
31538
31597
|
var m = /\.([A-Za-z0-9]+)$/.exec(String(p || ""));
|
|
@@ -31548,6 +31607,35 @@
|
|
|
31548
31607
|
if (ext === "tsv") return "tsv";
|
|
31549
31608
|
return "text";
|
|
31550
31609
|
}
|
|
31610
|
+
var EDITABLE_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
31611
|
+
".gitignore",
|
|
31612
|
+
".gitattributes",
|
|
31613
|
+
".gitmodules",
|
|
31614
|
+
".dockerignore",
|
|
31615
|
+
".editorconfig",
|
|
31616
|
+
".npmrc",
|
|
31617
|
+
".yarnrc",
|
|
31618
|
+
".yarnrc.yml",
|
|
31619
|
+
"dockerfile",
|
|
31620
|
+
"makefile",
|
|
31621
|
+
"justfile",
|
|
31622
|
+
"taskfile",
|
|
31623
|
+
"procfile",
|
|
31624
|
+
"vagrantfile",
|
|
31625
|
+
"gemfile",
|
|
31626
|
+
"rakefile",
|
|
31627
|
+
"brewfile",
|
|
31628
|
+
"license",
|
|
31629
|
+
"licence",
|
|
31630
|
+
"notice",
|
|
31631
|
+
"authors",
|
|
31632
|
+
"contributors"
|
|
31633
|
+
]);
|
|
31634
|
+
function baseNameOf(path) {
|
|
31635
|
+
var value = String(path || "").replace(/[\\/]+$/, "");
|
|
31636
|
+
var slash3 = Math.max(value.lastIndexOf("/"), value.lastIndexOf("\\"));
|
|
31637
|
+
return value.slice(slash3 + 1).toLowerCase();
|
|
31638
|
+
}
|
|
31551
31639
|
function parseDelimited(text, delim) {
|
|
31552
31640
|
var rows = [];
|
|
31553
31641
|
var row = [];
|
|
@@ -31597,7 +31685,7 @@
|
|
|
31597
31685
|
function isEditableTextFile(path) {
|
|
31598
31686
|
var kind = previewKind(path);
|
|
31599
31687
|
var ext = extOfName2(path);
|
|
31600
|
-
return kind === "markdown" || kind === "html" || kind === "json" || Object.prototype.hasOwnProperty.call(CODE_LANG_BY_EXT, ext);
|
|
31688
|
+
return kind === "markdown" || kind === "html" || kind === "json" || EDITABLE_FILE_NAMES.has(baseNameOf(path)) || Object.prototype.hasOwnProperty.call(CODE_LANG_BY_EXT, ext);
|
|
31601
31689
|
}
|
|
31602
31690
|
function createPreviewRenderers(ctx) {
|
|
31603
31691
|
var React = ctx.React;
|
|
@@ -34980,7 +35068,8 @@
|
|
|
34980
35068
|
document.removeEventListener("click", onOfficialExpand, true);
|
|
34981
35069
|
});
|
|
34982
35070
|
}
|
|
34983
|
-
var
|
|
35071
|
+
var bridgeDisposers = [];
|
|
35072
|
+
var bridgedServices = typeof WeakSet === "function" ? /* @__PURE__ */ new WeakSet() : [];
|
|
34984
35073
|
function resolveSidebarRight() {
|
|
34985
35074
|
try {
|
|
34986
35075
|
if (ctx.sidebarRight) return ctx.sidebarRight;
|
|
@@ -35002,42 +35091,43 @@
|
|
|
35002
35091
|
}
|
|
35003
35092
|
return null;
|
|
35004
35093
|
}
|
|
35005
|
-
function
|
|
35006
|
-
|
|
35007
|
-
|
|
35008
|
-
|
|
35009
|
-
if (
|
|
35010
|
-
|
|
35011
|
-
|
|
35012
|
-
|
|
35013
|
-
|
|
35014
|
-
|
|
35015
|
-
|
|
35016
|
-
|
|
35017
|
-
|
|
35018
|
-
};
|
|
35019
|
-
if (typeof origResourceIn === "function") {
|
|
35020
|
-
service.openResourceIn = function(sessionId, address, options) {
|
|
35021
|
-
if (fileAddressMatches(address, sessionId, activeSessionId)) {
|
|
35022
|
-
openFileAddressInSoup(address, sessionId);
|
|
35023
|
-
return;
|
|
35024
|
-
}
|
|
35025
|
-
return origResourceIn.call(service, sessionId, address, options);
|
|
35026
|
-
};
|
|
35027
|
-
}
|
|
35028
|
-
try {
|
|
35029
|
-
Object.defineProperty(service, "__dshSoupFileBridge", { value: true });
|
|
35030
|
-
} catch (e) {
|
|
35031
|
-
service.__dshSoupFileBridge = true;
|
|
35032
|
-
}
|
|
35094
|
+
function hasBridgedService(service) {
|
|
35095
|
+
return bridgedServices instanceof WeakSet ? bridgedServices.has(service) : bridgedServices.indexOf(service) !== -1;
|
|
35096
|
+
}
|
|
35097
|
+
function rememberBridgedService(service) {
|
|
35098
|
+
if (bridgedServices instanceof WeakSet) bridgedServices.add(service);
|
|
35099
|
+
else bridgedServices.push(service);
|
|
35100
|
+
}
|
|
35101
|
+
function patchSidebarRightBridge(service) {
|
|
35102
|
+
service = service || resolveSidebarRight();
|
|
35103
|
+
if (!service || hasBridgedService(service)) return !!service;
|
|
35104
|
+
if (service.__dshSoupFileBridge) {
|
|
35105
|
+
rememberBridgedService(service);
|
|
35106
|
+
return true;
|
|
35033
35107
|
}
|
|
35034
|
-
|
|
35108
|
+
var disposeBridge = bridgeFileAddressOpener(service, {
|
|
35109
|
+
openFileAddressInSoup,
|
|
35110
|
+
getActiveSessionId: function() {
|
|
35111
|
+
return activeSessionId;
|
|
35112
|
+
}
|
|
35113
|
+
});
|
|
35114
|
+
if (!disposeBridge) return false;
|
|
35115
|
+
rememberBridgedService(service);
|
|
35116
|
+
bridgeDisposers.push(disposeBridge);
|
|
35035
35117
|
try {
|
|
35036
35118
|
if (typeof window !== "undefined") window.__DSH_SOUP_FILE_BRIDGE = true;
|
|
35037
35119
|
} catch (e) {
|
|
35038
35120
|
}
|
|
35039
35121
|
return true;
|
|
35040
35122
|
}
|
|
35123
|
+
if (typeof ctx.inject === "function") {
|
|
35124
|
+
try {
|
|
35125
|
+
ctx.inject(["sidebarRight"], function(scope) {
|
|
35126
|
+
patchSidebarRightBridge(scope && scope.sidebarRight);
|
|
35127
|
+
});
|
|
35128
|
+
} catch (e) {
|
|
35129
|
+
}
|
|
35130
|
+
}
|
|
35041
35131
|
patchSidebarRightBridge();
|
|
35042
35132
|
var bridgeTries = 0;
|
|
35043
35133
|
var bridgeTimer = setInterval(function() {
|
|
@@ -35047,6 +35137,14 @@
|
|
|
35047
35137
|
disposers.push(function() {
|
|
35048
35138
|
clearInterval(bridgeTimer);
|
|
35049
35139
|
});
|
|
35140
|
+
disposers.push(function() {
|
|
35141
|
+
bridgeDisposers.forEach(function(disposeBridge) {
|
|
35142
|
+
try {
|
|
35143
|
+
disposeBridge();
|
|
35144
|
+
} catch (e) {
|
|
35145
|
+
}
|
|
35146
|
+
});
|
|
35147
|
+
});
|
|
35050
35148
|
if (ctx.locale && typeof ctx.locale.register === "function") {
|
|
35051
35149
|
ctx.effect(function() {
|
|
35052
35150
|
return ctx.locale.register(NS, DICT);
|
package/lib/client.main.js
CHANGED
|
@@ -3,7 +3,8 @@ import { NS, DICT } from './client/i18n.js'
|
|
|
3
3
|
import { iconSvgFor } from './client/file-icons.js'
|
|
4
4
|
import { createRpc, hostBase } from './client/rpc.js'
|
|
5
5
|
import { createHtmlPreview } from './client/html-preview.js'
|
|
6
|
-
import { parseFileAddress
|
|
6
|
+
import { parseFileAddress } from './client/file-address.js'
|
|
7
|
+
import { bridgeFileAddressOpener } from './client/file-address-bridge.js'
|
|
7
8
|
import { createPreviewRenderers, isEditableTextFile } from './client/preview-renderers.js'
|
|
8
9
|
import { createSpeedBadge } from './client/speed-badge.js'
|
|
9
10
|
import { createGoalBar } from './client/goal-bar.js'
|
|
@@ -385,10 +386,11 @@ window.__ModuleLoader__.load({
|
|
|
385
386
|
document.addEventListener('click', onOfficialExpand, true)
|
|
386
387
|
disposers.push(function () { document.removeEventListener('click', onOfficialExpand, true) })
|
|
387
388
|
}
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
var
|
|
389
|
+
// 官方产物链接桥接:产物卡片并不把 file 地址放进 DOM,而是直接调
|
|
390
|
+
// sidebarRight.openResource()。rightbar 槽位被替换后仍需接住该调用。
|
|
391
|
+
// 首选 ctx.inject 订阅服务就绪/重建;反射轮询仅保留给旧版宿主兼容。
|
|
392
|
+
var bridgeDisposers = []
|
|
393
|
+
var bridgedServices = typeof WeakSet === 'function' ? new WeakSet() : []
|
|
392
394
|
function resolveSidebarRight() {
|
|
393
395
|
try { if (ctx.sidebarRight) return ctx.sidebarRight } catch (e) {}
|
|
394
396
|
try {
|
|
@@ -405,30 +407,37 @@ window.__ModuleLoader__.load({
|
|
|
405
407
|
} catch (e) {}
|
|
406
408
|
return null
|
|
407
409
|
}
|
|
408
|
-
function
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (fileAddressMatches(address, sessionId, activeSessionId)) { openFileAddressInSoup(address, sessionId); return }
|
|
422
|
-
return origResourceIn.call(service, sessionId, address, options)
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
try { Object.defineProperty(service, '__dshSoupFileBridge', { value: true }) }
|
|
426
|
-
catch (e) { service.__dshSoupFileBridge = true }
|
|
410
|
+
function hasBridgedService(service) {
|
|
411
|
+
return bridgedServices instanceof WeakSet ? bridgedServices.has(service) : bridgedServices.indexOf(service) !== -1
|
|
412
|
+
}
|
|
413
|
+
function rememberBridgedService(service) {
|
|
414
|
+
if (bridgedServices instanceof WeakSet) bridgedServices.add(service)
|
|
415
|
+
else bridgedServices.push(service)
|
|
416
|
+
}
|
|
417
|
+
function patchSidebarRightBridge(service) {
|
|
418
|
+
service = service || resolveSidebarRight()
|
|
419
|
+
if (!service || hasBridgedService(service)) return !!service
|
|
420
|
+
if (service.__dshSoupFileBridge) {
|
|
421
|
+
rememberBridgedService(service)
|
|
422
|
+
return true
|
|
427
423
|
}
|
|
428
|
-
|
|
424
|
+
var disposeBridge = bridgeFileAddressOpener(service, {
|
|
425
|
+
openFileAddressInSoup: openFileAddressInSoup,
|
|
426
|
+
getActiveSessionId: function () { return activeSessionId },
|
|
427
|
+
})
|
|
428
|
+
if (!disposeBridge) return false
|
|
429
|
+
rememberBridgedService(service)
|
|
430
|
+
bridgeDisposers.push(disposeBridge)
|
|
429
431
|
try { if (typeof window !== 'undefined') window.__DSH_SOUP_FILE_BRIDGE = true } catch (e) {}
|
|
430
432
|
return true
|
|
431
433
|
}
|
|
434
|
+
if (typeof ctx.inject === 'function') {
|
|
435
|
+
try {
|
|
436
|
+
ctx.inject(['sidebarRight'], function (scope) {
|
|
437
|
+
patchSidebarRightBridge(scope && scope.sidebarRight)
|
|
438
|
+
})
|
|
439
|
+
} catch (e) {}
|
|
440
|
+
}
|
|
432
441
|
patchSidebarRightBridge()
|
|
433
442
|
var bridgeTries = 0
|
|
434
443
|
var bridgeTimer = setInterval(function () {
|
|
@@ -436,6 +445,9 @@ window.__ModuleLoader__.load({
|
|
|
436
445
|
if (patchSidebarRightBridge() || bridgeTries >= 10) clearInterval(bridgeTimer)
|
|
437
446
|
}, 1000)
|
|
438
447
|
disposers.push(function () { clearInterval(bridgeTimer) })
|
|
448
|
+
disposers.push(function () {
|
|
449
|
+
bridgeDisposers.forEach(function (disposeBridge) { try { disposeBridge() } catch (e) {} })
|
|
450
|
+
})
|
|
439
451
|
// i18n:注册双语词典;模块级 T 供组件外回调(菜单/错误横幅)取当前语言文案。
|
|
440
452
|
if (ctx.locale && typeof ctx.locale.register === 'function') {
|
|
441
453
|
ctx.effect(function () { return ctx.locale.register(NS, DICT) }, 'dsh-soup: dictionaries')
|
package/lib/host/speed.js
CHANGED
|
@@ -47,10 +47,25 @@ function extractChunk(chunk) {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
const appendToolCalls = (calls) => {
|
|
51
|
+
if (!Array.isArray(calls)) return
|
|
52
|
+
for (const call of calls) {
|
|
53
|
+
// OpenAI-style delta: { function: { arguments: '...' } }. Some adapters
|
|
54
|
+
// use `arguments` directly on the call object.
|
|
55
|
+
appendText(call?.function?.arguments)
|
|
56
|
+
appendText(call?.arguments)
|
|
57
|
+
appendText(call?.argumentsDelta)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
50
60
|
try {
|
|
51
61
|
if (!chunk) return { text, realTokens }
|
|
52
62
|
appendText(chunk.text)
|
|
53
63
|
appendText(chunk.content)
|
|
64
|
+
// Tool-call argument deltas are generated output too. DSH's abstract
|
|
65
|
+
// StreamChunk carries them as `argumentsDelta`; when the model writes a
|
|
66
|
+
// file the whole stream is tool-call deltas, so without this the content
|
|
67
|
+
// clock never advances and the badge degrades to the waiting text.
|
|
68
|
+
appendText(chunk.argumentsDelta)
|
|
54
69
|
// Reasoning streams use different names across providers/adapters. They
|
|
55
70
|
// are still generated output and must keep the throughput window alive.
|
|
56
71
|
appendText(chunk.reasoning_content)
|
|
@@ -64,8 +79,10 @@ function extractChunk(chunk) {
|
|
|
64
79
|
appendText(chunk.delta.reasoningContent)
|
|
65
80
|
appendText(chunk.delta.reasoning)
|
|
66
81
|
appendText(chunk.delta.thinking)
|
|
82
|
+
appendToolCalls(chunk.delta.tool_calls)
|
|
67
83
|
realTokens = outputTokens(chunk.delta.usage) ?? realTokens
|
|
68
84
|
}
|
|
85
|
+
appendToolCalls(chunk.tool_calls)
|
|
69
86
|
realTokens = outputTokens(chunk.usage) ?? realTokens
|
|
70
87
|
if (Array.isArray(chunk.choices)) {
|
|
71
88
|
for (const choice of chunk.choices) {
|
|
@@ -75,6 +92,7 @@ function extractChunk(chunk) {
|
|
|
75
92
|
appendText(choice?.delta?.reasoningContent)
|
|
76
93
|
appendText(choice?.delta?.reasoning)
|
|
77
94
|
appendText(choice?.delta?.thinking)
|
|
95
|
+
appendToolCalls(choice?.delta?.tool_calls)
|
|
78
96
|
realTokens = outputTokens(choice?.delta?.usage) ?? realTokens
|
|
79
97
|
}
|
|
80
98
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lyhue1991/dsh-soup",
|
|
3
3
|
"description": "dsh 体验增强四件套 = Jupyter风格SideBar + 中央文件预览Tab + 多行GoalBar + 实时Token流速徽标",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "esbuild lib/client.entry.js --bundle --format=iife --platform=browser --target=es2020 --outfile=lib/client.js",
|
|
9
9
|
"prepublishOnly": "npm run build",
|
|
10
|
-
"test": "npm run build && node --test test/client-goal.spec.mjs test/html-preview.spec.mjs test/host.spec.mjs"
|
|
10
|
+
"test": "npm run build && node --test test/client-goal.spec.mjs test/file-address-bridge.spec.mjs test/html-preview.spec.mjs test/host.spec.mjs"
|
|
11
11
|
},
|
|
12
12
|
"exports": {
|
|
13
13
|
".": "./lib/index.js",
|