dsh-dock 0.14.0 → 0.15.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 +4 -4
- package/client.js +112 -125
- package/features/mobile/host.js +84 -98
- package/features/mobile/view.js +116 -133
- package/features/runstate/host.js +10 -0
- package/features/tokenlog/view.jsx +42 -12
- package/package.json +1 -1
- package/src/client.jsx +1 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
| **趣味游戏** | 任务等待时消磨时间的十款小游戏(五子棋、中国象棋、俄罗斯方块、推箱子、贪吃蛇、打砖块、极速赛车、坦克大战、星际躲避、反应堆解谜);浮动窗口可拖动/缩放/最小化,不遮挡会话,靠屏幕侧边隐藏 |
|
|
27
27
|
| **图片理解代理** | 图片识别等(visionproxy);原图候选默认折叠展示,图片能力以你的声明为准 |
|
|
28
28
|
| **远程访问** | 账号密码登录的远程入口:网关监听 `0.0.0.0` 独立端口,手机/平板/局域网设备访问同一实例,会话与任务进度实时一致;主实例保持仅监听 `127.0.0.1`,结构上不存在绕开登录的直连路径;LAN 非安全上下文自动补 crypto.randomUUID。手机端排版不在此列——统一归「手机适配」。 |
|
|
29
|
-
| **手机适配** | 手机/窄屏排版与触控兜底:输入类控件 16px 防 iOS 聚焦缩放、纯图标按钮触控面积兜底 ≥36px、代码块横向滚动、viewport-fit=cover 安全区、滚动体 touch-action:pan-y + iOS 惯性滚动、设置弹层 min-height:0 滚动兜底;手机端 chrome
|
|
29
|
+
| **手机适配** | 手机/窄屏排版与触控兜底:输入类控件 16px 防 iOS 聚焦缩放、纯图标按钮触控面积兜底 ≥36px、代码块横向滚动、viewport-fit=cover 安全区、滚动体 touch-action:pan-y + iOS 惯性滚动、设置弹层 min-height:0 滚动兜底;手机端 chrome 含左上角「三条横线」汉堡菜单(点击展开左侧会话抽屉,不占页面空间)+ 侧栏抽屉遮罩 + 软键盘让位 + 设置弹层纵排(宿主经 tapIndex 注入,客户端自建同款互斥) |
|
|
30
30
|
| **心跳监视 / 主题信息** | 示例功能(纯 Client) |
|
|
31
31
|
|
|
32
32
|
**会话区随身小控件**:启用后,在会话输入区工具行(模型选择器左侧)显示——
|
|
@@ -244,11 +244,11 @@ dsh plugin --profile web add dsh-dock # 换回 npm 版
|
|
|
244
244
|
- **异地组网**:Tailscale/ZeroTier 等虚拟组网内直接用组网 IP 访问同一入口,效果与局域网相同。
|
|
245
245
|
- 旧版「服务器模式(0.0.0.0)」与「扫码令牌接力」已被此方案取代:前者把无认证的 DSH 裸露给局域网,后者双实例任务状态不同步。开启远程访问时会自动清理旧的 0.0.0.0 webserver 补丁行(提示重启后回到仅本机)。
|
|
246
246
|
2. **手机端接力体验(窄屏自动生效;由「手机适配」提供)**:
|
|
247
|
-
-
|
|
247
|
+
- **左上角汉堡菜单**:点「三条横线」展开左侧会话抽屉(不占页面空间,底部不再有常驻栏)。插件 / 设置 / 运行状态等入口本来就在抽屉里(原生侧栏),展开即可达;**任务等待你确认时汉堡右上角出现红色脉冲角标**(进行中为蓝色计数),一眼可见不用翻页。
|
|
248
248
|
- **PWA 添加到主屏幕**:网关自带 manifest 与图标(品牌渐变 + 白色气泡,零依赖生成),手机浏览器「添加到主屏幕」后像 App 一样全屏打开,无地址栏;未登录也能先装,打开后自然进登录页。
|
|
249
249
|
- **断线提示**:换网 / 锁屏久置导致连接中断时,页面顶部出现「连接已断开,正在重连…」胶囊,恢复后自动消失(连续 2 次探测失败才提示,不闪报)。
|
|
250
|
-
-
|
|
251
|
-
-
|
|
250
|
+
- **软键盘适配**:键盘弹出时汉堡菜单自动让位,不再挤占键盘上方空间。
|
|
251
|
+
- 会话入口统一在左上角汉堡菜单(底部 Tab 栏、左缘浮动抽屉把手与浮动侧栏按钮均已移除,避免占用/遮挡内容)。
|
|
252
252
|
- 面板「远程访问」页与首页总揽会显示当前**在线设备数**;登录页支持密码明文切换。
|
|
253
253
|
|
|
254
254
|
**不开「远程访问」的直连形态**:也可以不经网关、自行经 patch 层把主实例绑 `0.0.0.0` 直连(安全自担,浏览器表层没有登录认证)。这种形态下「远程访问」功能可以整个停用,dsh-dock 仍兜住两件与功能开关无关的事:**非回环浏览器的「预览版说明」自动确认**(dsh 对远程浏览器的确认不持久化,每次刷新都会重弹;插件级注入,回环地址不介入、保留原生的一次性持久确认)与**「手机适配」的整套窄屏排版**(与远程访问完全解耦,见下)。
|
package/client.js
CHANGED
|
@@ -2299,7 +2299,15 @@ function subscribePanel(fn) {
|
|
|
2299
2299
|
// features/tokenlog/view.jsx
|
|
2300
2300
|
var import_react2 = __toESM(require("react"), 1);
|
|
2301
2301
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
2302
|
+
var routeGone = false;
|
|
2303
|
+
function isTokenlogRouteGone() {
|
|
2304
|
+
return routeGone;
|
|
2305
|
+
}
|
|
2306
|
+
function routeGoneMessage() {
|
|
2307
|
+
return "\u5BBF\u4E3B\u672A\u542F\u7528\u300C\u7528\u91CF\u8BB0\u5F55\u300D\u8DEF\u7531\uFF08\u6216\u5BBF\u4E3B\u8FDB\u7A0B\u662F\u65E7\u7248\u672C\uFF09\uFF0C\u8BF7\u5728\u529F\u80FD\u575E\u91CC\u542F\u7528\u8BE5\u529F\u80FD\u6216\u91CD\u542F dsh web";
|
|
2308
|
+
}
|
|
2302
2309
|
function rpcCall(method, args) {
|
|
2310
|
+
if (routeGone) return Promise.reject(new Error(routeGoneMessage()));
|
|
2303
2311
|
return fetch("dsh-dock/tokenlog/" + method, {
|
|
2304
2312
|
method: "POST",
|
|
2305
2313
|
headers: { "content-type": "application/json" },
|
|
@@ -2308,7 +2316,8 @@ function rpcCall(method, args) {
|
|
|
2308
2316
|
const data = await res.json().catch(() => ({}));
|
|
2309
2317
|
if (res.ok && data && data.ok === true) return data.data;
|
|
2310
2318
|
if (res.status === 405 || res.status === 404) {
|
|
2311
|
-
|
|
2319
|
+
routeGone = true;
|
|
2320
|
+
throw new Error(routeGoneMessage());
|
|
2312
2321
|
}
|
|
2313
2322
|
if (data && data.ok === false) throw new Error(data.error && data.error.message || "HTTP " + res.status);
|
|
2314
2323
|
throw new Error("HTTP " + res.status + (data && data.error && data.error.message ? ": " + data.error.message : ""));
|
|
@@ -3670,32 +3679,37 @@ function TokenLogChip(props) {
|
|
|
3670
3679
|
(0, import_react2.useEffect)(() => {
|
|
3671
3680
|
if (!sid) return;
|
|
3672
3681
|
let cancel = false;
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3682
|
+
let timer = null;
|
|
3683
|
+
const load = () => {
|
|
3684
|
+
if (cancel || isTokenlogRouteGone()) return null;
|
|
3685
|
+
return rpcCall("query", { sessionId: sid }).then((d) => {
|
|
3686
|
+
if (!cancel) setSnap({ totals: d && d.totals, rate: d && d.rateUsdCny || 7.2, err: "" });
|
|
3687
|
+
}).catch((e) => {
|
|
3688
|
+
if (!cancel) setSnap((s) => ({ totals: s.totals, rate: s.rate, err: String(e && e.message || e) }));
|
|
3689
|
+
});
|
|
3690
|
+
};
|
|
3678
3691
|
load();
|
|
3679
|
-
|
|
3692
|
+
timer = setInterval(load, 1e4);
|
|
3680
3693
|
return () => {
|
|
3681
3694
|
cancel = true;
|
|
3682
|
-
clearInterval(timer);
|
|
3695
|
+
if (timer) clearInterval(timer);
|
|
3683
3696
|
};
|
|
3684
3697
|
}, [sid]);
|
|
3685
3698
|
if (!sid) return null;
|
|
3686
3699
|
const t = snap.totals;
|
|
3687
|
-
const
|
|
3688
|
-
const
|
|
3700
|
+
const gone = isTokenlogRouteGone();
|
|
3701
|
+
const label = gone ? "\u7528\u91CF\xB7\u672A\u542F\u7528" : snap.err && !t ? snap.err.includes("\u91CD\u542F") || snap.err.includes("\u542F\u7528") ? "\u7528\u91CF\xB7\u672A\u542F\u7528" : "\u7528\u91CF\xB7\u5931\u8D25" : t ? "\u26C1 " + fmtCompact(t.totalTokens) + (t.cost > 0 ? " \xB7 " + fmtCostCny(t.cost, snap.rate) : "") : "\u26C1 \u2026";
|
|
3702
|
+
const title = gone ? routeGoneMessage() + "\n\uFF08\u53EF\u5728\u529F\u80FD\u575E\u91CC\u542F\u7528\u300C\u7528\u91CF\u8BB0\u5F55\u300D\uFF09" : t ? "\u672C\u4F1A\u8BDD " + fmtNum(t.calls) + " \u6B21\u8C03\u7528 \xB7 " + fmtNum(t.totalTokens) + " Token \xB7 " + fmtCostCny(t.cost, snap.rate) + "\uFF08\u4F30\u7B97\uFF09\n\u70B9\u51FB\u5728\u529F\u80FD\u575E\u67E5\u770B\u7528\u91CF\u8BB0\u5F55" : (snap.err ? snap.err + "\n" : "") + "\u70B9\u51FB\u5728\u529F\u80FD\u575E\u67E5\u770B\u7528\u91CF\u8BB0\u5F55";
|
|
3689
3703
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
3690
3704
|
"button",
|
|
3691
3705
|
{
|
|
3692
3706
|
type: "button",
|
|
3693
|
-
className: "dockchip" + (snap.err && !t ? " err" : ""),
|
|
3707
|
+
className: "dockchip" + (snap.err && !t && !gone ? " err" : ""),
|
|
3694
3708
|
title,
|
|
3695
3709
|
"aria-label": "\u4F1A\u8BDD\u7528\u91CF",
|
|
3696
3710
|
onClick: () => openPanel("tokenlog", { sessionId: sid, navAt: Date.now() }),
|
|
3697
3711
|
children: [
|
|
3698
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dockchip-dot", style: { background: "var(--dk-warn)" } }),
|
|
3712
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dockchip-dot", style: { background: gone ? "var(--dsw-alias-label-tertiary,#8b95ab)" : "var(--dk-warn)" } }),
|
|
3699
3713
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: label })
|
|
3700
3714
|
]
|
|
3701
3715
|
}
|
|
@@ -12110,70 +12124,28 @@ function mobileChrome() {
|
|
|
12110
12124
|
scrim.className = "dsh-mobile-scrim";
|
|
12111
12125
|
document.body.appendChild(scrim);
|
|
12112
12126
|
document.querySelectorAll(".dsh-mobile-tabbar").forEach((el) => el.remove());
|
|
12113
|
-
const
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
icon: '<svg viewBox="0 0 24 24"><path d="M21 12a8 8 0 0 1-8 8H5l-2 2V12a8 8 0 0 1 8-8h2a8 8 0 0 1 8 8z"/></svg>',
|
|
12123
|
-
act: () => expand()
|
|
12124
|
-
},
|
|
12125
|
-
{
|
|
12126
|
-
id: "dock",
|
|
12127
|
-
label: "\u63D2\u4EF6",
|
|
12128
|
-
icon: '<svg viewBox="0 0 24 24"><rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/></svg>',
|
|
12129
|
-
act: () => {
|
|
12130
|
-
openPanel(tasksActive > 0 || tasksWaiting > 0 ? "runstate" : "home");
|
|
12131
|
-
}
|
|
12132
|
-
},
|
|
12133
|
-
{
|
|
12134
|
-
id: "settings",
|
|
12135
|
-
label: "\u8BBE\u7F6E",
|
|
12136
|
-
icon: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3.2"/><path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06A1.7 1.7 0 0 0 15 19.4a1.7 1.7 0 0 0-1 1.55V21a2 2 0 1 1-4 0v-.09A1.7 1.7 0 0 0 9 19.4a1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.55-1H3a2 2 0 1 1 0-4h.09A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.7 1.7 0 0 0 9 4.6a1.7 1.7 0 0 0 1-1.55V3a2 2 0 1 1 4 0v.09a1.7 1.7 0 0 0 1 1.51 1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.4 9c.23.6.86 1 1.51 1H21a2 2 0 1 1 0 4h-.09a1.7 1.7 0 0 0-1.51 1z"/></svg>',
|
|
12137
|
-
act: () => {
|
|
12138
|
-
const b = [...document.querySelectorAll("button")].find((x) => {
|
|
12139
|
-
const a = x.getAttribute("aria-label") || "";
|
|
12140
|
-
return a === "\u8BBE\u7F6E" || a === "Settings" || (x.textContent || "").trim() === "\u8BBE\u7F6E";
|
|
12141
|
-
});
|
|
12142
|
-
if (b) b.click();
|
|
12143
|
-
}
|
|
12144
|
-
}
|
|
12145
|
-
];
|
|
12146
|
-
let tasksBadge = null;
|
|
12147
|
-
const tabButtons = tabDefs.map((def) => {
|
|
12148
|
-
const b = document.createElement("button");
|
|
12149
|
-
b.type = "button";
|
|
12150
|
-
b.className = "dsh-mobile-tab";
|
|
12151
|
-
b.dataset.tab = def.id;
|
|
12152
|
-
b.innerHTML = def.icon + "<span>" + def.label + "</span>";
|
|
12153
|
-
b.addEventListener("click", () => def.act());
|
|
12154
|
-
if (def.id === "dock") {
|
|
12155
|
-
tasksBadge = document.createElement("span");
|
|
12156
|
-
tasksBadge.className = "dsh-mobile-tab-badge";
|
|
12157
|
-
tasksBadge.style.display = "none";
|
|
12158
|
-
b.appendChild(tasksBadge);
|
|
12159
|
-
}
|
|
12160
|
-
tabbar.appendChild(b);
|
|
12161
|
-
return b;
|
|
12162
|
-
});
|
|
12163
|
-
document.body.appendChild(tabbar);
|
|
12164
|
-
const syncTabs = () => {
|
|
12127
|
+
const burger = document.createElement("button");
|
|
12128
|
+
burger.type = "button";
|
|
12129
|
+
burger.className = "dsh-mobile-burger";
|
|
12130
|
+
burger.setAttribute("aria-label", "\u6253\u5F00\u4F1A\u8BDD\u5217\u8868");
|
|
12131
|
+
burger.setAttribute("aria-expanded", "false");
|
|
12132
|
+
burger.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16"/><path d="M4 12h16"/><path d="M4 17h16"/></svg><span class="dsh-mobile-burger-badge" hidden></span>';
|
|
12133
|
+
burger.addEventListener("click", () => expand());
|
|
12134
|
+
document.body.appendChild(burger);
|
|
12135
|
+
const syncBurger = () => {
|
|
12165
12136
|
const expanded = !sidebarCollapsed();
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
const id = b.dataset.tab;
|
|
12169
|
-
const on = id === "sessions" && expanded || id === "dock" && panelNav.open || id === "settings" && settingsOpen;
|
|
12170
|
-
b.classList.toggle("on", on);
|
|
12171
|
-
}
|
|
12137
|
+
burger.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
12138
|
+
burger.classList.toggle("on", expanded);
|
|
12172
12139
|
};
|
|
12173
|
-
const
|
|
12140
|
+
const badge = burger.querySelector(".dsh-mobile-burger-badge");
|
|
12174
12141
|
let runstateGone = false;
|
|
12175
12142
|
const pollTasks = () => {
|
|
12176
|
-
if (runstateGone || !
|
|
12143
|
+
if (runstateGone || !badge || !narrow() || document.visibilityState === "hidden" || !burger.isConnected) return;
|
|
12144
|
+
const on = stateOf("runstate").enabled !== false;
|
|
12145
|
+
if (!on && !window.__dshDockRunstate) {
|
|
12146
|
+
badge.hidden = true;
|
|
12147
|
+
return;
|
|
12148
|
+
}
|
|
12177
12149
|
fetch("dsh-dock/runstate/status", {
|
|
12178
12150
|
method: "POST",
|
|
12179
12151
|
headers: { "content-type": "application/json" },
|
|
@@ -12181,32 +12153,36 @@ function mobileChrome() {
|
|
|
12181
12153
|
}).then(async (res) => {
|
|
12182
12154
|
if (res.status === 404 || res.status === 405) {
|
|
12183
12155
|
runstateGone = true;
|
|
12184
|
-
|
|
12185
|
-
tasksWaiting = 0;
|
|
12186
|
-
tasksBadge.style.display = "none";
|
|
12156
|
+
badge.hidden = true;
|
|
12187
12157
|
return null;
|
|
12188
12158
|
}
|
|
12189
12159
|
const body = await res.json().catch(() => null);
|
|
12190
12160
|
if (!body) return null;
|
|
12191
12161
|
const active = body.data && body.data.active || [];
|
|
12192
|
-
const waiting = active.
|
|
12193
|
-
tasksActive = active.length;
|
|
12194
|
-
tasksWaiting = waiting;
|
|
12162
|
+
const waiting = active.reduce((n, t) => n + (Array.isArray(t.approvals) ? t.approvals.length : 0), 0);
|
|
12195
12163
|
if (waiting > 0) {
|
|
12196
|
-
|
|
12197
|
-
|
|
12198
|
-
|
|
12164
|
+
badge.textContent = waiting > 99 ? "99+" : String(waiting);
|
|
12165
|
+
badge.className = "dsh-mobile-burger-badge wait";
|
|
12166
|
+
badge.hidden = false;
|
|
12167
|
+
burger.setAttribute("title", waiting + " \u4E2A\u4EFB\u52A1\u7B49\u5F85\u4F60\u786E\u8BA4");
|
|
12199
12168
|
} else if (active.length > 0) {
|
|
12200
|
-
|
|
12201
|
-
|
|
12202
|
-
|
|
12169
|
+
badge.textContent = active.length > 99 ? "99+" : String(active.length);
|
|
12170
|
+
badge.className = "dsh-mobile-burger-badge";
|
|
12171
|
+
badge.hidden = false;
|
|
12172
|
+
burger.setAttribute("title", active.length + " \u4E2A\u4EFB\u52A1\u8FDB\u884C\u4E2D");
|
|
12203
12173
|
} else {
|
|
12204
|
-
|
|
12174
|
+
badge.hidden = true;
|
|
12175
|
+
burger.removeAttribute("title");
|
|
12205
12176
|
}
|
|
12206
12177
|
}).catch(() => {
|
|
12207
12178
|
});
|
|
12208
12179
|
};
|
|
12209
12180
|
const tasksTimer = setInterval(pollTasks, 3e3);
|
|
12181
|
+
const onVisible = () => {
|
|
12182
|
+
if (document.visibilityState === "visible") pollTasks();
|
|
12183
|
+
};
|
|
12184
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
12185
|
+
pollTasks();
|
|
12210
12186
|
const vv = window.visualViewport || null;
|
|
12211
12187
|
const kbdSync = () => {
|
|
12212
12188
|
if (!vv) return;
|
|
@@ -12215,18 +12191,15 @@ function mobileChrome() {
|
|
|
12215
12191
|
};
|
|
12216
12192
|
const sync = () => {
|
|
12217
12193
|
kbdSync();
|
|
12218
|
-
document.querySelectorAll(".dsh-mobile-tabbar").forEach((el) =>
|
|
12219
|
-
if (el !== tabbar) el.remove();
|
|
12220
|
-
});
|
|
12194
|
+
document.querySelectorAll(".dsh-mobile-tabbar").forEach((el) => el.remove());
|
|
12221
12195
|
const on = narrow() && !!frameEl() && !dialogOpen();
|
|
12222
12196
|
const collapsed = sidebarCollapsed();
|
|
12223
12197
|
const right = rightOpen();
|
|
12224
12198
|
if (scrim) scrim.style.display = on && !collapsed ? "" : "none";
|
|
12225
|
-
|
|
12226
|
-
|
|
12199
|
+
burger.style.display = narrow() && !!frameEl() && !panelNav.open && !right ? "" : "none";
|
|
12200
|
+
syncBurger();
|
|
12227
12201
|
};
|
|
12228
12202
|
sync();
|
|
12229
|
-
pollTasks();
|
|
12230
12203
|
const observer = typeof MutationObserver !== "undefined" ? new MutationObserver(sync) : null;
|
|
12231
12204
|
if (observer) observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-sidebar-collapsed"], subtree: true });
|
|
12232
12205
|
const timer = setInterval(sync, 1500);
|
|
@@ -12235,15 +12208,11 @@ function mobileChrome() {
|
|
|
12235
12208
|
else if (mq.addListener) mq.addListener(onMq);
|
|
12236
12209
|
window.addEventListener("resize", kbdSync);
|
|
12237
12210
|
if (vv && vv.addEventListener) vv.addEventListener("resize", kbdSync);
|
|
12238
|
-
const onVisible = () => {
|
|
12239
|
-
if (document.visibilityState === "visible") pollTasks();
|
|
12240
|
-
};
|
|
12241
|
-
document.addEventListener("visibilitychange", onVisible);
|
|
12242
12211
|
const onClick = (e) => {
|
|
12243
12212
|
if (!narrow()) return;
|
|
12244
12213
|
const t = e.target;
|
|
12245
12214
|
if (!t || !t.closest) return;
|
|
12246
|
-
if (t.closest(".dsh-mobile-
|
|
12215
|
+
if (t.closest(".dsh-mobile-burger")) return;
|
|
12247
12216
|
const expanded = !!document.querySelector('button[aria-label="\u6536\u8D77\u4FA7\u8FB9\u680F"],button[aria-label="Collapse sidebar"]');
|
|
12248
12217
|
const col = document.querySelector('[class*="sidebarCol"]');
|
|
12249
12218
|
const inSidebar = col && col.contains(t);
|
|
@@ -12275,15 +12244,14 @@ function mobileChrome() {
|
|
|
12275
12244
|
if (observer) observer.disconnect();
|
|
12276
12245
|
clearInterval(timer);
|
|
12277
12246
|
clearInterval(tasksTimer);
|
|
12278
|
-
|
|
12247
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
12279
12248
|
if (mq.removeEventListener) mq.removeEventListener("change", onMq);
|
|
12280
12249
|
else if (mq.removeListener) mq.removeListener(onMq);
|
|
12281
12250
|
window.removeEventListener("resize", kbdSync);
|
|
12282
12251
|
if (vv && vv.removeEventListener) vv.removeEventListener("resize", kbdSync);
|
|
12283
|
-
document.removeEventListener("visibilitychange", onVisible);
|
|
12284
12252
|
document.documentElement.classList.remove("dsh-dock-kbd");
|
|
12285
12253
|
if (scrim) scrim.remove();
|
|
12286
|
-
|
|
12254
|
+
burger.remove();
|
|
12287
12255
|
if (window.__dshDockMobileDrawer === "client") delete window.__dshDockMobileDrawer;
|
|
12288
12256
|
};
|
|
12289
12257
|
}
|
|
@@ -12352,7 +12320,7 @@ function MobileView() {
|
|
|
12352
12320
|
import_react22.default.createElement(
|
|
12353
12321
|
"span",
|
|
12354
12322
|
{ className: "dkmob-dim" },
|
|
12355
|
-
"\
|
|
12323
|
+
"\u5DE6\u4E0A\u89D2\u6C49\u5821\u83DC\u5355\uFF08\u5C55\u5F00\u5DE6\u4FA7\u4F1A\u8BDD\u62BD\u5C49\uFF09+ \u4FA7\u680F\u62BD\u5C49\u906E\u7F69 + \u8F6F\u952E\u76D8\u8BA9\u4F4D\uFF1B\u5E95\u90E8 Tab \u680F\u5DF2\u6309\u7528\u6237\u8981\u6C42\u79FB\u9664\uFF0C\u63D2\u4EF6/\u8BBE\u7F6E\u7B49\u5165\u53E3\u5728\u62BD\u5C49\u91CC\u3002",
|
|
12356
12324
|
owner === "host" ? "\u5F53\u524D\u7531\u5BBF\u4E3B\u6CE8\u5165\u7248\u9A71\u52A8\uFF08\u91CD\u542F dsh web \u540E\u751F\u6548\u7684\u90A3\u5957\uFF09\u3002" : owner === "client" ? "\u5F53\u524D\u7531\u5BA2\u6237\u7AEF\u81EA\u5EFA\u7248\u9A71\u52A8\uFF08\u5237\u65B0\u5373\u751F\u6548\uFF09\u3002" : ""
|
|
12357
12325
|
)
|
|
12358
12326
|
),
|
|
@@ -12369,7 +12337,7 @@ function MobileView() {
|
|
|
12369
12337
|
function MobileStat() {
|
|
12370
12338
|
const { active, chrome } = useMobileState();
|
|
12371
12339
|
if (!active) return import_react22.default.createElement("span", null, "\u5F53\u524D\u89C6\u53E3\u672A\u547D\u4E2D\u624B\u673A\u5F62\u6001\uFF0C\u6837\u5F0F\u672A\u6CE8\u5165");
|
|
12372
|
-
return import_react22.default.createElement("span", null, "\u624B\u673A\u6A21\u5F0F\u751F\u6548\u4E2D \xB7 " + (chrome ? "\
|
|
12340
|
+
return import_react22.default.createElement("span", null, "\u624B\u673A\u6A21\u5F0F\u751F\u6548\u4E2D \xB7 " + (chrome ? "\u5DE6\u4E0A\u89D2\u6C49\u5821\u83DC\u5355\u5DF2\u63A5\u7BA1\u4F1A\u8BDD\u5165\u53E3" : "chrome \u5E26\u5BBD\u672A\u547D\u4E2D\uFF08\u2264700px \u624D\u542F\u7528\uFF09"));
|
|
12373
12341
|
}
|
|
12374
12342
|
var css6 = [
|
|
12375
12343
|
// 输入类控件 16px:防 iOS 聚焦自动放大(<16px 的输入框聚焦时 Safari 强制缩放页面)
|
|
@@ -12378,7 +12346,7 @@ var css6 = [
|
|
|
12378
12346
|
'body[data-dk-mobile="1"] :is(button, [role="button"]){-webkit-tap-highlight-color:transparent;touch-action:manipulation;}',
|
|
12379
12347
|
// 纯图标按钮面积兜底:实测窄屏下一批 28×28 的工具钮很难点中;36px 是不引起行高剧变的上限。
|
|
12380
12348
|
// 只收「唯一子元素是 svg」的按钮,避免撑坏带文字的行内控件;排除本模块 chrome 按钮。
|
|
12381
|
-
'body[data-dk-mobile="1"] button:has(> svg:only-child):not(.dsh-mobile-
|
|
12349
|
+
'body[data-dk-mobile="1"] button:has(> svg:only-child):not(.dsh-mobile-burger){min-width:36px;min-height:36px;}',
|
|
12382
12350
|
// 代码块横向滚动、行内代码换行:窄屏不撑破版面
|
|
12383
12351
|
'body[data-dk-mobile="1"] pre{max-width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;}',
|
|
12384
12352
|
'body[data-dk-mobile="1"] code{overflow-wrap:anywhere;}',
|
|
@@ -12395,19 +12363,31 @@ var css6 = [
|
|
|
12395
12363
|
".dkmob-state.on{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary);}",
|
|
12396
12364
|
".dkmob-dim{color:var(--dsw-alias-label-secondary);font-size:12px;}",
|
|
12397
12365
|
".dkmob-note{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.7;border-top:1px solid var(--dsw-alias-border-l1);padding-top:10px;}",
|
|
12398
|
-
// 手机端 chrome
|
|
12366
|
+
// 手机端 chrome:左上角汉堡菜单 + 抽屉遮罩(窄屏 ≤700px;与宿主半部同款)
|
|
12399
12367
|
"@media (max-width:700px){",
|
|
12400
12368
|
".dsh-mobile-scrim{position:fixed;inset:0;z-index:75;background:rgba(8,10,14,.45);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);touch-action:none}",
|
|
12401
|
-
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
".dsh-mobile-
|
|
12409
|
-
"
|
|
12410
|
-
|
|
12369
|
+
// 左上角汉堡菜单(三条横线):44×44 触控面积,避让刘海/圆角安全区;z 66 高于正文、
|
|
12370
|
+
// 低于遮罩 75 / 抽屉 80——抽屉展开时被自然盖住。
|
|
12371
|
+
// ⚠️ 视觉上刻意「不贴合卡片」:页面左上角本来就是空的,原生同类控件(右上的侧栏开关)
|
|
12372
|
+
// 是**裸图标**——无底色、无边框、无阴影。早期版本做成白底圆角卡片 + 阴影 + 模糊,
|
|
12373
|
+
// 悬浮在白底页面上像一块贴上去的异物(用户实测反馈「没有贴合边缘,很突兀」)。
|
|
12374
|
+
// 现在与原生同款:透明底、无边框无阴影,只留图标;点按才给极轻的底色反馈。
|
|
12375
|
+
// 视觉重量靠图标本身撑住(22px、1.9px 描边、主文字色),不靠容器。
|
|
12376
|
+
".dsh-mobile-burger{position:fixed;left:env(safe-area-inset-left,0px);top:env(safe-area-inset-top,0px);z-index:66;box-sizing:border-box;width:44px;height:44px;display:flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:0;background:transparent;color:var(--dsw-alias-label-primary,#e6eaf2);cursor:pointer;-webkit-tap-highlight-color:transparent;touch-action:manipulation;transition:color .15s var(--ds-ease-in-out,ease)}",
|
|
12377
|
+
".dsh-mobile-burger svg{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round}",
|
|
12378
|
+
// 展开态:图标换成强调色(与原生「已激活」的语义一致),不引入额外容器。
|
|
12379
|
+
".dsh-mobile-burger.on{color:var(--dk-accent,#4d9fff)}",
|
|
12380
|
+
// 点按反馈走图标本身(用 :active 的底色透出,范围就是 44×44 触控区)。
|
|
12381
|
+
".dsh-mobile-burger:active{background:color-mix(in srgb,currentColor 12%,transparent)}",
|
|
12382
|
+
// 待确认任务角标:锚在按钮右上角的小圆点/数字。刻意做成「贴边小圆点」而不是
|
|
12383
|
+
// 底部 Tab 栏那种带容器的页签角标——汉堡是裸图标,角标再带容器就又突兀了。
|
|
12384
|
+
// 等待确认(要人介入)= 红点 + 脉冲;进行中 = 蓝色数字(弱一级,不抢注意力)。
|
|
12385
|
+
".dsh-mobile-burger-badge{position:absolute;top:4px;right:4px;box-sizing:border-box;min-width:8px;height:8px;padding:0;border-radius:999px;background:#4d9fff;font:700 9px/8px inherit;font-variant-numeric:tabular-nums;text-align:center;color:#fff;pointer-events:none;box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-1,#12151b)}",
|
|
12386
|
+
".dsh-mobile-burger-badge[hidden]{display:none}",
|
|
12387
|
+
".dsh-mobile-burger-badge:not([hidden]){min-width:16px;height:16px;line-height:16px;padding:0 4px}",
|
|
12388
|
+
".dsh-mobile-burger-badge.wait{background:#ef4444;animation:dsh-burger-badge-pulse 1.2s ease-in-out infinite}",
|
|
12389
|
+
"@keyframes dsh-burger-badge-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.18)}}",
|
|
12390
|
+
"@media (prefers-reduced-motion:reduce){.dsh-mobile-burger-badge.wait{animation:none}}",
|
|
12411
12391
|
// ⚠️ 「设置页滑不动」的关键兜底(客户端无条件携带 = 刷新即生效,不必等宿主重启):
|
|
12412
12392
|
// 窄屏把设置弹层改成纵向堆叠后,内容列若是默认 min-height:auto 就会被内容撑高
|
|
12413
12393
|
// (无头 390×844 实测列高 1216px 塞在 844px 视口里)、再被 .panel 的 overflow:hidden
|
|
@@ -12415,19 +12395,26 @@ var css6 = [
|
|
|
12415
12395
|
// 都不动。补 min-height:0 后 scrollTop 0 → 425。宿主半部 MOBILE_LAYOUT_CSS 同款;
|
|
12416
12396
|
// 两处都写是因为宿主注入要重启 dsh web 才更新,而客户端样式刷新即生效,覆盖窗口期。
|
|
12417
12397
|
'[role="dialog"][class*="panel"]>[class*="content"]{min-height:0!important}',
|
|
12418
|
-
//
|
|
12398
|
+
// 输入区吸附到视口底部 + 滚动体留白(与宿主半部同款)。
|
|
12399
|
+
// ⚠️ 底部留白只保留 iPhone home indicator 的安全区,不再额外留白。沿革:原 72px 是
|
|
12400
|
+
// 「底部 Tab 栏高度 + 安全区」;Tab 栏移除后先收到 24px,但实测(真实实例 390×844
|
|
12401
|
+
// 像素扫描)输入卡片下方仍有 **51px** 空白带,其中 24px 是这里的滚动体内边距、
|
|
12402
|
+
// 6px 是下面 composerSeat 的内边距——两者都是 Tab 栏时代的遗留,在没有底栏的页面上
|
|
12403
|
+
// 纯属浪费。收窄到「只有安全区」后卡片下沿更贴近视口底,这 30px 直接变成会话区
|
|
12404
|
+
// 可显示的正文(滚动体内边距在 flex 容器里会把 composerSeat 连同消息区一起下移/增高)。
|
|
12405
|
+
// dsh 自带的 ~21px 底部间距(卡片 margin)保留,视觉上仍有呼吸感,不会贴死屏幕边缘。
|
|
12419
12406
|
// ⚠️ 绝不能给滚动体加 justify-content:flex-end/center 贴底:内容会被挤到滚动体
|
|
12420
12407
|
// 起点之外,Chrome 不把起点方向溢出算进 scrollHeight(手机视口实测
|
|
12421
12408
|
// scrollHeight==clientHeight、maxScroll==0),会话记录彻底滚不动、首条消息永远看不到。
|
|
12422
12409
|
// 宿主半部旧版注入里还留着那条 flex-end(要重启 dsh web 才消失),这里用更高优先级
|
|
12423
|
-
//
|
|
12424
|
-
'[class*="scrollBody"][data-conversation-scroll]{justify-content:flex-start !important;padding-bottom:
|
|
12425
|
-
'html:not(.dsh-dock-kbd) [class*="scrollBody"]{padding-bottom:
|
|
12426
|
-
'html.dsh-dock-kbd [class*="scrollBody"]{padding-bottom:
|
|
12427
|
-
'[class*="scrollBody"]>[class*="composerSeat"]{margin-top:auto;padding-bottom:
|
|
12410
|
+
// 的选择器显式复位:刷新页面即修好,不必等重启。贴底只能用输入区自己的 margin-top:auto。
|
|
12411
|
+
'[class*="scrollBody"][data-conversation-scroll]{justify-content:flex-start !important;padding-bottom:env(safe-area-inset-bottom,0px) !important}',
|
|
12412
|
+
'html:not(.dsh-dock-kbd) [class*="scrollBody"]{padding-bottom:env(safe-area-inset-bottom,0px) !important}',
|
|
12413
|
+
'html.dsh-dock-kbd [class*="scrollBody"]{padding-bottom:env(safe-area-inset-bottom,0px) !important}',
|
|
12414
|
+
'[class*="scrollBody"]>[class*="composerSeat"]{margin-top:auto;padding-bottom:0}',
|
|
12428
12415
|
// 输入区工具行上的 dsh-dock chip(用量/余额):窄屏下 28cqw 的宽度上限把它们挤成
|
|
12429
12416
|
// 「…」「余..」(手机实测),既读不出数值又白占输入行空间。手机端整组收起,数值在
|
|
12430
|
-
//
|
|
12417
|
+
// 功能坞(抽屉里进)看;桌面端不动。
|
|
12431
12418
|
".dockchip-row{display:none}",
|
|
12432
12419
|
'[class*="sidebarCol"]:has([class*="collapsed"]){visibility:hidden}',
|
|
12433
12420
|
// ⚠️ 列级 visibility:hidden 在 dsh 0.2.0 上挡不住:工作区浏览器给自己的
|
|
@@ -12442,9 +12429,9 @@ var css6 = [
|
|
|
12442
12429
|
// 设置/插件管理弹层挂在侧栏列内部:上面隐藏整列会把它连坐隐藏(空气弹层)。
|
|
12443
12430
|
// visibility 可被后代覆盖,弹层关闭态整体卸载,所以只在真正打开时命中。
|
|
12444
12431
|
'[class*="sidebarCol"]:has([class*="collapsed"]) [role="dialog"]{visibility:visible}',
|
|
12445
|
-
"html.dsh-dock-kbd .dsh-mobile-
|
|
12446
|
-
//
|
|
12447
|
-
// 面板默认的 200
|
|
12432
|
+
"html.dsh-dock-kbd .dsh-mobile-burger{display:none !important}",
|
|
12433
|
+
// 功能坞面板在手机端抬到原生弹层之上:从抽屉进的官方插件管理页等原生弹层 z 序高于
|
|
12434
|
+
// 面板默认的 200,功能坞开在下面会被盖住。抽屉展开时从侧栏 rail 点功能坞图标进入。
|
|
12448
12435
|
'[class*="dockm-backdrop"]{z-index:2000 !important}',
|
|
12449
12436
|
"}"
|
|
12450
12437
|
].join("\n");
|
|
@@ -12453,7 +12440,7 @@ var feature11 = {
|
|
|
12453
12440
|
name: "\u624B\u673A\u9002\u914D",
|
|
12454
12441
|
order: 170,
|
|
12455
12442
|
accent: "#34d399",
|
|
12456
|
-
description: "\u624B\u673A/\u7A84\u5C4F\u6392\u7248\u4E0E\u89E6\u63A7\u515C\u5E95\uFF1A\
|
|
12443
|
+
description: "\u624B\u673A/\u7A84\u5C4F\u6392\u7248\u4E0E\u89E6\u63A7\u515C\u5E95\uFF1A\u5DE6\u4E0A\u89D2\u6C49\u5821\u83DC\u5355 + \u4FA7\u680F\u62BD\u5C49\u300116px \u8F93\u5165\u9632 iOS \u7F29\u653E\u3001\u56FE\u6807\u6309\u94AE\u89E6\u63A7\u9762\u79EF\u3001\u4EE3\u7801\u5757\u6EDA\u52A8\u3001\u6EDA\u52A8\u515C\u5E95",
|
|
12457
12444
|
defaultEnabled: true,
|
|
12458
12445
|
css: css6,
|
|
12459
12446
|
Overlay: MobileOverlay,
|
|
@@ -12462,7 +12449,7 @@ var feature11 = {
|
|
|
12462
12449
|
};
|
|
12463
12450
|
|
|
12464
12451
|
// src/client.jsx
|
|
12465
|
-
var DOCK_VERSION = "0.
|
|
12452
|
+
var DOCK_VERSION = "0.15.0";
|
|
12466
12453
|
var BUILTIN_FEATURES = [feature, feature2, feature3, feature4, feature5, feature6, feature7, feature8, feature9, feature10, feature11];
|
|
12467
12454
|
var PLANNED_FEATURES = [];
|
|
12468
12455
|
var PLANNED_NOTES = {};
|