dsh-multi-chat 0.6.8 → 1.0.1
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 +88 -30
- package/README.zh.md +54 -30
- package/bin/dsh-multi-chat.mjs +5 -9
- package/lib/client.js +778 -0
- package/lib/client.js.map +1 -0
- package/{plugin/dsh-client-ui-multi-wall/lib → lib}/index.js +58 -70
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/lib/types/client/WallToggle.d.ts.map +1 -0
- package/lib/types/client/WallToggle.js +36 -0
- package/lib/types/client/WallToggle.js.map +1 -0
- package/lib/types/client/WallView.d.ts.map +1 -0
- package/lib/types/client/WallView.js +218 -0
- package/lib/types/client/WallView.js.map +1 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/index.js +59 -0
- package/lib/types/client/index.js.map +1 -0
- package/lib/types/client/locales.d.ts.map +1 -0
- package/lib/types/client/locales.js +96 -0
- package/lib/types/client/locales.js.map +1 -0
- package/lib/types/client/store.d.ts.map +1 -0
- package/lib/types/client/store.js +27 -0
- package/lib/types/client/store.js.map +1 -0
- package/lib/types/client/wall-injected.d.ts.map +1 -0
- package/lib/types/client/wall-injected.js +76 -0
- package/lib/types/client/wall-injected.js.map +1 -0
- package/lib/types/gateway.d.ts +57 -0
- package/lib/types/gateway.d.ts.map +1 -0
- package/lib/types/gateway.js +566 -0
- package/lib/types/gateway.js.map +1 -0
- package/{plugin/dsh-client-ui-multi-wall/lib → lib}/types/index.d.ts +80 -80
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +550 -0
- package/lib/types/index.js.map +1 -0
- package/{plugin/dsh-client-ui-multi-wall/lib → lib}/types/invariant.d.ts +3 -4
- package/lib/types/invariant.d.ts.map +1 -0
- package/lib/types/invariant.js +25 -0
- package/lib/types/invariant.js.map +1 -0
- package/package.json +49 -17
- package/patches/multi-wall.yml +1 -1
- package/scripts/install-plugin.ps1 +6 -9
- package/scripts/start-multi.ps1 +1 -1
- package/scripts/sync-vitest-paths.mjs +63 -0
- package/src/client/WallToggle.module.css +26 -0
- package/src/client/WallToggle.tsx +52 -0
- package/src/client/WallView.module.css +190 -0
- package/src/client/WallView.tsx +406 -0
- package/src/client/index.ts +103 -0
- package/src/client/locales.ts +100 -0
- package/src/client/store.ts +40 -0
- package/src/client/wall-injected.ts +123 -0
- package/src/css-modules.d.ts +5 -0
- package/src/gateway.ts +627 -0
- package/src/index.ts +618 -0
- package/src/invariant.ts +33 -0
- package/plugin/dsh-client-ui-multi-wall/README.md +0 -26
- package/plugin/dsh-client-ui-multi-wall/README.zh.md +0 -26
- package/plugin/dsh-client-ui-multi-wall/cordis.patch.yml +0 -7
- package/plugin/dsh-client-ui-multi-wall/lib/client.js +0 -781
- package/plugin/dsh-client-ui-multi-wall/lib/invariant.js +0 -27
- package/plugin/dsh-client-ui-multi-wall/package.json +0 -88
- /package/{plugin/dsh-client-ui-multi-wall/lib → lib}/types/client/WallToggle.d.ts +0 -0
- /package/{plugin/dsh-client-ui-multi-wall/lib → lib}/types/client/WallView.d.ts +0 -0
- /package/{plugin/dsh-client-ui-multi-wall/lib → lib}/types/client/index.d.ts +0 -0
- /package/{plugin/dsh-client-ui-multi-wall/lib → lib}/types/client/locales.d.ts +0 -0
- /package/{plugin/dsh-client-ui-multi-wall/lib → lib}/types/client/store.d.ts +0 -0
- /package/{plugin/dsh-client-ui-multi-wall/lib → lib}/types/client/wall-injected.d.ts +0 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,778 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "dsh-multi-chat", factory: (require) => {
|
|
2
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
3
|
+
//#region rolldown:runtime
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
21
|
+
value: mod,
|
|
22
|
+
enumerable: true
|
|
23
|
+
}) : target, mod));
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
const __deepseek_ai_dsh_client_runtime_client = __toESM(require("@deepseek-ai/dsh-client-runtime/client"));
|
|
27
|
+
const react = __toESM(require("react"));
|
|
28
|
+
const __deepseek_ai_dsh_client_ui_primitives = __toESM(require("@deepseek-ai/dsh-client-ui-primitives"));
|
|
29
|
+
const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
|
|
30
|
+
|
|
31
|
+
//#region src/client/locales.ts
|
|
32
|
+
/** `multiWall` namespace dictionaries. */
|
|
33
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
34
|
+
const zh = {
|
|
35
|
+
"toggle": "多窗口",
|
|
36
|
+
"toggle.aria": "打开或关闭多窗口",
|
|
37
|
+
"view.multiWall": "多窗口",
|
|
38
|
+
"overlay.title": "多窗口",
|
|
39
|
+
"overlay.close": "关闭",
|
|
40
|
+
"scan": "发现实例",
|
|
41
|
+
"scan.from": "起始端口",
|
|
42
|
+
"scan.to": "结束端口",
|
|
43
|
+
"create": "新建窗口",
|
|
44
|
+
"create.pending": "启动中…",
|
|
45
|
+
"create.done": "已创建窗口 :{port}",
|
|
46
|
+
"create.failed": "创建失败:{error}",
|
|
47
|
+
"create.unknown": "未知原因",
|
|
48
|
+
"exit": "退出",
|
|
49
|
+
"exit.aria": "退出多窗口,返回对话",
|
|
50
|
+
"link": "手机访问",
|
|
51
|
+
"link.aria": "获取手机访问链接",
|
|
52
|
+
"link.fetching": "正在获取链接…",
|
|
53
|
+
"link.none": "未检测到局域网地址",
|
|
54
|
+
"link.copy": "复制链接",
|
|
55
|
+
"link.copied": "已复制",
|
|
56
|
+
"link.hint": "提示:{hint}",
|
|
57
|
+
"link.reachable": "手机在同一网络时可用:{urls}",
|
|
58
|
+
"link.unreachable": "当前实例仅绑定本机,手机无法访问。{hint}",
|
|
59
|
+
"link.token": "口令:{token}",
|
|
60
|
+
"columns": "列数",
|
|
61
|
+
"columns.auto": "自动",
|
|
62
|
+
"refresh": "全部刷新",
|
|
63
|
+
"openTab": "新标签页打开",
|
|
64
|
+
"reload": "重新加载",
|
|
65
|
+
"remove": "从视图移除",
|
|
66
|
+
"stop": "关闭实例",
|
|
67
|
+
"stop.confirm": "确定关闭?",
|
|
68
|
+
"stop.self": "即将关闭当前实例(本页面将断开)",
|
|
69
|
+
"stop.done": "已关闭 :{port}",
|
|
70
|
+
"stop.failed": "关闭 :{port} 失败:{error}",
|
|
71
|
+
"zoom": "放大",
|
|
72
|
+
"loading": "加载中",
|
|
73
|
+
"empty": "没有检测到 DSH 实例",
|
|
74
|
+
"empty.hint": "先启动若干 dsh web --port <n> 实例,再点击「发现实例」或手动添加端口。",
|
|
75
|
+
"status.scanning": "扫描 {from}–{to} …",
|
|
76
|
+
"status.found": "发现 {count} 个实例:{ports}",
|
|
77
|
+
"status.none": "区间 {from}–{to} 未发现 DSH 实例",
|
|
78
|
+
"status.refreshed": "已刷新全部窗口"
|
|
79
|
+
};
|
|
80
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
81
|
+
const en = {
|
|
82
|
+
"toggle": "Multi-Window",
|
|
83
|
+
"toggle.aria": "Toggle multi-window",
|
|
84
|
+
"view.multiWall": "Multi-Window",
|
|
85
|
+
"overlay.title": "Multi-Window",
|
|
86
|
+
"overlay.close": "Close",
|
|
87
|
+
"scan": "Discover",
|
|
88
|
+
"scan.from": "Start port",
|
|
89
|
+
"scan.to": "End port",
|
|
90
|
+
"create": "New window",
|
|
91
|
+
"create.pending": "Starting…",
|
|
92
|
+
"create.done": "Created window :{port}",
|
|
93
|
+
"create.failed": "Create failed: {error}",
|
|
94
|
+
"create.unknown": "unknown reason",
|
|
95
|
+
"exit": "Exit",
|
|
96
|
+
"exit.aria": "Exit multi-window and return to chat",
|
|
97
|
+
"link": "Phone access",
|
|
98
|
+
"link.aria": "Get phone access link",
|
|
99
|
+
"link.fetching": "Fetching link…",
|
|
100
|
+
"link.none": "No LAN address detected",
|
|
101
|
+
"link.copy": "Copy link",
|
|
102
|
+
"link.copied": "Copied",
|
|
103
|
+
"link.hint": "Hint: {hint}",
|
|
104
|
+
"link.reachable": "Reachable on the same network: {urls}",
|
|
105
|
+
"link.unreachable": "This instance binds loopback only; phones cannot reach it. {hint}",
|
|
106
|
+
"link.token": "Token: {token}",
|
|
107
|
+
"columns": "Columns",
|
|
108
|
+
"columns.auto": "Auto",
|
|
109
|
+
"refresh": "Refresh all",
|
|
110
|
+
"openTab": "Open in new tab",
|
|
111
|
+
"reload": "Reload",
|
|
112
|
+
"remove": "Remove from view",
|
|
113
|
+
"stop": "Stop instance",
|
|
114
|
+
"stop.confirm": "Confirm stop?",
|
|
115
|
+
"stop.self": "Stopping the current instance (this page will disconnect)",
|
|
116
|
+
"stop.done": "Stopped :{port}",
|
|
117
|
+
"stop.failed": "Failed to stop :{port}: {error}",
|
|
118
|
+
"zoom": "Zoom",
|
|
119
|
+
"loading": "Loading",
|
|
120
|
+
"empty": "No DSH instances found",
|
|
121
|
+
"empty.hint": "Start a few `dsh web --port <n>` instances first, then click \"Discover\" or add a port manually.",
|
|
122
|
+
"status.scanning": "Scanning {from}–{to} …",
|
|
123
|
+
"status.found": "Found {count} instance(s): {ports}",
|
|
124
|
+
"status.none": "No DSH instances in {from}–{to}",
|
|
125
|
+
"status.refreshed": "Refreshed all windows"
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/client/store.ts
|
|
130
|
+
/**
|
|
131
|
+
* Create the wall store handle. Persisted under `dsh.multi-wall` so the port
|
|
132
|
+
* set and column choice survive view switches and reloads.
|
|
133
|
+
* @returns the store handle (spec + type + identity + factory in one).
|
|
134
|
+
*/
|
|
135
|
+
function createWallStore() {
|
|
136
|
+
return (0, __deepseek_ai_dsh_client_runtime_client.defineStore)({
|
|
137
|
+
init: () => ({
|
|
138
|
+
ports: [],
|
|
139
|
+
columns: "auto"
|
|
140
|
+
}),
|
|
141
|
+
persist: "dsh.multi-wall",
|
|
142
|
+
actions: {
|
|
143
|
+
setPorts: (d, ports) => {
|
|
144
|
+
d.ports = ports;
|
|
145
|
+
},
|
|
146
|
+
addPort: (d, port) => {
|
|
147
|
+
if (!d.ports.includes(port)) d.ports = [...d.ports, port];
|
|
148
|
+
},
|
|
149
|
+
removePort: (d, port) => {
|
|
150
|
+
d.ports = d.ports.filter((p) => p !== port);
|
|
151
|
+
},
|
|
152
|
+
setColumns: (d, columns) => {
|
|
153
|
+
d.columns = columns;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/client/wall-injected.ts
|
|
161
|
+
/**
|
|
162
|
+
* Build the probe face bound to this origin's /multi/api routes.
|
|
163
|
+
* @param mount - the API base path ('' at the root, '/multi' when mounted).
|
|
164
|
+
* @returns the injected callbacks.
|
|
165
|
+
*/
|
|
166
|
+
function createWallInjected(mount = "") {
|
|
167
|
+
const base = mount.replace(/\/+$/, "");
|
|
168
|
+
return {
|
|
169
|
+
discover: async () => {
|
|
170
|
+
const res = await fetch(`${base}/multi/api/ports`);
|
|
171
|
+
if (!res.ok) return [];
|
|
172
|
+
const data = await res.json();
|
|
173
|
+
return (data.ports ?? []).filter((p) => p.alive).map((p) => p.port);
|
|
174
|
+
},
|
|
175
|
+
probe: async (ports) => {
|
|
176
|
+
const res = await fetch(`${base}/multi/api/status?ports=${ports.join(",")}`);
|
|
177
|
+
if (!res.ok) return [];
|
|
178
|
+
const data = await res.json();
|
|
179
|
+
return data.ports ?? [];
|
|
180
|
+
},
|
|
181
|
+
stop: async (port) => {
|
|
182
|
+
const res = await fetch(`${base}/multi/api/stop?port=${port}`, { method: "POST" });
|
|
183
|
+
if (!res.ok) return {
|
|
184
|
+
port,
|
|
185
|
+
ok: false,
|
|
186
|
+
error: `HTTP ${res.status}`
|
|
187
|
+
};
|
|
188
|
+
const data = await res.json();
|
|
189
|
+
return data.ports?.[0] ?? {
|
|
190
|
+
port,
|
|
191
|
+
ok: false,
|
|
192
|
+
error: "no result"
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
create: async () => {
|
|
196
|
+
const res = await fetch(`${base}/multi/api/create`, { method: "POST" });
|
|
197
|
+
if (!res.ok) return {
|
|
198
|
+
ok: false,
|
|
199
|
+
error: `HTTP ${res.status}`
|
|
200
|
+
};
|
|
201
|
+
const text = await res.text();
|
|
202
|
+
let data;
|
|
203
|
+
try {
|
|
204
|
+
data = JSON.parse(text);
|
|
205
|
+
} catch {
|
|
206
|
+
const snippet = text.trim().slice(0, 120);
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
error: `invalid response: ${snippet === "" ? "<empty body>" : snippet}`
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (data.ok === true) return data.port !== void 0 ? {
|
|
213
|
+
ok: true,
|
|
214
|
+
port: data.port
|
|
215
|
+
} : {
|
|
216
|
+
ok: false,
|
|
217
|
+
error: "no port in response"
|
|
218
|
+
};
|
|
219
|
+
return {
|
|
220
|
+
ok: false,
|
|
221
|
+
...typeof data.error === "string" ? { error: data.error } : { error: "server returned no reason" }
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
link: async () => {
|
|
225
|
+
const res = await fetch(`${base}/multi/api/link`);
|
|
226
|
+
const data = await res.json().catch(() => ({}));
|
|
227
|
+
return {
|
|
228
|
+
port: data.port ?? 0,
|
|
229
|
+
host: data.host ?? "unknown",
|
|
230
|
+
lan: data.lan ?? [],
|
|
231
|
+
reachable: data.reachable === true,
|
|
232
|
+
...typeof data.gatewayPort === "number" ? { gatewayPort: data.gatewayPort } : {},
|
|
233
|
+
...typeof data.token === "string" ? { token: data.token } : {},
|
|
234
|
+
...typeof data.hint === "string" ? { hint: data.hint } : {}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region node_modules/clsx/dist/clsx.mjs
|
|
242
|
+
function r(e) {
|
|
243
|
+
var t, f, n = "";
|
|
244
|
+
if ("string" == typeof e || "number" == typeof e) n += e;
|
|
245
|
+
else if ("object" == typeof e) if (Array.isArray(e)) {
|
|
246
|
+
var o = e.length;
|
|
247
|
+
for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
|
|
248
|
+
} else for (f in e) e[f] && (n && (n += " "), n += f);
|
|
249
|
+
return n;
|
|
250
|
+
}
|
|
251
|
+
function clsx() {
|
|
252
|
+
for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
|
|
253
|
+
return n;
|
|
254
|
+
}
|
|
255
|
+
var clsx_default = clsx;
|
|
256
|
+
|
|
257
|
+
//#endregion
|
|
258
|
+
//#region \0dsh-css:D:\2026AppDev\dsh-plugins-multi-task\src\client\WallView.module.css.mjs
|
|
259
|
+
const css$1 = "[data-conversation-scroll]:has([data-wall-view]) [data-composer-seat]{display:none}.sRUrTW_wall{background:var(--dsw-alias-bg-base);min-width:0;min-height:0;color:var(--dsw-alias-label-primary);flex-direction:column;flex:1;display:flex}.sRUrTW_toolbar{border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);flex-wrap:wrap;flex:none;align-items:center;gap:12px;padding:8px 14px;display:flex}.sRUrTW_title{font-size:14px;font-weight:600}.sRUrTW_linkBar{border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);flex-wrap:wrap;flex:none;align-items:center;gap:10px;padding:6px 14px;display:flex}.sRUrTW_linkText{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;flex:1;min-width:160px;font-size:12px}.sRUrTW_status{color:var(--dsw-alias-label-secondary);flex:1;min-width:120px;font-size:12px}.sRUrTW_controls{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.sRUrTW_field{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:12px;display:inline-flex}.sRUrTW_field>span{width:76px}.sRUrTW_grid{flex:1;grid-auto-rows:minmax(280px,1fr);align-content:start;gap:8px;padding:8px;display:grid;overflow:auto}.sRUrTW_grid[data-cols=auto]{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}.sRUrTW_grid[data-cols=\"1\"]{grid-template-columns:1fr}.sRUrTW_grid[data-cols=\"2\"]{grid-template-columns:repeat(2,1fr)}.sRUrTW_grid[data-cols=\"3\"]{grid-template-columns:repeat(3,1fr)}.sRUrTW_grid[data-cols=\"4\"]{grid-template-columns:repeat(4,1fr)}.sRUrTW_grid[data-cols=\"6\"]{grid-template-columns:repeat(6,1fr)}.sRUrTW_pane{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;flex-direction:column;min-width:0;min-height:0;display:flex;overflow:hidden}.sRUrTW_pane.sRUrTW_zoomed{grid-area:1/1/-1/-1}.sRUrTW_paneHead{border-bottom:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);flex:none;align-items:center;gap:8px;padding:4px 8px;display:flex}.sRUrTW_dot{flex:none}.sRUrTW_paneTitle{text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.sRUrTW_paneActions{flex:none;gap:2px;margin-left:auto;display:flex}.sRUrTW_action{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:4px;padding:2px 6px;line-height:1}.sRUrTW_action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.sRUrTW_action.sRUrTW_danger:hover{color:var(--dsw-static-amber-600)}.sRUrTW_action.sRUrTW_danger.sRUrTW_confirm{background:var(--dsw-static-amber-500);color:var(--dsw-static-neutral-00);padding:2px 8px;font-size:11px}.sRUrTW_action.sRUrTW_danger.sRUrTW_confirm:hover{background:var(--dsw-static-amber-600);color:var(--dsw-static-neutral-00)}.sRUrTW_paneBody{flex:1;min-height:0;position:relative}.sRUrTW_paneBody iframe{border:none;width:100%;height:100%;display:block}.sRUrTW_empty{color:var(--dsw-alias-label-secondary);flex-direction:column;grid-column:1/-1;justify-content:center;align-items:center;gap:6px;display:flex}.sRUrTW_empty .sRUrTW_hint{font-size:12px}";
|
|
260
|
+
const tagId$1 = "dsh-multi-chat/WallView.module.css";
|
|
261
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
262
|
+
const tag = document.createElement("style");
|
|
263
|
+
tag.dataset.plugin = "dsh-multi-chat";
|
|
264
|
+
tag.dataset.pluginCss = tagId$1;
|
|
265
|
+
tag.textContent = css$1;
|
|
266
|
+
document.head.appendChild(tag);
|
|
267
|
+
}
|
|
268
|
+
var WallView_module_css_default = {
|
|
269
|
+
"pane": "sRUrTW_pane",
|
|
270
|
+
"hint": "sRUrTW_hint",
|
|
271
|
+
"wall": "sRUrTW_wall",
|
|
272
|
+
"danger": "sRUrTW_danger",
|
|
273
|
+
"field": "sRUrTW_field",
|
|
274
|
+
"dot": "sRUrTW_dot",
|
|
275
|
+
"paneTitle": "sRUrTW_paneTitle",
|
|
276
|
+
"action": "sRUrTW_action",
|
|
277
|
+
"grid": "sRUrTW_grid",
|
|
278
|
+
"paneBody": "sRUrTW_paneBody",
|
|
279
|
+
"title": "sRUrTW_title",
|
|
280
|
+
"linkText": "sRUrTW_linkText",
|
|
281
|
+
"paneActions": "sRUrTW_paneActions",
|
|
282
|
+
"status": "sRUrTW_status",
|
|
283
|
+
"confirm": "sRUrTW_confirm",
|
|
284
|
+
"controls": "sRUrTW_controls",
|
|
285
|
+
"paneHead": "sRUrTW_paneHead",
|
|
286
|
+
"empty": "sRUrTW_empty",
|
|
287
|
+
"zoomed": "sRUrTW_zoomed",
|
|
288
|
+
"toolbar": "sRUrTW_toolbar",
|
|
289
|
+
"linkBar": "sRUrTW_linkBar"
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
//#endregion
|
|
293
|
+
//#region src/client/WallView.tsx
|
|
294
|
+
/** Grid column presets, driven by the toolbar menu. 'auto' fills the row. */
|
|
295
|
+
const COLUMN_PRESETS = [
|
|
296
|
+
"auto",
|
|
297
|
+
"1",
|
|
298
|
+
"2",
|
|
299
|
+
"3",
|
|
300
|
+
"4",
|
|
301
|
+
"6"
|
|
302
|
+
];
|
|
303
|
+
/** Embed flag appended to every pane URL; such pages register no wall UI. */
|
|
304
|
+
const EMBED_FLAG = "multi-wall=embed";
|
|
305
|
+
/**
|
|
306
|
+
* Whether this wall is being viewed through the phone gateway (a non-loopback
|
|
307
|
+
* host) rather than on the machine running DSH. When remote, pane iframes must
|
|
308
|
+
* load through the gateway's `/gw/<port>` route — a phone's `127.0.0.1` points
|
|
309
|
+
* at the phone itself, not the host.
|
|
310
|
+
*/
|
|
311
|
+
function isRemoteViewer() {
|
|
312
|
+
const host = typeof window !== "undefined" ? window.location.hostname : "";
|
|
313
|
+
return host !== "localhost" && host !== "127.0.0.1" && host !== "::1";
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* The iframe URL for a pane. Local viewers embed the loopback instance
|
|
317
|
+
* directly; remote (phone) viewers route through the gateway that is already
|
|
318
|
+
* serving this page, appending the port so the gateway proxies to it.
|
|
319
|
+
* @param port - target DSH instance port.
|
|
320
|
+
* @returns the pane URL.
|
|
321
|
+
*/
|
|
322
|
+
function paneUrl(port) {
|
|
323
|
+
if (isRemoteViewer()) return `${window.location.origin}/gw/${port}/?${EMBED_FLAG}`;
|
|
324
|
+
return `http://127.0.0.1:${port}/?${EMBED_FLAG}`;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* One pane: header (port, liveness dot, zoom/refresh/open/stop/remove) plus
|
|
328
|
+
* the embedded original DSH UI.
|
|
329
|
+
*/
|
|
330
|
+
function WallPane(props) {
|
|
331
|
+
const { port, alive, zoomed, stopping, onZoom, onStop, onRemove, t } = props;
|
|
332
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
333
|
+
className: clsx_default(WallView_module_css_default.pane, zoomed && WallView_module_css_default.zoomed),
|
|
334
|
+
"data-port": port,
|
|
335
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
336
|
+
className: WallView_module_css_default.paneHead,
|
|
337
|
+
children: [
|
|
338
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.StateDot, {
|
|
339
|
+
state: alive ? "done" : "warning",
|
|
340
|
+
size: 8,
|
|
341
|
+
className: WallView_module_css_default.dot
|
|
342
|
+
}),
|
|
343
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
344
|
+
className: WallView_module_css_default.paneTitle,
|
|
345
|
+
children: ["127.0.0.1:", port]
|
|
346
|
+
}),
|
|
347
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
348
|
+
className: WallView_module_css_default.paneActions,
|
|
349
|
+
children: [
|
|
350
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
351
|
+
type: "button",
|
|
352
|
+
className: WallView_module_css_default.action,
|
|
353
|
+
title: t("zoom"),
|
|
354
|
+
onClick: onZoom,
|
|
355
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { size: 14 })
|
|
356
|
+
}),
|
|
357
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
358
|
+
type: "button",
|
|
359
|
+
className: WallView_module_css_default.action,
|
|
360
|
+
title: t("reload"),
|
|
361
|
+
onClick: (e) => {
|
|
362
|
+
e.currentTarget.closest("section")?.querySelector("iframe")?.contentWindow?.location.reload();
|
|
363
|
+
},
|
|
364
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, { size: 14 })
|
|
365
|
+
}),
|
|
366
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
367
|
+
type: "button",
|
|
368
|
+
className: WallView_module_css_default.action,
|
|
369
|
+
title: t("openTab"),
|
|
370
|
+
onClick: () => {
|
|
371
|
+
window.open(paneUrl(port), "_blank");
|
|
372
|
+
},
|
|
373
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconRightUpOutline16, { size: 14 })
|
|
374
|
+
}),
|
|
375
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
376
|
+
type: "button",
|
|
377
|
+
className: clsx_default(WallView_module_css_default.action, WallView_module_css_default.danger, stopping && WallView_module_css_default.confirm),
|
|
378
|
+
title: t("stop"),
|
|
379
|
+
onClick: onStop,
|
|
380
|
+
children: stopping ? t("stop.confirm") : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconStopFill16, { size: 14 })
|
|
381
|
+
}),
|
|
382
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
383
|
+
type: "button",
|
|
384
|
+
className: WallView_module_css_default.action,
|
|
385
|
+
title: t("remove"),
|
|
386
|
+
onClick: onRemove,
|
|
387
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
|
|
388
|
+
})
|
|
389
|
+
]
|
|
390
|
+
})
|
|
391
|
+
]
|
|
392
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
393
|
+
className: WallView_module_css_default.paneBody,
|
|
394
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
|
|
395
|
+
title: `DSH :${port}`,
|
|
396
|
+
src: paneUrl(port),
|
|
397
|
+
loading: "lazy"
|
|
398
|
+
})
|
|
399
|
+
})]
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Render the wall: toolbar plus the horizontally-filled pane grid. Discovery
|
|
404
|
+
* runs on mount and liveness polls every 5s; the store's persisted ports
|
|
405
|
+
* survive view switches and reloads.
|
|
406
|
+
* @param props - composed slot props.
|
|
407
|
+
* @returns the wall surface.
|
|
408
|
+
*/
|
|
409
|
+
function WallView({ useStore, actions, discover, probe, stop, create, link, t }) {
|
|
410
|
+
const ports = useStore((s) => s.ports);
|
|
411
|
+
const columns = useStore((s) => s.columns);
|
|
412
|
+
const [alive, setAlive] = (0, react.useState)({});
|
|
413
|
+
const [zoomedPort, setZoomedPort] = (0, react.useState)(null);
|
|
414
|
+
const [confirmingStop, setConfirmingStop] = (0, react.useState)(null);
|
|
415
|
+
const [scanFrom, setScanFrom] = (0, react.useState)(3070);
|
|
416
|
+
const [scanTo, setScanTo] = (0, react.useState)(3110);
|
|
417
|
+
const [colsMenuOpen, setColsMenuOpen] = (0, react.useState)(false);
|
|
418
|
+
const [creating, setCreating] = (0, react.useState)(false);
|
|
419
|
+
const [linkOpen, setLinkOpen] = (0, react.useState)(false);
|
|
420
|
+
const [linkInfo, setLinkInfo] = (0, react.useState)(null);
|
|
421
|
+
const [linkCopied, setLinkCopied] = (0, react.useState)(false);
|
|
422
|
+
const [status, setStatus] = (0, react.useState)("");
|
|
423
|
+
const aliveRef = (0, react.useRef)({});
|
|
424
|
+
aliveRef.current = alive;
|
|
425
|
+
(0, react.useEffect)(() => {
|
|
426
|
+
discover().then((found) => {
|
|
427
|
+
if (found.length > 0) actions.setPorts(found);
|
|
428
|
+
setStatus(found.length > 0 ? t("status.found").replace("{count}", String(found.length)).replace("{ports}", found.join(", ")) : "");
|
|
429
|
+
});
|
|
430
|
+
const timer = setInterval(() => {
|
|
431
|
+
if (ports.length === 0) return;
|
|
432
|
+
probe(ports).then((rows) => {
|
|
433
|
+
const next = {};
|
|
434
|
+
for (const row of rows) next[row.port] = row.alive;
|
|
435
|
+
setAlive(next);
|
|
436
|
+
});
|
|
437
|
+
}, 5e3);
|
|
438
|
+
return () => {
|
|
439
|
+
clearInterval(timer);
|
|
440
|
+
};
|
|
441
|
+
}, []);
|
|
442
|
+
const handleStop = async (port) => {
|
|
443
|
+
if (confirmingStop !== port) {
|
|
444
|
+
setConfirmingStop(port);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
setConfirmingStop(null);
|
|
448
|
+
const result = await stop(port);
|
|
449
|
+
if (result.ok) {
|
|
450
|
+
actions.removePort(port);
|
|
451
|
+
setAlive((current) => ({
|
|
452
|
+
...current,
|
|
453
|
+
[port]: false
|
|
454
|
+
}));
|
|
455
|
+
setStatus(t("stop.done").replace("{port}", String(port)));
|
|
456
|
+
} else setStatus(t("stop.failed").replace("{port}", String(port)).replace("{error}", result.error ?? ""));
|
|
457
|
+
};
|
|
458
|
+
const runDiscovery = async () => {
|
|
459
|
+
setStatus(t("status.scanning").replace("{from}", String(scanFrom)).replace("{to}", String(scanTo)));
|
|
460
|
+
const found = await discover();
|
|
461
|
+
if (found.length === 0) {
|
|
462
|
+
setStatus(t("status.none").replace("{from}", String(scanFrom)).replace("{to}", String(scanTo)));
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
actions.setPorts(found);
|
|
466
|
+
setStatus(t("status.found").replace("{count}", String(found.length)).replace("{ports}", found.join(", ")));
|
|
467
|
+
};
|
|
468
|
+
const handleCreate = async () => {
|
|
469
|
+
setCreating(true);
|
|
470
|
+
setStatus("");
|
|
471
|
+
try {
|
|
472
|
+
const result = await create();
|
|
473
|
+
if (result.ok && result.port !== void 0) {
|
|
474
|
+
actions.addPort(result.port);
|
|
475
|
+
setAlive((current) => ({
|
|
476
|
+
...current,
|
|
477
|
+
[result.port]: true
|
|
478
|
+
}));
|
|
479
|
+
setStatus(t("create.done").replace("{port}", String(result.port)));
|
|
480
|
+
} else setStatus(t("create.failed").replace("{error}", result.error ?? t("create.unknown")));
|
|
481
|
+
} catch (error) {
|
|
482
|
+
setStatus(t("create.failed").replace("{error}", error instanceof Error ? error.message : String(error)));
|
|
483
|
+
} finally {
|
|
484
|
+
setCreating(false);
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
const exitWall = () => {
|
|
488
|
+
const tab = document.querySelector("[role=\"tablist\"] [role=\"tab\"]");
|
|
489
|
+
tab?.click();
|
|
490
|
+
};
|
|
491
|
+
const handleLink = async () => {
|
|
492
|
+
if (linkInfo === null) {
|
|
493
|
+
setStatus(t("link.fetching"));
|
|
494
|
+
const info = await link();
|
|
495
|
+
setLinkInfo(info);
|
|
496
|
+
}
|
|
497
|
+
setLinkOpen(true);
|
|
498
|
+
setLinkCopied(false);
|
|
499
|
+
setStatus("");
|
|
500
|
+
};
|
|
501
|
+
const copyFirstLink = async () => {
|
|
502
|
+
if (linkInfo === null || linkInfo.lan.length === 0) return;
|
|
503
|
+
try {
|
|
504
|
+
const base = linkInfo.lan[0] ?? "";
|
|
505
|
+
const token = linkInfo.token ?? "";
|
|
506
|
+
const url = token !== "" ? `${base}?token=${encodeURIComponent(token)}` : base;
|
|
507
|
+
await navigator.clipboard.writeText(url);
|
|
508
|
+
setLinkCopied(true);
|
|
509
|
+
} catch {
|
|
510
|
+
setLinkCopied(false);
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
const shown = ports;
|
|
514
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
515
|
+
className: WallView_module_css_default.wall,
|
|
516
|
+
role: "region",
|
|
517
|
+
"aria-label": t("overlay.title"),
|
|
518
|
+
"data-wall-view": "",
|
|
519
|
+
children: [
|
|
520
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
521
|
+
className: WallView_module_css_default.toolbar,
|
|
522
|
+
children: [
|
|
523
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
524
|
+
className: WallView_module_css_default.title,
|
|
525
|
+
children: t("overlay.title")
|
|
526
|
+
}),
|
|
527
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
528
|
+
className: WallView_module_css_default.status,
|
|
529
|
+
children: status
|
|
530
|
+
}),
|
|
531
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
532
|
+
className: WallView_module_css_default.controls,
|
|
533
|
+
children: [
|
|
534
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
535
|
+
className: WallView_module_css_default.field,
|
|
536
|
+
children: [t("scan.from"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
537
|
+
type: "number",
|
|
538
|
+
value: scanFrom,
|
|
539
|
+
onChange: (e) => setScanFrom(Number(e.target.value))
|
|
540
|
+
})]
|
|
541
|
+
}),
|
|
542
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
543
|
+
className: WallView_module_css_default.field,
|
|
544
|
+
children: [t("scan.to"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
545
|
+
type: "number",
|
|
546
|
+
value: scanTo,
|
|
547
|
+
onChange: (e) => setScanTo(Number(e.target.value))
|
|
548
|
+
})]
|
|
549
|
+
}),
|
|
550
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
551
|
+
variant: "toolbar",
|
|
552
|
+
size: "sm",
|
|
553
|
+
onClick: () => {
|
|
554
|
+
runDiscovery();
|
|
555
|
+
},
|
|
556
|
+
children: t("scan")
|
|
557
|
+
}),
|
|
558
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
559
|
+
variant: "toolbar",
|
|
560
|
+
size: "sm",
|
|
561
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
|
|
562
|
+
disabled: creating,
|
|
563
|
+
onClick: () => {
|
|
564
|
+
handleCreate();
|
|
565
|
+
},
|
|
566
|
+
children: creating ? t("create.pending") : t("create")
|
|
567
|
+
}),
|
|
568
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
569
|
+
open: colsMenuOpen,
|
|
570
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
571
|
+
variant: "toolbar",
|
|
572
|
+
size: "sm",
|
|
573
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconRightUpOutline16, { size: 14 }),
|
|
574
|
+
onClick: () => {
|
|
575
|
+
setColsMenuOpen(true);
|
|
576
|
+
},
|
|
577
|
+
children: columns === "auto" ? t("columns.auto") : columns
|
|
578
|
+
}),
|
|
579
|
+
items: COLUMN_PRESETS.map((c) => ({
|
|
580
|
+
id: c,
|
|
581
|
+
label: c === "auto" ? t("columns.auto") : c
|
|
582
|
+
})),
|
|
583
|
+
selectedId: columns,
|
|
584
|
+
onSelect: (id) => {
|
|
585
|
+
actions.setColumns(id);
|
|
586
|
+
setColsMenuOpen(false);
|
|
587
|
+
},
|
|
588
|
+
onClose: () => {
|
|
589
|
+
setColsMenuOpen(false);
|
|
590
|
+
},
|
|
591
|
+
compact: true
|
|
592
|
+
}),
|
|
593
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
594
|
+
variant: "toolbar",
|
|
595
|
+
size: "sm",
|
|
596
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline14, { size: 14 }),
|
|
597
|
+
onClick: () => {
|
|
598
|
+
document.querySelectorAll(`.${WallView_module_css_default.paneBody} iframe`).forEach((f) => {
|
|
599
|
+
f.contentWindow?.location.reload();
|
|
600
|
+
});
|
|
601
|
+
setStatus(t("status.refreshed"));
|
|
602
|
+
},
|
|
603
|
+
children: t("refresh")
|
|
604
|
+
}),
|
|
605
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
606
|
+
variant: "toolbar",
|
|
607
|
+
size: "sm",
|
|
608
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconGlobeOutline14, { size: 14 }),
|
|
609
|
+
"aria-label": t("link.aria"),
|
|
610
|
+
onClick: () => {
|
|
611
|
+
handleLink();
|
|
612
|
+
},
|
|
613
|
+
children: t("link")
|
|
614
|
+
}),
|
|
615
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
616
|
+
variant: "toolbar",
|
|
617
|
+
size: "sm",
|
|
618
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 }),
|
|
619
|
+
"aria-label": t("exit.aria"),
|
|
620
|
+
title: t("exit"),
|
|
621
|
+
onClick: () => {
|
|
622
|
+
exitWall();
|
|
623
|
+
},
|
|
624
|
+
children: t("exit")
|
|
625
|
+
})
|
|
626
|
+
]
|
|
627
|
+
})
|
|
628
|
+
]
|
|
629
|
+
}),
|
|
630
|
+
linkOpen && linkInfo !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
631
|
+
className: WallView_module_css_default.linkBar,
|
|
632
|
+
children: [linkInfo.reachable ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
633
|
+
className: WallView_module_css_default.linkText,
|
|
634
|
+
children: [t("link.reachable").replace("{urls}", linkInfo.lan.join(" ")), linkInfo.token !== void 0 && linkInfo.token !== "" ? ` ${t("link.token").replace("{token}", linkInfo.token)}` : ""]
|
|
635
|
+
}), linkInfo.lan.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
636
|
+
variant: "outline",
|
|
637
|
+
size: "sm",
|
|
638
|
+
onClick: () => {
|
|
639
|
+
copyFirstLink();
|
|
640
|
+
},
|
|
641
|
+
children: linkCopied ? t("link.copied") : t("link.copy")
|
|
642
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
643
|
+
className: WallView_module_css_default.linkText,
|
|
644
|
+
children: t("link.unreachable").replace("{hint}", linkInfo.hint ?? "")
|
|
645
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
646
|
+
variant: "ghost",
|
|
647
|
+
size: "sm",
|
|
648
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 }),
|
|
649
|
+
onClick: () => {
|
|
650
|
+
setLinkOpen(false);
|
|
651
|
+
},
|
|
652
|
+
children: t("overlay.close")
|
|
653
|
+
})]
|
|
654
|
+
}),
|
|
655
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
656
|
+
className: WallView_module_css_default.grid,
|
|
657
|
+
"data-cols": columns,
|
|
658
|
+
children: [shown.map((port) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WallPane, {
|
|
659
|
+
port,
|
|
660
|
+
alive: aliveRef.current[port] ?? true,
|
|
661
|
+
zoomed: zoomedPort === port,
|
|
662
|
+
stopping: confirmingStop === port,
|
|
663
|
+
onZoom: () => setZoomedPort(zoomedPort === port ? null : port),
|
|
664
|
+
onStop: () => {
|
|
665
|
+
handleStop(port);
|
|
666
|
+
},
|
|
667
|
+
onRemove: () => {
|
|
668
|
+
setConfirmingStop(null);
|
|
669
|
+
actions.removePort(port);
|
|
670
|
+
},
|
|
671
|
+
t
|
|
672
|
+
}, port)), shown.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
673
|
+
className: WallView_module_css_default.empty,
|
|
674
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("empty") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
675
|
+
className: WallView_module_css_default.hint,
|
|
676
|
+
children: t("empty.hint")
|
|
677
|
+
})]
|
|
678
|
+
})]
|
|
679
|
+
})
|
|
680
|
+
]
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
//#endregion
|
|
685
|
+
//#region \0dsh-css:D:\2026AppDev\dsh-plugins-multi-task\src\client\WallToggle.module.css.mjs
|
|
686
|
+
const css = ".i1n9BW_row{box-sizing:border-box;width:calc(100% + 8px);height:34px;color:var(--dsw-alias-label-secondary);cursor:pointer;font:inherit;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;margin:4px -4px;padding:6px 2px 6px 10px;display:flex;overflow:hidden}.i1n9BW_row:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.i1n9BW_label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}";
|
|
687
|
+
const tagId = "dsh-multi-chat/WallToggle.module.css";
|
|
688
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
689
|
+
const tag = document.createElement("style");
|
|
690
|
+
tag.dataset.plugin = "dsh-multi-chat";
|
|
691
|
+
tag.dataset.pluginCss = tagId;
|
|
692
|
+
tag.textContent = css;
|
|
693
|
+
document.head.appendChild(tag);
|
|
694
|
+
}
|
|
695
|
+
var WallToggle_module_css_default = {
|
|
696
|
+
"row": "i1n9BW_row",
|
|
697
|
+
"label": "i1n9BW_label"
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/client/WallToggle.tsx
|
|
702
|
+
/**
|
|
703
|
+
* Render the wall shortcut row (icon; label only in the wide column).
|
|
704
|
+
* @param props - composed slot props.
|
|
705
|
+
* @returns the shortcut row.
|
|
706
|
+
*/
|
|
707
|
+
function WallToggle({ wide, t }) {
|
|
708
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
709
|
+
type: "button",
|
|
710
|
+
className: WallToggle_module_css_default.row,
|
|
711
|
+
"aria-label": t("toggle.aria"),
|
|
712
|
+
title: t("toggle"),
|
|
713
|
+
onClick: () => {
|
|
714
|
+
const label = t("view.multiWall");
|
|
715
|
+
const tab = Array.from(document.querySelectorAll("[role=\"tab\"]")).find((el) => el.textContent?.trim() === label);
|
|
716
|
+
tab?.click();
|
|
717
|
+
},
|
|
718
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { size: wide ? 16 : 18 }), wide && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
719
|
+
className: WallToggle_module_css_default.label,
|
|
720
|
+
children: t("toggle")
|
|
721
|
+
})]
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
//#endregion
|
|
726
|
+
//#region src/client/index.ts
|
|
727
|
+
/** Dictionary namespace owned by this plugin. */
|
|
728
|
+
const NS = "multiWall";
|
|
729
|
+
/** Required services: slots for both registrations, locale for copy. */
|
|
730
|
+
const inject = ["slots", "locale"];
|
|
731
|
+
/**
|
|
732
|
+
* Whether this page is an embedded wall pane. Panes load
|
|
733
|
+
* `?multi-wall=embed`; such pages register no wall UI at all, which stops a
|
|
734
|
+
* wall inside a wall (the pane would otherwise recursively embed the
|
|
735
|
+
* serving instance).
|
|
736
|
+
* @returns true when the query flag is present.
|
|
737
|
+
*/
|
|
738
|
+
function isEmbeddedPane() {
|
|
739
|
+
return new URLSearchParams(window.location.search).has("multi-wall");
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Client plugin body: register the dictionaries, the view-ring entry (the
|
|
743
|
+
* wall), and the sidebar footer shortcut. Embedded panes register nothing.
|
|
744
|
+
* @param ctx - client root context.
|
|
745
|
+
*/
|
|
746
|
+
function apply(ctx) {
|
|
747
|
+
if (isEmbeddedPane()) return;
|
|
748
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
749
|
+
zh,
|
|
750
|
+
en
|
|
751
|
+
}), "ui-multi-wall: dictionaries");
|
|
752
|
+
const t = ctx.locale.bind(NS);
|
|
753
|
+
const wallStore = createWallStore();
|
|
754
|
+
ctx.slots.inject("conversation.view", () => ctx.slots.register({
|
|
755
|
+
name: "conversation.view",
|
|
756
|
+
id: "multi-wall",
|
|
757
|
+
order: 20,
|
|
758
|
+
label: () => t("view.multiWall"),
|
|
759
|
+
locale: NS,
|
|
760
|
+
store: wallStore,
|
|
761
|
+
inject: () => createWallInjected()
|
|
762
|
+
}, WallView));
|
|
763
|
+
ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
|
|
764
|
+
name: "sidebar.footer.action",
|
|
765
|
+
id: "multi-wall",
|
|
766
|
+
order: 10,
|
|
767
|
+
locale: NS
|
|
768
|
+
}, WallToggle));
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
//#endregion
|
|
772
|
+
exports.WallToggle = WallToggle;
|
|
773
|
+
exports.WallView = WallView;
|
|
774
|
+
exports.apply = apply;
|
|
775
|
+
exports.createWallStore = createWallStore;
|
|
776
|
+
exports.inject = inject;
|
|
777
|
+
return module.exports; } });
|
|
778
|
+
//# sourceMappingURL=client.js.map
|