dsh-dock 0.9.3 → 0.9.6
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/client.js +118 -6
- package/features/animation/host.js +1 -1
- package/features/balance/host.js +1 -1
- package/features/games/view.jsx +1 -1
- package/features/heartbeat/view.js +1 -0
- package/features/mobile-relay/gateway.js +125 -68
- package/features/mobile-relay/host.js +116 -3
- package/features/mobile-relay/view.jsx +129 -3
- package/features/modelconfig/host.js +1 -1
- package/features/theme/view.js +1 -0
- package/features/tokenlog/host.js +1 -1
- package/features/visionproxy/host.js +1 -1
- package/package.json +1 -1
- package/src/client.jsx +1 -1
- package/src/shared.js +3 -2
package/client.js
CHANGED
|
@@ -2174,7 +2174,7 @@ function persistFeatureEnabled() {
|
|
|
2174
2174
|
function initFeatureState(defs) {
|
|
2175
2175
|
for (const f of defs) {
|
|
2176
2176
|
if (featureState.has(f.id)) continue;
|
|
2177
|
-
let enabled = !f.planned && f.defaultEnabled
|
|
2177
|
+
let enabled = !f.planned && f.defaultEnabled === true;
|
|
2178
2178
|
const saved = featurePersist.map[f.id];
|
|
2179
2179
|
if (typeof saved === "boolean") enabled = saved;
|
|
2180
2180
|
featureState.set(f.id, { enabled, error: null });
|
|
@@ -3391,6 +3391,7 @@ var feature3 = {
|
|
|
3391
3391
|
order: 140,
|
|
3392
3392
|
accent: "#34d399",
|
|
3393
3393
|
description: "\u793A\u4F8B\u529F\u80FD\uFF1A\u9762\u677F\u4FA7\u8FD0\u884C\u65F6\u957F\u5FC3\u8DF3\uFF08\u7EAF Client\uFF09",
|
|
3394
|
+
defaultEnabled: false,
|
|
3394
3395
|
View: HeartbeatView,
|
|
3395
3396
|
HomeStat: HeartbeatStat
|
|
3396
3397
|
};
|
|
@@ -3449,6 +3450,7 @@ var feature4 = {
|
|
|
3449
3450
|
order: 150,
|
|
3450
3451
|
accent: "#a78bfa",
|
|
3451
3452
|
description: "\u8BFB\u53D6\u5F53\u524D\u4E3B\u9898\u5FEB\u7167\uFF1A\u504F\u597D\u4E0E\u5B9E\u9645\u751F\u6548\u4E3B\u9898\uFF08\u7EAF Client\uFF0C\u4E3B\u9898\u5207\u6362\u5B9E\u65F6\u5237\u65B0\uFF09",
|
|
3453
|
+
defaultEnabled: false,
|
|
3452
3454
|
View: ThemeView,
|
|
3453
3455
|
HomeStat: ThemeStat
|
|
3454
3456
|
};
|
|
@@ -9665,7 +9667,7 @@ var feature7 = {
|
|
|
9665
9667
|
order: 135,
|
|
9666
9668
|
accent: "#a78bfa",
|
|
9667
9669
|
description: "\u4EFB\u52A1\u7B49\u5F85\u65F6\u53EF\u73A9\u7684\u5341\u6B3E\u5C0F\u6E38\u620F\uFF1A\u4E94\u5B50\u68CB\u3001\u4E2D\u56FD\u8C61\u68CB\uFF08\u4F60 vs AI\uFF09\u3001\u4FC4\u7F57\u65AF\u65B9\u5757\u3001\u63A8\u7BB1\u5B50\u3001\u8D2A\u5403\u86C7\u3001\u6253\u7816\u5757\u3001\u6781\u901F\u8D5B\u8F66\u3001\u5766\u514B\u5927\u6218\u3001\u661F\u9645\u8EB2\u907F\u3001\u53CD\u5E94\u5806\u70B9\u4EAE\u3002\u6D6E\u52A8\u7A97\u53E3\u8FD0\u884C\uFF08\u53EF\u62D6\u52A8\u3001\u53EF\u8C03\u5927\u5C0F\u3001\u53EF\u6700\u5927\u5316\u6700\u5C0F\u5316\uFF0C\u4E0D\u906E\u6321\u4F1A\u8BDD\uFF09\uFF0C\u5C4F\u5E55\u4FA7\u8FB9\u78C1\u5438\u5FEB\u6377\u5165\u53E3\u8D34\u5728\u5DE6\u4FA7\u8FB9\u680F\u53F3\u7F18\uFF08\u4E0D\u6321\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF09\uFF0C\u53EF\u4E0A\u4E0B\u632A\u4F4D\u3001\u5DE6\u53F3\u6362\u8FB9\uFF1B\u5747\u652F\u6301\u952E\u76D8",
|
|
9668
|
-
defaultEnabled:
|
|
9670
|
+
defaultEnabled: false,
|
|
9669
9671
|
css: css3,
|
|
9670
9672
|
View: GamesView,
|
|
9671
9673
|
HomeStat: GamesHomeStat,
|
|
@@ -9971,6 +9973,112 @@ function RemoteCard() {
|
|
|
9971
9973
|
function MobileRelayView() {
|
|
9972
9974
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(RemoteCard, {});
|
|
9973
9975
|
}
|
|
9976
|
+
function MobileRelayOverlay() {
|
|
9977
|
+
(0, import_react19.useEffect)(() => {
|
|
9978
|
+
if (typeof window === "undefined" || typeof document === "undefined") return void 0;
|
|
9979
|
+
if (window.__dshDockMobileDrawer) return void 0;
|
|
9980
|
+
const mq = window.matchMedia ? window.matchMedia("(max-width:700px)") : null;
|
|
9981
|
+
if (!mq) return void 0;
|
|
9982
|
+
window.__dshDockMobileDrawer = "client";
|
|
9983
|
+
const narrow = () => mq.matches;
|
|
9984
|
+
const frameEl = () => document.querySelector('[class$="frame"]');
|
|
9985
|
+
const sidebarCollapsed = () => {
|
|
9986
|
+
const f = frameEl();
|
|
9987
|
+
return !!f && f.hasAttribute("data-sidebar-collapsed");
|
|
9988
|
+
};
|
|
9989
|
+
const dialogOpen = () => !!document.querySelector('[role="dialog"][class*="panel"]');
|
|
9990
|
+
const collapse = () => {
|
|
9991
|
+
const b = document.querySelector('button[aria-label="\u6536\u8D77\u4FA7\u8FB9\u680F"],button[aria-label="Collapse sidebar"]');
|
|
9992
|
+
if (b) b.click();
|
|
9993
|
+
};
|
|
9994
|
+
const expand = () => {
|
|
9995
|
+
const b = document.querySelector('button[aria-label="\u6253\u5F00\u4FA7\u8FB9\u680F"],button[aria-label="Open sidebar"]');
|
|
9996
|
+
if (b) b.click();
|
|
9997
|
+
};
|
|
9998
|
+
const closeDetails = () => {
|
|
9999
|
+
const d = document.querySelector('[class*="detailsCol"]');
|
|
10000
|
+
if (!d || !d.getBoundingClientRect().width) return;
|
|
10001
|
+
const c = d.querySelector('button[aria-label="\u5173\u95ED\u8BE6\u60C5"]');
|
|
10002
|
+
if (c) c.click();
|
|
10003
|
+
};
|
|
10004
|
+
const fab = document.createElement("button");
|
|
10005
|
+
fab.type = "button";
|
|
10006
|
+
fab.className = "dsh-mobile-drawer-btn";
|
|
10007
|
+
fab.setAttribute("aria-label", "\u6253\u5F00\u4F1A\u8BDD\u5217\u8868");
|
|
10008
|
+
fab.innerHTML = '<svg viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h10"/></svg>';
|
|
10009
|
+
fab.addEventListener("click", expand);
|
|
10010
|
+
const scrim = document.createElement("div");
|
|
10011
|
+
scrim.className = "dsh-mobile-scrim";
|
|
10012
|
+
document.body.appendChild(fab);
|
|
10013
|
+
document.body.appendChild(scrim);
|
|
10014
|
+
const place = () => {
|
|
10015
|
+
const vh = window.innerHeight;
|
|
10016
|
+
let top = null;
|
|
10017
|
+
const g = document.querySelector(".dgfab");
|
|
10018
|
+
if (g && !g.classList.contains("dgfab-hide") && g.getBoundingClientRect) {
|
|
10019
|
+
const r = g.getBoundingClientRect();
|
|
10020
|
+
if (r && r.height > 0 && r.top > 0) {
|
|
10021
|
+
top = r.top - fab.offsetHeight - 8;
|
|
10022
|
+
if (top < 64) top = r.bottom + 8;
|
|
10023
|
+
if (top + fab.offsetHeight > vh - 16) top = null;
|
|
10024
|
+
}
|
|
10025
|
+
}
|
|
10026
|
+
if (top == null) top = Math.round(vh * 0.38);
|
|
10027
|
+
fab.style.top = top + "px";
|
|
10028
|
+
};
|
|
10029
|
+
const sync = () => {
|
|
10030
|
+
const on = narrow() && !!frameEl() && !dialogOpen();
|
|
10031
|
+
const collapsed = sidebarCollapsed();
|
|
10032
|
+
const showFab = on && collapsed;
|
|
10033
|
+
fab.style.display = showFab ? "" : "none";
|
|
10034
|
+
if (showFab) place();
|
|
10035
|
+
scrim.style.display = on && !collapsed ? "" : "none";
|
|
10036
|
+
};
|
|
10037
|
+
sync();
|
|
10038
|
+
const observer = typeof MutationObserver !== "undefined" ? new MutationObserver(sync) : null;
|
|
10039
|
+
if (observer) observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-sidebar-collapsed"], subtree: true });
|
|
10040
|
+
const timer = setInterval(sync, 1500);
|
|
10041
|
+
const onMq = () => sync();
|
|
10042
|
+
if (mq.addEventListener) mq.addEventListener("change", onMq);
|
|
10043
|
+
else if (mq.addListener) mq.addListener(onMq);
|
|
10044
|
+
window.addEventListener("resize", place);
|
|
10045
|
+
const onClick = (e) => {
|
|
10046
|
+
if (!narrow()) return;
|
|
10047
|
+
const t = e.target;
|
|
10048
|
+
if (!t || !t.closest) return;
|
|
10049
|
+
if (t.closest(".dsh-mobile-drawer-btn")) return;
|
|
10050
|
+
const expanded = !!document.querySelector('button[aria-label="\u6536\u8D77\u4FA7\u8FB9\u680F"],button[aria-label="Collapse sidebar"]');
|
|
10051
|
+
const col = document.querySelector('[class*="sidebarCol"]');
|
|
10052
|
+
const inSidebar = col && col.contains(t);
|
|
10053
|
+
if (!expanded && !inSidebar) return;
|
|
10054
|
+
if (inSidebar) {
|
|
10055
|
+
const row = t.closest('[role="treeitem"]');
|
|
10056
|
+
const leaf = row && !row.querySelector('[role="treeitem"]') && row.closest('[role="tree"]');
|
|
10057
|
+
const fresh = t.closest('[class*="newSession"]');
|
|
10058
|
+
if (leaf || fresh) setTimeout(() => {
|
|
10059
|
+
collapse();
|
|
10060
|
+
closeDetails();
|
|
10061
|
+
}, 300);
|
|
10062
|
+
return;
|
|
10063
|
+
}
|
|
10064
|
+
if (t.closest('[role="dialog"],[class*="dockm"],[class*="dgfab"],[class*="dgwin"],[class*="dgame"],[class*="detailsCol"]')) return;
|
|
10065
|
+
collapse();
|
|
10066
|
+
};
|
|
10067
|
+
document.addEventListener("click", onClick, true);
|
|
10068
|
+
return () => {
|
|
10069
|
+
document.removeEventListener("click", onClick, true);
|
|
10070
|
+
if (observer) observer.disconnect();
|
|
10071
|
+
clearInterval(timer);
|
|
10072
|
+
if (mq.removeEventListener) mq.removeEventListener("change", onMq);
|
|
10073
|
+
else if (mq.removeListener) mq.removeListener(onMq);
|
|
10074
|
+
window.removeEventListener("resize", place);
|
|
10075
|
+
fab.remove();
|
|
10076
|
+
scrim.remove();
|
|
10077
|
+
if (window.__dshDockMobileDrawer === "client") delete window.__dshDockMobileDrawer;
|
|
10078
|
+
};
|
|
10079
|
+
}, []);
|
|
10080
|
+
return null;
|
|
10081
|
+
}
|
|
9974
10082
|
function MobileRelayHomeStat() {
|
|
9975
10083
|
const [summary, setSummary] = (0, import_react19.useState)("\u672A\u5F00\u542F\uFF0C\u5F00\u542F\u540E\u53EF\u8FDC\u7A0B\u767B\u5F55");
|
|
9976
10084
|
(0, import_react19.useEffect)(() => {
|
|
@@ -9987,15 +10095,19 @@ var feature8 = {
|
|
|
9987
10095
|
order: 80,
|
|
9988
10096
|
accent: "#38bdf8",
|
|
9989
10097
|
description: "\u8D26\u53F7\u5BC6\u7801\u767B\u5F55\u7684\u8FDC\u7A0B\u5165\u53E3\uFF1A\u6240\u6709\u8BBE\u5907\u8BBF\u95EE\u540C\u4E00\u4E2A DSH\uFF0C\u4EFB\u52A1\u8FDB\u5EA6\u5B9E\u65F6\u4E00\u81F4",
|
|
9990
|
-
defaultEnabled:
|
|
10098
|
+
defaultEnabled: false,
|
|
9991
10099
|
css: `
|
|
9992
|
-
.dmr{--dmr-accent:var(--dk-accent,#2f6fed);--dmr-accent-soft:color-mix(in srgb,var(--dmr-accent) 13%,transparent);display:flex;flex-direction:column;gap:16px;max-width:720px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}.dmr h3,.dmr h4,.dmr p{margin:0}.dmr h3{font-size:17px;line-height:1.25;letter-spacing:-.01em}.dmr h4{font-size:13px}.dmr-hero,.dmr-status-head{display:flex;align-items:flex-start;gap:12px}.dmr-hero>div,.dmr-status-head>div{min-width:0;display:flex;flex-direction:column;gap:4px}.dmr-hero p,.dmr-status-head p,.dmr-field small,.dmr-share small,.dmr-overview-card small,.dmr-task small,.dmr-note p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}.dmr-hero-icon{width:42px;height:42px;display:inline-flex;align-items:center;justify-content:center;flex:none;border-radius:14px;background:var(--dmr-accent-soft);color:var(--dmr-accent);border:1px solid color-mix(in srgb,var(--dmr-accent) 35%,var(--dsw-alias-border-l1))}.dmr-steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin:0;padding:0;list-style:none}.dmr-steps li{display:flex;gap:9px;padding:11px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:12px}.dmr-steps li>span{width:21px;height:21px;display:inline-flex;align-items:center;justify-content:center;flex:none;border-radius:50%;font-size:11px;font-weight:700;color:var(--dmr-accent);background:var(--dmr-accent-soft)}.dmr-steps div{display:flex;flex-direction:column;gap:3px;min-width:0}.dmr-steps strong{font-size:12px}.dmr-steps small{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.45}.dmr-field{display:flex;flex-direction:column;gap:6px;font-weight:600}.dmr-field input,.dmr-field select,.dmr-note textarea{box-sizing:border-box;width:100%;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:10px;padding:10px 12px;min-height:44px;font:inherit;outline:none;transition:border-color .18s ease,box-shadow .18s ease}.dmr-field input:focus,.dmr-field select:focus,.dmr-note textarea:focus{border-color:var(--dmr-accent);box-shadow:0 0 0 3px var(--dmr-accent-soft)}.dmr-field small{font-weight:400}.dmr-network-grid{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(150px,.6fr);gap:12px}.dmr-security{padding:12px 13px;border:1px solid color-mix(in srgb,var(--dk-warn) 48%,var(--dsw-alias-border-l1));border-radius:12px;background:color-mix(in srgb,var(--dk-warn) 9%,transparent)}.dmr-security strong{display:block;margin-bottom:4px;color:var(--dk-warn);font-size:12px}.dmr-security p{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.55}.dmr-primary,.dmr-secondary,.dmr-text-button,.dmr-icon-button{font:inherit;touch-action:manipulation;cursor:pointer;transition:transform .15s ease,background .18s ease,border-color .18s ease,opacity .18s ease}.dmr-primary,.dmr-secondary{min-height:44px;display:inline-flex;align-items:center;justify-content:center;gap:8px;border-radius:10px;padding:9px 13px;font-weight:600}.dmr-primary{align-self:flex-start;border:1px solid var(--dmr-accent);background:var(--dmr-accent);color:#fff}.dmr-primary:hover{filter:brightness(1.05)}.dmr-secondary{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.dmr-secondary:hover{border-color:var(--dmr-accent);background:var(--dmr-accent-soft)}.dmr-primary:active,.dmr-secondary:active,.dmr-icon-button:active{transform:scale(.98)}.dmr-primary:disabled,.dmr-secondary:disabled,.dmr-text-button:disabled,.dmr-icon-button:disabled{cursor:not-allowed;opacity:.5}.dmr-message{display:flex;align-items:flex-start;gap:7px;padding:10px 12px;border-radius:10px;font-size:12px}.dmr-message.success{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 10%,transparent)}.dmr-message.error{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}.dmr-status-head{justify-content:space-between}.dmr-eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:var(--dmr-accent)}.dmr-eyebrow i{width:7px;height:7px;border-radius:50%;background:currentColor;box-shadow:0 0 0 4px var(--dmr-accent-soft)}.dmr-icon-button{display:inline-flex;align-items:center;justify-content:center;flex:none;width:44px;height:44px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:transparent;color:var(--dsw-alias-label-secondary)}.dmr-icon-button:hover{color:var(--dsw-alias-state-error-primary);border-color:currentColor}.dmr-share{display:flex;flex-direction:column;gap:9px;padding:13px;border:1px solid color-mix(in srgb,var(--dmr-accent) 32%,var(--dsw-alias-border-l1));border-radius:13px;background:var(--dmr-accent-soft)}.dmr-share-layout{display:grid;grid-template-columns:196px minmax(0,1fr);gap:16px;align-items:center}.dmr-qr-card{box-sizing:border-box;display:flex;flex-direction:column;align-items:center;gap:5px;padding:10px;border-radius:12px;background:#fff;color:#111827;text-align:center}.dmr-qr-card img{display:block;width:176px;height:176px;max-width:100%;object-fit:contain}.dmr-qr-card strong{font-size:12px}.dmr-qr-card small{color:#4b5563;font-size:10px}.dmr-qr-loading{display:grid;place-items:center;width:176px;height:176px;color:#64748b;font-size:12px}.dmr-share-detail{min-width:0;display:flex;flex-direction:column;gap:10px}.dmr-share-detail>div:first-child{display:flex;flex-direction:column;gap:4px}.dmr-link{padding:10px 11px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;word-break:break-all;color:var(--dsw-alias-label-secondary)}.dmr-share-actions{display:flex;gap:8px;flex-wrap:wrap}.dmr-share-actions .dmr-primary{align-self:auto}.dmr-overview{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.dmr-overview-card{display:flex;flex-direction:column;gap:3px;padding:12px;border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}.dmr-overview-card>span{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dmr-overview-card strong{font-size:16px;letter-spacing:-.01em}.dmr-section{display:flex;flex-direction:column;gap:9px}.dmr-section-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.dmr-section-head small{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dmr-text-button{border:0;background:transparent;color:var(--dmr-accent);padding:8px;min-height:36px;font-weight:600}.dmr-task-list{display:flex;flex-direction:column;gap:8px}.dmr-task{display:flex;align-items:flex-start;gap:9px;padding:10px 11px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}.dmr-task>div{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}.dmr-task strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dmr-phase{flex:none;border-radius:999px;padding:3px 8px;font-size:11px;background:var(--dmr-accent-soft);color:var(--dmr-accent)}.dmr-phase.write{color:var(--dsw-alias-state-success-primary)}.dmr-phase.code{color:var(--dk-warn)}.dmr-phase.search{color:#0d9488}.dmr-empty{padding:18px 12px;text-align:center;border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-secondary);font-size:12px}.dmr-note{display:flex;flex-direction:column;gap:9px;padding-top:2px}.dmr-note blockquote{margin:0;padding:10px 12px;border-left:3px solid var(--dmr-accent);border-radius:0 9px 9px 0;background:var(--dmr-accent-soft);white-space:pre-wrap;font-size:12px}.dmr-note textarea{min-height:88px;resize:vertical;line-height:1.5}.dmr-note .dmr-secondary{align-self:flex-start}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media (max-width:680px){.dmr{gap:14px;font-size:14px}.dmr h3{font-size:18px}.dmr-steps{grid-template-columns:1fr;gap:8px}.dmr-steps li{padding:10px}.dmr-steps small,.dmr-hero p,.dmr-status-head p,.dmr-field small,.dmr-share small,.dmr-overview-card small,.dmr-task small,.dmr-note p{font-size:12px}.dmr-network-grid{grid-template-columns:1fr;gap:10px}.dmr-field input,.dmr-field select{font-size:16px}.dmr-primary,.dmr-secondary{width:100%;font-size:14px}.dmr-share-layout{grid-template-columns:1fr;gap:12px}.dmr-qr-card{width:min(240px,100%);margin:0 auto}.dmr-qr-card img,.dmr-qr-loading{width:210px;height:210px}.dmr-share-actions{flex-direction:column}.dmr-share-actions .dmr-primary{width:100%}.dmr-overview{gap:8px}.dmr-overview-card{padding:11px}.dmr-status-head{gap:8px}.dmr-link{font-size:11px}.dmr-note textarea{font-size:16px;min-height:104px}.dmr-note .dmr-secondary{align-self:stretch}.dmr-compact .dmr-hero-icon{width:40px;height:40px;border-radius:13px}}@media (prefers-reduced-motion:reduce){.dmr-primary,.dmr-secondary,.dmr-text-button,.dmr-icon-button,.dmr-field input,.dmr-field select,.dmr-note textarea{transition:none}}.dmr-lan{flex:none;margin-top:6px;padding-top:16px;border-top:1px solid var(--dsw-alias-border-l1)}.dmr-lan-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.dmr-lan-title{min-width:0;display:flex;flex-direction:column;gap:4px}.dmr-lan-title p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}.dmr-lan-badge{flex:none;display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:5px 10px;font-size:11px;font-weight:700;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}.dmr-lan-badge.on{border-color:color-mix(in srgb,var(--dmr-accent) 45%,transparent);background:var(--dmr-accent-soft);color:var(--dmr-accent)}.dmr-lan-active,.dmr-lan-idle{display:flex;flex-direction:column;gap:12px}.dmr-lan-active .dmr-share-detail small strong{color:var(--dsw-alias-state-error-primary)}.dmr-danger{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,var(--dsw-alias-border-l2));color:var(--dsw-alias-state-error-primary)}.dmr-danger:hover{border-color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}`,
|
|
10100
|
+
.dmr{--dmr-accent:var(--dk-accent,#2f6fed);--dmr-accent-soft:color-mix(in srgb,var(--dmr-accent) 13%,transparent);display:flex;flex-direction:column;gap:16px;max-width:720px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}.dmr h3,.dmr h4,.dmr p{margin:0}.dmr h3{font-size:17px;line-height:1.25;letter-spacing:-.01em}.dmr h4{font-size:13px}.dmr-hero,.dmr-status-head{display:flex;align-items:flex-start;gap:12px}.dmr-hero>div,.dmr-status-head>div{min-width:0;display:flex;flex-direction:column;gap:4px}.dmr-hero p,.dmr-status-head p,.dmr-field small,.dmr-share small,.dmr-overview-card small,.dmr-task small,.dmr-note p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}.dmr-hero-icon{width:42px;height:42px;display:inline-flex;align-items:center;justify-content:center;flex:none;border-radius:14px;background:var(--dmr-accent-soft);color:var(--dmr-accent);border:1px solid color-mix(in srgb,var(--dmr-accent) 35%,var(--dsw-alias-border-l1))}.dmr-steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin:0;padding:0;list-style:none}.dmr-steps li{display:flex;gap:9px;padding:11px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:12px}.dmr-steps li>span{width:21px;height:21px;display:inline-flex;align-items:center;justify-content:center;flex:none;border-radius:50%;font-size:11px;font-weight:700;color:var(--dmr-accent);background:var(--dmr-accent-soft)}.dmr-steps div{display:flex;flex-direction:column;gap:3px;min-width:0}.dmr-steps strong{font-size:12px}.dmr-steps small{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.45}.dmr-field{display:flex;flex-direction:column;gap:6px;font-weight:600}.dmr-field input,.dmr-field select,.dmr-note textarea{box-sizing:border-box;width:100%;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:10px;padding:10px 12px;min-height:44px;font:inherit;outline:none;transition:border-color .18s ease,box-shadow .18s ease}.dmr-field input:focus,.dmr-field select:focus,.dmr-note textarea:focus{border-color:var(--dmr-accent);box-shadow:0 0 0 3px var(--dmr-accent-soft)}.dmr-field small{font-weight:400}.dmr-network-grid{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(150px,.6fr);gap:12px}.dmr-security{padding:12px 13px;border:1px solid color-mix(in srgb,var(--dk-warn) 48%,var(--dsw-alias-border-l1));border-radius:12px;background:color-mix(in srgb,var(--dk-warn) 9%,transparent)}.dmr-security strong{display:block;margin-bottom:4px;color:var(--dk-warn);font-size:12px}.dmr-security p{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.55}.dmr-primary,.dmr-secondary,.dmr-text-button,.dmr-icon-button{font:inherit;touch-action:manipulation;cursor:pointer;transition:transform .15s ease,background .18s ease,border-color .18s ease,opacity .18s ease}.dmr-primary,.dmr-secondary{min-height:44px;display:inline-flex;align-items:center;justify-content:center;gap:8px;border-radius:10px;padding:9px 13px;font-weight:600}.dmr-primary{align-self:flex-start;border:1px solid var(--dmr-accent);background:var(--dmr-accent);color:#fff}.dmr-primary:hover{filter:brightness(1.05)}.dmr-secondary{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.dmr-secondary:hover{border-color:var(--dmr-accent);background:var(--dmr-accent-soft)}.dmr-primary:active,.dmr-secondary:active,.dmr-icon-button:active{transform:scale(.98)}.dmr-primary:disabled,.dmr-secondary:disabled,.dmr-text-button:disabled,.dmr-icon-button:disabled{cursor:not-allowed;opacity:.5}.dmr-message{display:flex;align-items:flex-start;gap:7px;padding:10px 12px;border-radius:10px;font-size:12px}.dmr-message.success{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 10%,transparent)}.dmr-message.error{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}.dmr-status-head{justify-content:space-between}.dmr-eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:var(--dmr-accent)}.dmr-eyebrow i{width:7px;height:7px;border-radius:50%;background:currentColor;box-shadow:0 0 0 4px var(--dmr-accent-soft)}.dmr-icon-button{display:inline-flex;align-items:center;justify-content:center;flex:none;width:44px;height:44px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:transparent;color:var(--dsw-alias-label-secondary)}.dmr-icon-button:hover{color:var(--dsw-alias-state-error-primary);border-color:currentColor}.dmr-share{display:flex;flex-direction:column;gap:9px;padding:13px;border:1px solid color-mix(in srgb,var(--dmr-accent) 32%,var(--dsw-alias-border-l1));border-radius:13px;background:var(--dmr-accent-soft)}.dmr-share-layout{display:grid;grid-template-columns:196px minmax(0,1fr);gap:16px;align-items:center}.dmr-qr-card{box-sizing:border-box;display:flex;flex-direction:column;align-items:center;gap:5px;padding:10px;border-radius:12px;background:#fff;color:#111827;text-align:center}.dmr-qr-card img{display:block;width:176px;height:176px;max-width:100%;object-fit:contain}.dmr-qr-card strong{font-size:12px}.dmr-qr-card small{color:#4b5563;font-size:10px}.dmr-qr-loading{display:grid;place-items:center;width:176px;height:176px;color:#64748b;font-size:12px}.dmr-share-detail{min-width:0;display:flex;flex-direction:column;gap:10px}.dmr-share-detail>div:first-child{display:flex;flex-direction:column;gap:4px}.dmr-link{padding:10px 11px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;word-break:break-all;color:var(--dsw-alias-label-secondary)}.dmr-share-actions{display:flex;gap:8px;flex-wrap:wrap}.dmr-share-actions .dmr-primary{align-self:auto}.dmr-overview{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.dmr-overview-card{display:flex;flex-direction:column;gap:3px;padding:12px;border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}.dmr-overview-card>span{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dmr-overview-card strong{font-size:16px;letter-spacing:-.01em}.dmr-section{display:flex;flex-direction:column;gap:9px}.dmr-section-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.dmr-section-head small{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dmr-text-button{border:0;background:transparent;color:var(--dmr-accent);padding:8px;min-height:36px;font-weight:600}.dmr-task-list{display:flex;flex-direction:column;gap:8px}.dmr-task{display:flex;align-items:flex-start;gap:9px;padding:10px 11px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}.dmr-task>div{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}.dmr-task strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dmr-phase{flex:none;border-radius:999px;padding:3px 8px;font-size:11px;background:var(--dmr-accent-soft);color:var(--dmr-accent)}.dmr-phase.write{color:var(--dsw-alias-state-success-primary)}.dmr-phase.code{color:var(--dk-warn)}.dmr-phase.search{color:#0d9488}.dmr-empty{padding:18px 12px;text-align:center;border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-secondary);font-size:12px}.dmr-note{display:flex;flex-direction:column;gap:9px;padding-top:2px}.dmr-note blockquote{margin:0;padding:10px 12px;border-left:3px solid var(--dmr-accent);border-radius:0 9px 9px 0;background:var(--dmr-accent-soft);white-space:pre-wrap;font-size:12px}.dmr-note textarea{min-height:88px;resize:vertical;line-height:1.5}.dmr-note .dmr-secondary{align-self:flex-start}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media (max-width:680px){.dmr{gap:14px;font-size:14px}.dmr h3{font-size:18px}.dmr-steps{grid-template-columns:1fr;gap:8px}.dmr-steps li{padding:10px}.dmr-steps small,.dmr-hero p,.dmr-status-head p,.dmr-field small,.dmr-share small,.dmr-overview-card small,.dmr-task small,.dmr-note p{font-size:12px}.dmr-network-grid{grid-template-columns:1fr;gap:10px}.dmr-field input,.dmr-field select{font-size:16px}.dmr-primary,.dmr-secondary{width:100%;font-size:14px}.dmr-share-layout{grid-template-columns:1fr;gap:12px}.dmr-qr-card{width:min(240px,100%);margin:0 auto}.dmr-qr-card img,.dmr-qr-loading{width:210px;height:210px}.dmr-share-actions{flex-direction:column}.dmr-share-actions .dmr-primary{width:100%}.dmr-overview{gap:8px}.dmr-overview-card{padding:11px}.dmr-status-head{gap:8px}.dmr-link{font-size:11px}.dmr-note textarea{font-size:16px;min-height:104px}.dmr-note .dmr-secondary{align-self:stretch}.dmr-compact .dmr-hero-icon{width:40px;height:40px;border-radius:13px}}@media (prefers-reduced-motion:reduce){.dmr-primary,.dmr-secondary,.dmr-text-button,.dmr-icon-button,.dmr-field input,.dmr-field select,.dmr-note textarea{transition:none}}.dmr-lan{flex:none;margin-top:6px;padding-top:16px;border-top:1px solid var(--dsw-alias-border-l1)}.dmr-lan-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.dmr-lan-title{min-width:0;display:flex;flex-direction:column;gap:4px}.dmr-lan-title p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}.dmr-lan-badge{flex:none;display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:5px 10px;font-size:11px;font-weight:700;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}.dmr-lan-badge.on{border-color:color-mix(in srgb,var(--dmr-accent) 45%,transparent);background:var(--dmr-accent-soft);color:var(--dmr-accent)}.dmr-lan-active,.dmr-lan-idle{display:flex;flex-direction:column;gap:12px}.dmr-lan-active .dmr-share-detail small strong{color:var(--dsw-alias-state-error-primary)}.dmr-danger{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,var(--dsw-alias-border-l2));color:var(--dsw-alias-state-error-primary)}.dmr-danger:hover{border-color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}
|
|
10101
|
+
/* \u62BD\u5C49\u628A\u624B\uFF1A\u8D34\u5DE6\u8FB9\u7F18\uFF08\u540C\u8DA3\u5473\u6E38\u620F\u6D6E\u6807\u7684\u8FB9\u7F18\u5438\u9644\u8BED\u8A00\uFF09\uFF0Ctop \u7531 place() \u52A8\u6001\u8BBE\u5B9A\uFF1B
|
|
10102
|
+
* z \u5E8F 70 < \u906E\u7F69 75 < \u4FA7\u680F 80 < overlayLayer 90\uFF0C\u9762\u677F/\u5F39\u7A97\u6253\u5F00\u65F6\u81EA\u7136\u88AB\u76D6\u3002 */
|
|
10103
|
+
@media (max-width:700px){.dsh-mobile-drawer-btn{position:fixed;left:0;top:38vh;z-index:70;box-sizing:border-box;width:30px;height:48px;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid var(--dsw-alias-border-l1,rgba(127,139,161,.35));border-left:none;border-radius:0 12px 12px 0;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#1c2230) 88%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:var(--dsw-alias-label-primary,#e6eaf2);box-shadow:4px 0 18px rgba(0,0,0,.28);cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent}.dsh-mobile-drawer-btn:active{transform:scale(.94)}.dsh-mobile-drawer-btn svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.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}}`,
|
|
9993
10104
|
View: MobileRelayView,
|
|
9994
|
-
HomeStat: MobileRelayHomeStat
|
|
10105
|
+
HomeStat: MobileRelayHomeStat,
|
|
10106
|
+
Overlay: MobileRelayOverlay
|
|
9995
10107
|
};
|
|
9996
10108
|
|
|
9997
10109
|
// src/client.jsx
|
|
9998
|
-
var DOCK_VERSION = "0.9.
|
|
10110
|
+
var DOCK_VERSION = "0.9.5";
|
|
9999
10111
|
var BUILTIN_FEATURES = [feature, feature2, feature3, feature4, feature5, feature6, feature7, feature8];
|
|
10000
10112
|
var PLANNED_FEATURES = [];
|
|
10001
10113
|
var PLANNED_NOTES = {};
|
package/features/balance/host.js
CHANGED
|
@@ -475,7 +475,7 @@ export const feature = {
|
|
|
475
475
|
id: 'balance',
|
|
476
476
|
name: '模型余额',
|
|
477
477
|
description: '拉取所有模型 Provider 账户余额并展示',
|
|
478
|
-
defaultEnabled:
|
|
478
|
+
defaultEnabled: false,
|
|
479
479
|
setup(ctx) {
|
|
480
480
|
const webServer = ctx.get('webServer')
|
|
481
481
|
if (webServer === undefined) throw new Error('webServer 服务不可用,无法提供余额查询路由')
|
package/features/games/view.jsx
CHANGED
|
@@ -631,7 +631,7 @@ export const feature = {
|
|
|
631
631
|
order: 135,
|
|
632
632
|
accent: "#a78bfa",
|
|
633
633
|
description: "任务等待时可玩的十款小游戏:五子棋、中国象棋(你 vs AI)、俄罗斯方块、推箱子、贪吃蛇、打砖块、极速赛车、坦克大战、星际躲避、反应堆点亮。浮动窗口运行(可拖动、可调大小、可最大化最小化,不遮挡会话),屏幕侧边磁吸快捷入口贴在左侧边栏右缘(不挡工作区目录),可上下挪位、左右换边;均支持键盘",
|
|
634
|
-
defaultEnabled:
|
|
634
|
+
defaultEnabled: false,
|
|
635
635
|
css,
|
|
636
636
|
View: GamesView,
|
|
637
637
|
HomeStat: GamesHomeStat,
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
// 统一经本网关访问:未登录一律跳转登录页(账号 + 密码),登录成功发放 HttpOnly
|
|
5
5
|
// 会话 Cookie(7 天滑动过期),支持退出登录;登录失败按来源 IP 限速。上游即 DSH
|
|
6
6
|
// 主实例:绑定回环时把 Host/Origin 改写为回环(回环恒被信任),已绑 0.0.0.0 时
|
|
7
|
-
//
|
|
8
|
-
//
|
|
7
|
+
// 透传以走局域网信任派生。宿主可注入 mintUpstreamCookie:服务端兑换 dsh web 的
|
|
8
|
+
// 启动令牌会话并在代理时自动附带(含 WebSocket 升级),远程浏览器只需账号密码。
|
|
9
|
+
// HTML 响应注入远程标记(window.__DSH_REMOTE__,供面板显示退出按钮)与
|
|
10
|
+
// crypto.randomUUID 兜底脚本引用。
|
|
9
11
|
import { createServer, request as httpRequest } from 'node:http'
|
|
10
12
|
import { networkInterfaces } from 'node:os'
|
|
11
13
|
import { randomBytes } from 'node:crypto'
|
|
@@ -153,7 +155,7 @@ function writeUpgradeHead(socket, response) {
|
|
|
153
155
|
* (读 settings 中的加盐哈希做常量时间比对);会话 Cookie 7 天滑动过期;
|
|
154
156
|
* revokeAllSessions() 在账号密码变更时作废所有已登录设备。
|
|
155
157
|
*/
|
|
156
|
-
export function startProtectedLanGateway({ port, upstreamHost = '127.0.0.1', upstreamPort, spoofLoopback = false, verifyLogin, sessionTtlMs = SESSION_TTL_MS }) {
|
|
158
|
+
export function startProtectedLanGateway({ port, upstreamHost = '127.0.0.1', upstreamPort, spoofLoopback = false, verifyLogin, mintUpstreamCookie, sessionTtlMs = SESSION_TTL_MS }) {
|
|
157
159
|
return new Promise((resolve, reject) => {
|
|
158
160
|
const sessions = new Map() // cookie secret -> { expiresAt, seenAt }
|
|
159
161
|
const upgradedSockets = new Map() // client socket -> { cookie, upstreamSocket }
|
|
@@ -161,6 +163,46 @@ export function startProtectedLanGateway({ port, upstreamHost = '127.0.0.1', ups
|
|
|
161
163
|
const patchedBundles = new Map() // 上游 client.js URL(含 rev)-> 改写后字节
|
|
162
164
|
const loopbackAuthority = '127.0.0.1:' + upstreamPort
|
|
163
165
|
|
|
166
|
+
// 上游会话(dsh web 自己的启动令牌认证):mintUpstreamCookie 由宿主注入,
|
|
167
|
+
// 服务端完成 /?token= 兑换并持有上游会话 Cookie,代理请求时自动附带——远程
|
|
168
|
+
// 浏览器只需网关账号登录,无需再手动访问令牌地址。仅在改写回环(上游仅绑
|
|
169
|
+
// 回环、Cookie 与该 authority 绑定)时附带;兑换失败退回官方行为。
|
|
170
|
+
let upstreamCookie = null
|
|
171
|
+
let minting = null
|
|
172
|
+
let lastMintAt = 0
|
|
173
|
+
async function ensureUpstreamCookie() {
|
|
174
|
+
if (!spoofLoopback || typeof mintUpstreamCookie !== 'function') return null
|
|
175
|
+
if (upstreamCookie) return upstreamCookie
|
|
176
|
+
if (minting) return minting
|
|
177
|
+
if (now() - lastMintAt < 5000) return null
|
|
178
|
+
lastMintAt = now()
|
|
179
|
+
minting = Promise.resolve().then(mintUpstreamCookie).then((cookie) => {
|
|
180
|
+
minting = null
|
|
181
|
+
upstreamCookie = typeof cookie === 'string' && cookie.includes('=') ? cookie : null
|
|
182
|
+
return upstreamCookie
|
|
183
|
+
}).catch(() => { minting = null; return null })
|
|
184
|
+
return minting
|
|
185
|
+
}
|
|
186
|
+
function dropUpstreamCookie() {
|
|
187
|
+
upstreamCookie = null
|
|
188
|
+
lastMintAt = 0
|
|
189
|
+
}
|
|
190
|
+
// 同名 Cookie 浏览器侧可能持有旧值(换 secret 后失效),附加以我们的为准
|
|
191
|
+
async function upstreamRequestHeaders(req) {
|
|
192
|
+
const headers = proxyHeaders(req)
|
|
193
|
+
const cookie = await ensureUpstreamCookie()
|
|
194
|
+
if (cookie) {
|
|
195
|
+
const name = cookie.slice(0, cookie.indexOf('=')).trim()
|
|
196
|
+
const own = String(headers.cookie || '')
|
|
197
|
+
.split(';')
|
|
198
|
+
.map((part) => part.trim())
|
|
199
|
+
.filter((part) => part && part.slice(0, part.indexOf('=')).trim() !== name)
|
|
200
|
+
.join('; ')
|
|
201
|
+
headers.cookie = own ? own + '; ' + cookie : cookie
|
|
202
|
+
}
|
|
203
|
+
return headers
|
|
204
|
+
}
|
|
205
|
+
|
|
164
206
|
function purge() {
|
|
165
207
|
const stamp = now()
|
|
166
208
|
const expired = []
|
|
@@ -220,72 +262,80 @@ export function startProtectedLanGateway({ port, upstreamHost = '127.0.0.1', ups
|
|
|
220
262
|
if (wantsHtml) delete next['accept-encoding']
|
|
221
263
|
return next
|
|
222
264
|
}
|
|
223
|
-
function proxyHttp(req, res) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const limit = isConnectionBundle ? MAX_ASSET_BYTES : MAX_HTML_BYTES
|
|
252
|
-
upstreamRes.on('data', (chunk) => {
|
|
253
|
-
size += chunk.length
|
|
254
|
-
if (size <= limit) chunks.push(chunk)
|
|
255
|
-
})
|
|
256
|
-
upstreamRes.on('end', () => {
|
|
257
|
-
if (size > limit) {
|
|
258
|
-
if (!res.headersSent) sendJson(res, 502, { ok: false, error: 'DSH 页面过大,无法注入手机兼容层。' })
|
|
265
|
+
async function proxyHttp(req, res) {
|
|
266
|
+
let upstream
|
|
267
|
+
try {
|
|
268
|
+
upstream = httpRequest({
|
|
269
|
+
hostname: upstreamHost, port: upstreamPort, method: req.method, path: req.url,
|
|
270
|
+
headers: await upstreamRequestHeaders(req),
|
|
271
|
+
}, (upstreamRes) => {
|
|
272
|
+
// 上游 401 说明持有的会话已失效(secret 轮换/过期),丢弃后下次请求重兑
|
|
273
|
+
if (upstreamRes.statusCode === 401) dropUpstreamCookie()
|
|
274
|
+
// 浏览器侧中途断开时把上游响应读完丢弃(而非 destroy)——宿主对半路 RST
|
|
275
|
+
// 敏感,读完让上游 socket 干净收尾复用。缓冲分支天然读尽,无需处理。
|
|
276
|
+
if (upstreamRes) upstreamRes.on('error', () => res.destroy())
|
|
277
|
+
const contentType = String(upstreamRes.headers['content-type'] || '').toLowerCase()
|
|
278
|
+
// 连接客户端 bundle:设置面在远程浏览器被客户端自我判定关死(见文件头注释),
|
|
279
|
+
// 做定点改写;命中缓存直接回,改不动(上游升级)则原样透传。
|
|
280
|
+
const isConnectionBundle = req.method === 'GET' && String(req.url || '').startsWith(CONNECTION_BUNDLE_PREFIX)
|
|
281
|
+
&& contentType.includes('javascript')
|
|
282
|
+
if (isConnectionBundle && patchedBundles.has(req.url)) {
|
|
283
|
+
const headers = { ...upstreamRes.headers, 'content-length': String(patchedBundles.get(req.url).length) }
|
|
284
|
+
delete headers['transfer-encoding']
|
|
285
|
+
res.writeHead(upstreamRes.statusCode || 200, headers)
|
|
286
|
+
return res.end(patchedBundles.get(req.url))
|
|
287
|
+
}
|
|
288
|
+
if (!contentType.includes('text/html') && !isConnectionBundle) {
|
|
289
|
+
// 直通分支:浏览器断开就停止写、改为耗尽上游响应(读完丢弃)。
|
|
290
|
+
res.on('close', () => { if (!upstreamRes.complete) upstreamRes.resume() })
|
|
291
|
+
res.writeHead(upstreamRes.statusCode || 502, upstreamRes.headers)
|
|
292
|
+
upstreamRes.pipe(res)
|
|
259
293
|
return
|
|
260
294
|
}
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
295
|
+
const chunks = []
|
|
296
|
+
let size = 0
|
|
297
|
+
const limit = isConnectionBundle ? MAX_ASSET_BYTES : MAX_HTML_BYTES
|
|
298
|
+
upstreamRes.on('data', (chunk) => {
|
|
299
|
+
size += chunk.length
|
|
300
|
+
if (size <= limit) chunks.push(chunk)
|
|
301
|
+
})
|
|
302
|
+
upstreamRes.on('end', () => {
|
|
303
|
+
if (size > limit) {
|
|
304
|
+
if (!res.headersSent) sendJson(res, 502, { ok: false, error: 'DSH 页面过大,无法注入手机兼容层。' })
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
const headers = { ...upstreamRes.headers }
|
|
308
|
+
delete headers['content-length']
|
|
309
|
+
delete headers['content-encoding']
|
|
310
|
+
delete headers['transfer-encoding']
|
|
311
|
+
headers['cache-control'] = 'no-store'
|
|
312
|
+
let body = Buffer.concat(chunks)
|
|
313
|
+
if (isConnectionBundle) {
|
|
314
|
+
const text = body.toString('utf8')
|
|
315
|
+
const at = text.indexOf(CONNECTION_ISLOOPBACK_SNIPPET)
|
|
316
|
+
if (at >= 0) {
|
|
317
|
+
body = Buffer.from(text.slice(0, at) + CONNECTION_ISLOOPBACK_PATCHED + text.slice(at + CONNECTION_ISLOOPBACK_SNIPPET.length), 'utf8')
|
|
318
|
+
if (patchedBundles.size >= PATCH_CACHE_LIMIT) patchedBundles.clear()
|
|
319
|
+
patchedBundles.set(req.url, body)
|
|
320
|
+
}
|
|
321
|
+
headers['content-length'] = String(body.length)
|
|
322
|
+
res.writeHead(upstreamRes.statusCode || 502, headers)
|
|
323
|
+
return res.end(body)
|
|
274
324
|
}
|
|
275
|
-
headers['content-length'] = String(body.length)
|
|
276
325
|
res.writeHead(upstreamRes.statusCode || 502, headers)
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
res.writeHead(upstreamRes.statusCode || 502, headers)
|
|
280
|
-
res.end(injectMobileCompat(body.toString('utf8')))
|
|
326
|
+
res.end(injectMobileCompat(body.toString('utf8')))
|
|
327
|
+
})
|
|
281
328
|
})
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
329
|
+
upstream.on('error', (error) => {
|
|
330
|
+
if (!res.headersSent) sendJson(res, 502, { ok: false, error: 'DSH 主服务连接失败:' + error.message })
|
|
331
|
+
else res.destroy(error)
|
|
332
|
+
})
|
|
333
|
+
req.on('error', () => upstream.destroy())
|
|
334
|
+
req.pipe(upstream)
|
|
335
|
+
} catch (error) {
|
|
336
|
+
if (!res.headersSent) sendJson(res, 502, { ok: false, error: 'DSH 主服务连接失败:' + (error && error.message || String(error)) })
|
|
285
337
|
else res.destroy(error)
|
|
286
|
-
}
|
|
287
|
-
req.on('error', () => upstream.destroy())
|
|
288
|
-
req.pipe(upstream)
|
|
338
|
+
}
|
|
289
339
|
}
|
|
290
340
|
|
|
291
341
|
const server = createServer(async (req, res) => {
|
|
@@ -357,17 +407,23 @@ export function startProtectedLanGateway({ port, upstreamHost = '127.0.0.1', ups
|
|
|
357
407
|
return proxyHttp(req, res)
|
|
358
408
|
})
|
|
359
409
|
|
|
360
|
-
server.on('upgrade', (req, socket, head) => {
|
|
410
|
+
server.on('upgrade', async (req, socket, head) => {
|
|
361
411
|
const authorized = authorize(req)
|
|
362
412
|
if (!authorized) { socket.end('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n'); return }
|
|
363
413
|
const connection = { cookie: authorized.cookie, upstreamSocket: null }
|
|
364
414
|
upgradedSockets.set(socket, connection)
|
|
365
415
|
const forgetSocket = () => upgradedSockets.delete(socket)
|
|
366
416
|
socket.once('close', forgetSocket)
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
417
|
+
let upstream
|
|
418
|
+
try {
|
|
419
|
+
upstream = httpRequest({
|
|
420
|
+
hostname: upstreamHost, port: upstreamPort, method: req.method, path: req.url,
|
|
421
|
+
headers: await upstreamRequestHeaders(req),
|
|
422
|
+
})
|
|
423
|
+
} catch {
|
|
424
|
+
socket.destroy()
|
|
425
|
+
return
|
|
426
|
+
}
|
|
371
427
|
upstream.on('upgrade', (response, upstreamSocket, upstreamHead) => {
|
|
372
428
|
connection.upstreamSocket = upstreamSocket
|
|
373
429
|
writeUpgradeHead(socket, response)
|
|
@@ -378,6 +434,7 @@ export function startProtectedLanGateway({ port, upstreamHost = '127.0.0.1', ups
|
|
|
378
434
|
socket.pipe(upstreamSocket).pipe(socket)
|
|
379
435
|
})
|
|
380
436
|
upstream.on('response', (response) => {
|
|
437
|
+
if (response.statusCode === 401) dropUpstreamCookie()
|
|
381
438
|
socket.end(`HTTP/1.1 ${response.statusCode || 502} ${response.statusMessage || 'Bad Gateway'}\r\nConnection: close\r\n\r\n`)
|
|
382
439
|
})
|
|
383
440
|
upstream.on('error', () => socket.destroy())
|
|
@@ -129,6 +129,9 @@ const LAN_COMPAT_MARKER = 'data-dsh-lan-compat'
|
|
|
129
129
|
// 1) 设置弹窗官方只有"左导航+右内容"两栏,窄屏内容栏被压到不足百像素(一字一行);
|
|
130
130
|
// 改纵向堆叠:导航横排在上、内容占满。
|
|
131
131
|
// 2) 侧边栏展开是 grid 栅格挤占会话区(窄屏会话区只剩 ~110px);改为浮层覆盖。
|
|
132
|
+
// 3) 侧栏栅格归零后,原生「打开侧边栏」按钮随窄轨一起被裁掉——补贴左边缘的抽屉把手
|
|
133
|
+
// (.dsh-mobile-drawer-btn,行为脚本创建,点击转发原生 toggle,位置在趣味游戏
|
|
134
|
+
// 浮标正上方、兜底左侧中部)与抽屉遮罩(.dsh-mobile-scrim,点击即收起,阻断滚动穿透)。
|
|
132
135
|
// 类名用语义后缀匹配(哈希前缀随 DSH 版本会变,后缀稳定);DSH 升级改了结构时
|
|
133
136
|
// 选择器自然失效,不影响其他功能。
|
|
134
137
|
const MOBILE_LAYOUT_CSS = [
|
|
@@ -144,13 +147,100 @@ const MOBILE_LAYOUT_CSS = [
|
|
|
144
147
|
' [class$="frame"]:not([data-sidebar-collapsed]) [class*="sidebarCol"]{position:fixed!important;top:0!important;bottom:0!important;left:0!important;width:min(85vw,320px)!important;z-index:80!important;box-shadow:0 12px 48px rgba(0,0,0,.45)}',
|
|
145
148
|
' [class$="frame"]:not([data-details-collapsed="true"]) [class*="detailsCol"]{position:fixed!important;top:0!important;bottom:0!important;right:0!important;left:auto!important;width:min(85vw,320px)!important;z-index:85!important;box-shadow:-12px 0 48px rgba(0,0,0,.45)}',
|
|
146
149
|
' [class*="overlayLayer"]{z-index:90!important}',
|
|
150
|
+
// 抽屉把手:贴左边缘(同趣味游戏浮标的边缘吸附语言),top 由 place() 动态设定
|
|
151
|
+
// (趣味游戏浮标正上方,兜底左侧中部);z 序 70 < 遮罩 75 < 侧栏 80 < overlayLayer 90,
|
|
152
|
+
// 面板/弹窗打开时自然被盖,无需额外隐藏逻辑。
|
|
153
|
+
' .dsh-mobile-drawer-btn{position:fixed;left:0;top:38vh;z-index:70;box-sizing:border-box;width:30px;height:48px;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid var(--dsw-alias-border-l1,rgba(127,139,161,.35));border-left:none;border-radius:0 12px 12px 0;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#1c2230) 88%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:var(--dsw-alias-label-primary,#e6eaf2);box-shadow:4px 0 18px rgba(0,0,0,.28);cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent}',
|
|
154
|
+
' .dsh-mobile-drawer-btn:active{transform:scale(.94)}',
|
|
155
|
+
' .dsh-mobile-drawer-btn svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}',
|
|
156
|
+
' .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}',
|
|
147
157
|
'}',
|
|
148
158
|
].join('\n')
|
|
149
159
|
const MOBILE_LAYOUT_MARKER = 'data-dsh-mobile-layout'
|
|
150
160
|
// 窄屏行为补丁:侧边栏浮层化后,原生的"保持展开"会一直挡住半屏——
|
|
151
161
|
// 点会话行/新会话后自动收起;点侧栏外的页面区域也收起(浮层语义)。
|
|
152
162
|
// 侧栏内的其他点击(工作区折叠、搜索、功能坞入口)保持原生行为不收。
|
|
153
|
-
|
|
163
|
+
// 另创建抽屉按钮(窄屏 + 侧栏收起时才显示,点击转发原生「打开侧边栏」toggle;
|
|
164
|
+
// 侧栏展开或设置弹窗打开时自动隐藏)与抽屉遮罩(点击经全局捕获 handler 收起,
|
|
165
|
+
// 单一路径防止同一事件两次 toggle)。注入点在 <head> 后,body 尚未解析,
|
|
166
|
+
// DOM 创建一律推迟到 DOMContentLoaded;显隐同步走 MutationObserver(属性翻转即时)
|
|
167
|
+
// + 低速轮询(覆盖路由重渲等一切边角)。
|
|
168
|
+
const MOBILE_BEHAVIOR_JS = [
|
|
169
|
+
';(function(){',
|
|
170
|
+
'var mq=window.matchMedia?window.matchMedia("(max-width:700px)"):null;',
|
|
171
|
+
'if(!mq)return;',
|
|
172
|
+
// 与 dsh-dock 客户端 Overlay 版(features/mobile-relay/view.jsx,刷新即生效)互斥:
|
|
173
|
+
// 本脚本在 <head> 先执行,存在即接管;客户端版看到旗标自动让位。
|
|
174
|
+
'if(window.__dshDockMobileDrawer)return;',
|
|
175
|
+
'window.__dshDockMobileDrawer="host";',
|
|
176
|
+
'function narrow(){return mq.matches}',
|
|
177
|
+
'function frameEl(){return document.querySelector(\'[class$="frame"]\')}',
|
|
178
|
+
'function sidebarCollapsed(){var f=frameEl();return !!f&&f.hasAttribute("data-sidebar-collapsed")}',
|
|
179
|
+
'function dialogOpen(){return !!document.querySelector(\'[role="dialog"][class*="panel"]\')}',
|
|
180
|
+
'function collapse(){var b=document.querySelector(\'button[aria-label="收起侧边栏"],button[aria-label="Collapse sidebar"]\');if(b)b.click()}',
|
|
181
|
+
'function expand(){var b=document.querySelector(\'button[aria-label="打开侧边栏"],button[aria-label="Open sidebar"]\');if(b)b.click()}',
|
|
182
|
+
'function closeDetails(){var d=document.querySelector(\'[class*="detailsCol"]\');if(!d||!d.getBoundingClientRect().width)return;var c=d.querySelector(\'button[aria-label="关闭详情"]\');if(c)c.click()}',
|
|
183
|
+
'var fab=null,scrim=null;',
|
|
184
|
+
'function ensureChrome(){',
|
|
185
|
+
' if(!fab){fab=document.createElement("button");fab.type="button";fab.className="dsh-mobile-drawer-btn";fab.setAttribute("aria-label","打开会话列表");fab.innerHTML=\'<svg viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h10"/></svg>\';fab.addEventListener("click",function(){expand()});document.body.appendChild(fab)}',
|
|
186
|
+
' if(!scrim){scrim=document.createElement("div");scrim.className="dsh-mobile-scrim";document.body.appendChild(scrim)}',
|
|
187
|
+
'}',
|
|
188
|
+
// 把手定位:贴着趣味游戏浮标(.dgfab,可拖拽)正上方;浮标太靠上时改放它下面
|
|
189
|
+
// (避开顶栏标题),被拖走/隐藏/不存在时兜底左侧中部(38% 视高)。
|
|
190
|
+
'function place(){',
|
|
191
|
+
' if(!fab)return;',
|
|
192
|
+
' var vh=window.innerHeight,top=null;',
|
|
193
|
+
' var g=document.querySelector(".dgfab");',
|
|
194
|
+
' if(g&&!g.classList.contains("dgfab-hide")&&g.getBoundingClientRect){',
|
|
195
|
+
' var r=g.getBoundingClientRect();',
|
|
196
|
+
' if(r&&r.height>0&&r.top>0){',
|
|
197
|
+
' top=r.top-fab.offsetHeight-8;',
|
|
198
|
+
' if(top<64)top=r.bottom+8;',
|
|
199
|
+
' if(top+fab.offsetHeight>vh-16)top=null;',
|
|
200
|
+
' }',
|
|
201
|
+
' }',
|
|
202
|
+
' if(top==null)top=Math.round(vh*.38);',
|
|
203
|
+
' fab.style.top=top+"px";',
|
|
204
|
+
'}',
|
|
205
|
+
'function syncChrome(){',
|
|
206
|
+
' if(!fab||!scrim)return;',
|
|
207
|
+
' var on=narrow()&&!!frameEl()&&!dialogOpen();',
|
|
208
|
+
' var col=sidebarCollapsed();',
|
|
209
|
+
' var showFab=on&&col;',
|
|
210
|
+
' fab.style.display=showFab?"":"none";',
|
|
211
|
+
' if(showFab)place();',
|
|
212
|
+
' scrim.style.display=on&&!col?"":"none";',
|
|
213
|
+
'}',
|
|
214
|
+
'function boot(){',
|
|
215
|
+
' ensureChrome();syncChrome();',
|
|
216
|
+
' if(typeof MutationObserver!=="undefined"){new MutationObserver(function(){syncChrome()}).observe(document.documentElement,{attributes:true,attributeFilter:["data-sidebar-collapsed"],subtree:true})}',
|
|
217
|
+
' setInterval(syncChrome,1500);',
|
|
218
|
+
' var onMq=function(){syncChrome()};',
|
|
219
|
+
' if(mq.addEventListener)mq.addEventListener("change",onMq);else if(mq.addListener)mq.addListener(onMq);',
|
|
220
|
+
' window.addEventListener("resize",place);',
|
|
221
|
+
'}',
|
|
222
|
+
'if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",boot);else boot();',
|
|
223
|
+
'document.addEventListener("click",function(e){',
|
|
224
|
+
' if(!narrow())return;',
|
|
225
|
+
' var t=e.target;',
|
|
226
|
+
' if(!t||!t.closest)return;',
|
|
227
|
+
' if(t.closest(".dsh-mobile-drawer-btn"))return;',
|
|
228
|
+
' var expanded=!!document.querySelector(\'button[aria-label="收起侧边栏"],button[aria-label="Collapse sidebar"]\');',
|
|
229
|
+
' var col=document.querySelector(\'[class*="sidebarCol"]\');',
|
|
230
|
+
' var inSidebar=col&&col.contains(t);',
|
|
231
|
+
' if(!expanded&&!inSidebar)return;',
|
|
232
|
+
' if(inSidebar){',
|
|
233
|
+
' var row=t.closest(\'[role="treeitem"]\');',
|
|
234
|
+
' var leaf=row&&!row.querySelector(\'[role="treeitem"]\')&&row.closest(\'[role="tree"]\');',
|
|
235
|
+
' var fresh=t.closest(\'[class*="newSession"]\');',
|
|
236
|
+
' if(leaf||fresh)setTimeout(function(){collapse();closeDetails()},300);',
|
|
237
|
+
' return',
|
|
238
|
+
' }',
|
|
239
|
+
' if(t.closest(\'[role="dialog"],[class*="dockm"],[class*="dgfab"],[class*="dgwin"],[class*="dgame"],[class*="detailsCol"]\'))return;',
|
|
240
|
+
' collapse()',
|
|
241
|
+
'},true)',
|
|
242
|
+
'})();',
|
|
243
|
+
].join('\n')
|
|
154
244
|
const MOBILE_BEHAVIOR_MARKER = 'data-dsh-mobile-behave'
|
|
155
245
|
/** 把兼容脚本、移动端排版样式与行为脚本内联注入 index.html 的 <head> 之后(幂等)。 */
|
|
156
246
|
function injectLanCompat(html) {
|
|
@@ -174,6 +264,8 @@ export const feature = {
|
|
|
174
264
|
id: 'mobile-relay',
|
|
175
265
|
name: '远程访问',
|
|
176
266
|
description: '账号密码登录的远程入口:局域网/虚拟网设备访问同一个 DSH,任务进度实时一致',
|
|
267
|
+
// 宿主半部必须随插件加载(网关路由/账号服务都在这里),techfunway-dsh 等部署
|
|
268
|
+
// 完全依赖它——保持默认开启;面板开关只影响浏览器侧 UI(双向同步未打通)
|
|
177
269
|
defaultEnabled: true,
|
|
178
270
|
setup(ctx) {
|
|
179
271
|
const disposers = []
|
|
@@ -282,6 +374,26 @@ export const feature = {
|
|
|
282
374
|
return sameText(username, auth.username) && sameText(hashPassword(auth.salt, password), auth.passwordHash)
|
|
283
375
|
}
|
|
284
376
|
|
|
377
|
+
// 上游会话自动兑换:dsh web 自身还有一层启动令牌认证(首次访问 /?token= 换
|
|
378
|
+
// 30 天会话 Cookie,绑定回环 authority)。网关与主实例同进程,直接取
|
|
379
|
+
// connection.authenticatedUrl 生成的带令牌地址,服务端完成兑换并返回 Cookie
|
|
380
|
+
// 对,由网关在代理时自动附带——远程浏览器只需网关账号登录,无需再手动访问
|
|
381
|
+
// 令牌地址。老版本 dsh 没有 connection 服务时返回空,网关退回官方行为。
|
|
382
|
+
async function mintUpstreamSession(mainPort) {
|
|
383
|
+
try {
|
|
384
|
+
const connection = ctx.get('connection')
|
|
385
|
+
if (!connection || typeof connection.authenticatedUrl !== 'function') return null
|
|
386
|
+
const url = connection.authenticatedUrl(`http://127.0.0.1:${mainPort}`)
|
|
387
|
+
const res = await fetch(url, { redirect: 'manual' })
|
|
388
|
+
const cookies = typeof res.headers.getSetCookie === 'function' ? res.headers.getSetCookie() : [res.headers.get('set-cookie')].filter(Boolean)
|
|
389
|
+
for (const raw of cookies) {
|
|
390
|
+
const pair = String(raw).split(';')[0].trim()
|
|
391
|
+
if (pair.includes('=')) return pair
|
|
392
|
+
}
|
|
393
|
+
return null
|
|
394
|
+
} catch { return null }
|
|
395
|
+
}
|
|
396
|
+
|
|
285
397
|
/** 远程访问状态:网关、账号、浏览模式补丁、旧服务器模式残留。 */
|
|
286
398
|
function lanStatus(webServer) {
|
|
287
399
|
const file = serverPatchFile()
|
|
@@ -314,7 +426,7 @@ export const feature = {
|
|
|
314
426
|
}
|
|
315
427
|
|
|
316
428
|
/** 远程访问网关:上游即主实例;主实例保持仅本机,远程一律经账号登录进入。 */
|
|
317
|
-
async function ensureGateway(webServer, requestedPort) {
|
|
429
|
+
async function ensureGateway(webServer, requestedPort, mintUpstreamCookie) {
|
|
318
430
|
// 默认端口跟主实例走(主端口+1):网关和主服务是同一台机器上的两个监听,
|
|
319
431
|
// 结构上不能同端口;跟随主端口让"哪个口是哪个"一目了然。
|
|
320
432
|
const port = Number(requestedPort === undefined || requestedPort === null || requestedPort === ''
|
|
@@ -345,6 +457,7 @@ export const feature = {
|
|
|
345
457
|
upstreamPort: webServer.port,
|
|
346
458
|
spoofLoopback: true,
|
|
347
459
|
verifyLogin,
|
|
460
|
+
mintUpstreamCookie: typeof mintUpstreamCookie === 'function' ? mintUpstreamCookie : undefined,
|
|
348
461
|
})
|
|
349
462
|
.then((started) => { gateway = started; gatewayStarting = null; return started })
|
|
350
463
|
.catch((error) => { gatewayStarting = null; throw error })
|
|
@@ -421,7 +534,7 @@ export const feature = {
|
|
|
421
534
|
const list = readPatchList(file) || []
|
|
422
535
|
const changed = !serverPatchApplied(list) || legacyWebserverRowPresent(list)
|
|
423
536
|
writePatchList(file, upsertRemotePatches(list))
|
|
424
|
-
await ensureGateway(wsCtx.webServer, payload && payload.port)
|
|
537
|
+
await ensureGateway(wsCtx.webServer, payload && payload.port, () => mintUpstreamSession(wsCtx.webServer.port))
|
|
425
538
|
return sendJson(res, 200, { ok: true, data: { ...lanStatus(wsCtx.webServer), needsRestart: changed } })
|
|
426
539
|
}
|
|
427
540
|
if (method === 'lan/stop') {
|
|
@@ -155,6 +155,129 @@ export function MobileRelayView() {
|
|
|
155
155
|
return <RemoteCard/>
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
/**
|
|
159
|
+
* 手机端会话抽屉(窄屏 ≤700px):悬浮按钮拉出侧边栏 + 遮罩点击收起。
|
|
160
|
+
*
|
|
161
|
+
* 直接操作 document.body 下的 DOM(渲染返回 null)——若作为 overlayLayer 的子元素,
|
|
162
|
+
* 会落入 overlayLayer(z 90)的堆叠上下文,遮罩压不住侧栏抽屉(z 80)、按钮也盖不住内容;
|
|
163
|
+
* 挂到 body 下 z 序才是设计值(按钮 70 < 遮罩 75 < 侧栏 80 < overlayLayer 90)。
|
|
164
|
+
*
|
|
165
|
+
* 与宿主 tapIndex 注入版(features/mobile-relay/host.js,重启 dsh web 后才生效)互斥:
|
|
166
|
+
* window.__dshDockMobileDrawer 先到先得——宿主脚本在 <head> 先跑,重启后总是宿主版接管,
|
|
167
|
+
* 本组件自动让位;本组件的价值是 client bundle 按内容 hash 服务、刷新页面即生效,无需重启。
|
|
168
|
+
* 与旧版宿主行为脚本(无抽屉按钮、只有自动收起)并存是安全的:收起链路同一事件内
|
|
169
|
+
* 第二次 collapse() 因 aria-label 已翻转而自然空转。
|
|
170
|
+
*/
|
|
171
|
+
export function MobileRelayOverlay() {
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') return undefined
|
|
174
|
+
if (window.__dshDockMobileDrawer) return undefined
|
|
175
|
+
const mq = window.matchMedia ? window.matchMedia('(max-width:700px)') : null
|
|
176
|
+
if (!mq) return undefined
|
|
177
|
+
window.__dshDockMobileDrawer = 'client'
|
|
178
|
+
|
|
179
|
+
const narrow = () => mq.matches
|
|
180
|
+
const frameEl = () => document.querySelector('[class$="frame"]')
|
|
181
|
+
const sidebarCollapsed = () => { const f = frameEl(); return !!f && f.hasAttribute('data-sidebar-collapsed') }
|
|
182
|
+
const dialogOpen = () => !!document.querySelector('[role="dialog"][class*="panel"]')
|
|
183
|
+
const collapse = () => { const b = document.querySelector('button[aria-label="收起侧边栏"],button[aria-label="Collapse sidebar"]'); if (b) b.click() }
|
|
184
|
+
const expand = () => { const b = document.querySelector('button[aria-label="打开侧边栏"],button[aria-label="Open sidebar"]'); if (b) b.click() }
|
|
185
|
+
const closeDetails = () => {
|
|
186
|
+
const d = document.querySelector('[class*="detailsCol"]')
|
|
187
|
+
if (!d || !d.getBoundingClientRect().width) return
|
|
188
|
+
const c = d.querySelector('button[aria-label="关闭详情"]')
|
|
189
|
+
if (c) c.click()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 抽屉按钮:窄屏 + 侧栏收起 + 无设置弹窗时才显示(见 sync)。
|
|
193
|
+
// 视觉是贴左边缘的「抽屉把手」(与趣味游戏浮标 dgfab 同款边缘吸附语言),
|
|
194
|
+
// 位置动态放在趣味游戏浮标正上方(place()),游戏浮标不在时兜底左侧中部。
|
|
195
|
+
const fab = document.createElement('button')
|
|
196
|
+
fab.type = 'button'
|
|
197
|
+
fab.className = 'dsh-mobile-drawer-btn'
|
|
198
|
+
fab.setAttribute('aria-label', '打开会话列表')
|
|
199
|
+
fab.innerHTML = '<svg viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h10"/></svg>'
|
|
200
|
+
fab.addEventListener('click', expand)
|
|
201
|
+
const scrim = document.createElement('div')
|
|
202
|
+
scrim.className = 'dsh-mobile-scrim'
|
|
203
|
+
document.body.appendChild(fab)
|
|
204
|
+
document.body.appendChild(scrim)
|
|
205
|
+
|
|
206
|
+
// 把手定位:贴着趣味游戏浮标(.dgfab,可拖拽、位置内联在 style 上)正上方;
|
|
207
|
+
// 浮标被拖到顶/隐藏/不存在时兜底左侧中部(38% 视高)。sync 每 1.5s 复测,
|
|
208
|
+
// 拖拽浮标后把手最迟一个周期跟上;resize 立即复测。
|
|
209
|
+
const place = () => {
|
|
210
|
+
const vh = window.innerHeight
|
|
211
|
+
let top = null
|
|
212
|
+
const g = document.querySelector('.dgfab')
|
|
213
|
+
if (g && !g.classList.contains('dgfab-hide') && g.getBoundingClientRect) {
|
|
214
|
+
const r = g.getBoundingClientRect()
|
|
215
|
+
if (r && r.height > 0 && r.top > 0) {
|
|
216
|
+
top = r.top - fab.offsetHeight - 8
|
|
217
|
+
if (top < 64) top = r.bottom + 8 // 浮标太靠上时改放它下面,避开顶栏标题
|
|
218
|
+
if (top + fab.offsetHeight > vh - 16) top = null
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (top == null) top = Math.round(vh * 0.38)
|
|
222
|
+
fab.style.top = top + 'px'
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const sync = () => {
|
|
226
|
+
const on = narrow() && !!frameEl() && !dialogOpen()
|
|
227
|
+
const collapsed = sidebarCollapsed()
|
|
228
|
+
const showFab = on && collapsed
|
|
229
|
+
fab.style.display = showFab ? '' : 'none'
|
|
230
|
+
if (showFab) place()
|
|
231
|
+
scrim.style.display = on && !collapsed ? '' : 'none'
|
|
232
|
+
}
|
|
233
|
+
sync()
|
|
234
|
+
// MutationObserver 跟属性翻转即时同步;1.5s 轮询覆盖路由重渲等一切边角
|
|
235
|
+
const observer = typeof MutationObserver !== 'undefined' ? new MutationObserver(sync) : null
|
|
236
|
+
if (observer) observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-sidebar-collapsed'], subtree: true })
|
|
237
|
+
const timer = setInterval(sync, 1500)
|
|
238
|
+
const onMq = () => sync()
|
|
239
|
+
if (mq.addEventListener) mq.addEventListener('change', onMq)
|
|
240
|
+
else if (mq.addListener) mq.addListener(onMq)
|
|
241
|
+
window.addEventListener('resize', place)
|
|
242
|
+
|
|
243
|
+
// 捕获阶段统一处理:抽屉按钮自身排除(否则展开动作会被"点外部收起"当场撤销);
|
|
244
|
+
// 遮罩不挂自己的 click——它的点击也走这里 collapse(),单一路径防止同一事件两次 toggle。
|
|
245
|
+
const onClick = (e) => {
|
|
246
|
+
if (!narrow()) return
|
|
247
|
+
const t = e.target
|
|
248
|
+
if (!t || !t.closest) return
|
|
249
|
+
if (t.closest('.dsh-mobile-drawer-btn')) return
|
|
250
|
+
const expanded = !!document.querySelector('button[aria-label="收起侧边栏"],button[aria-label="Collapse sidebar"]')
|
|
251
|
+
const col = document.querySelector('[class*="sidebarCol"]')
|
|
252
|
+
const inSidebar = col && col.contains(t)
|
|
253
|
+
if (!expanded && !inSidebar) return
|
|
254
|
+
if (inSidebar) {
|
|
255
|
+
const row = t.closest('[role="treeitem"]')
|
|
256
|
+
const leaf = row && !row.querySelector('[role="treeitem"]') && row.closest('[role="tree"]')
|
|
257
|
+
const fresh = t.closest('[class*="newSession"]')
|
|
258
|
+
if (leaf || fresh) setTimeout(() => { collapse(); closeDetails() }, 300)
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
if (t.closest('[role="dialog"],[class*="dockm"],[class*="dgfab"],[class*="dgwin"],[class*="dgame"],[class*="detailsCol"]')) return
|
|
262
|
+
collapse()
|
|
263
|
+
}
|
|
264
|
+
document.addEventListener('click', onClick, true)
|
|
265
|
+
|
|
266
|
+
return () => {
|
|
267
|
+
document.removeEventListener('click', onClick, true)
|
|
268
|
+
if (observer) observer.disconnect()
|
|
269
|
+
clearInterval(timer)
|
|
270
|
+
if (mq.removeEventListener) mq.removeEventListener('change', onMq)
|
|
271
|
+
else if (mq.removeListener) mq.removeListener(onMq)
|
|
272
|
+
window.removeEventListener('resize', place)
|
|
273
|
+
fab.remove()
|
|
274
|
+
scrim.remove()
|
|
275
|
+
if (window.__dshDockMobileDrawer === 'client') delete window.__dshDockMobileDrawer
|
|
276
|
+
}
|
|
277
|
+
}, [])
|
|
278
|
+
return null
|
|
279
|
+
}
|
|
280
|
+
|
|
158
281
|
export function MobileRelayHomeStat() {
|
|
159
282
|
const [summary, setSummary] = useState('未开启,开启后可远程登录')
|
|
160
283
|
useEffect(() => {
|
|
@@ -167,8 +290,11 @@ export function MobileRelayHomeStat() {
|
|
|
167
290
|
|
|
168
291
|
export const feature = {
|
|
169
292
|
id: 'mobile-relay', name: '远程访问', order: 80, accent: '#38bdf8',
|
|
170
|
-
description: '账号密码登录的远程入口:所有设备访问同一个 DSH,任务进度实时一致', defaultEnabled:
|
|
293
|
+
description: '账号密码登录的远程入口:所有设备访问同一个 DSH,任务进度实时一致', defaultEnabled: false,
|
|
171
294
|
css: `
|
|
172
|
-
.dmr{--dmr-accent:var(--dk-accent,#2f6fed);--dmr-accent-soft:color-mix(in srgb,var(--dmr-accent) 13%,transparent);display:flex;flex-direction:column;gap:16px;max-width:720px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}.dmr h3,.dmr h4,.dmr p{margin:0}.dmr h3{font-size:17px;line-height:1.25;letter-spacing:-.01em}.dmr h4{font-size:13px}.dmr-hero,.dmr-status-head{display:flex;align-items:flex-start;gap:12px}.dmr-hero>div,.dmr-status-head>div{min-width:0;display:flex;flex-direction:column;gap:4px}.dmr-hero p,.dmr-status-head p,.dmr-field small,.dmr-share small,.dmr-overview-card small,.dmr-task small,.dmr-note p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}.dmr-hero-icon{width:42px;height:42px;display:inline-flex;align-items:center;justify-content:center;flex:none;border-radius:14px;background:var(--dmr-accent-soft);color:var(--dmr-accent);border:1px solid color-mix(in srgb,var(--dmr-accent) 35%,var(--dsw-alias-border-l1))}.dmr-steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin:0;padding:0;list-style:none}.dmr-steps li{display:flex;gap:9px;padding:11px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:12px}.dmr-steps li>span{width:21px;height:21px;display:inline-flex;align-items:center;justify-content:center;flex:none;border-radius:50%;font-size:11px;font-weight:700;color:var(--dmr-accent);background:var(--dmr-accent-soft)}.dmr-steps div{display:flex;flex-direction:column;gap:3px;min-width:0}.dmr-steps strong{font-size:12px}.dmr-steps small{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.45}.dmr-field{display:flex;flex-direction:column;gap:6px;font-weight:600}.dmr-field input,.dmr-field select,.dmr-note textarea{box-sizing:border-box;width:100%;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:10px;padding:10px 12px;min-height:44px;font:inherit;outline:none;transition:border-color .18s ease,box-shadow .18s ease}.dmr-field input:focus,.dmr-field select:focus,.dmr-note textarea:focus{border-color:var(--dmr-accent);box-shadow:0 0 0 3px var(--dmr-accent-soft)}.dmr-field small{font-weight:400}.dmr-network-grid{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(150px,.6fr);gap:12px}.dmr-security{padding:12px 13px;border:1px solid color-mix(in srgb,var(--dk-warn) 48%,var(--dsw-alias-border-l1));border-radius:12px;background:color-mix(in srgb,var(--dk-warn) 9%,transparent)}.dmr-security strong{display:block;margin-bottom:4px;color:var(--dk-warn);font-size:12px}.dmr-security p{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.55}.dmr-primary,.dmr-secondary,.dmr-text-button,.dmr-icon-button{font:inherit;touch-action:manipulation;cursor:pointer;transition:transform .15s ease,background .18s ease,border-color .18s ease,opacity .18s ease}.dmr-primary,.dmr-secondary{min-height:44px;display:inline-flex;align-items:center;justify-content:center;gap:8px;border-radius:10px;padding:9px 13px;font-weight:600}.dmr-primary{align-self:flex-start;border:1px solid var(--dmr-accent);background:var(--dmr-accent);color:#fff}.dmr-primary:hover{filter:brightness(1.05)}.dmr-secondary{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.dmr-secondary:hover{border-color:var(--dmr-accent);background:var(--dmr-accent-soft)}.dmr-primary:active,.dmr-secondary:active,.dmr-icon-button:active{transform:scale(.98)}.dmr-primary:disabled,.dmr-secondary:disabled,.dmr-text-button:disabled,.dmr-icon-button:disabled{cursor:not-allowed;opacity:.5}.dmr-message{display:flex;align-items:flex-start;gap:7px;padding:10px 12px;border-radius:10px;font-size:12px}.dmr-message.success{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 10%,transparent)}.dmr-message.error{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}.dmr-status-head{justify-content:space-between}.dmr-eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:var(--dmr-accent)}.dmr-eyebrow i{width:7px;height:7px;border-radius:50%;background:currentColor;box-shadow:0 0 0 4px var(--dmr-accent-soft)}.dmr-icon-button{display:inline-flex;align-items:center;justify-content:center;flex:none;width:44px;height:44px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:transparent;color:var(--dsw-alias-label-secondary)}.dmr-icon-button:hover{color:var(--dsw-alias-state-error-primary);border-color:currentColor}.dmr-share{display:flex;flex-direction:column;gap:9px;padding:13px;border:1px solid color-mix(in srgb,var(--dmr-accent) 32%,var(--dsw-alias-border-l1));border-radius:13px;background:var(--dmr-accent-soft)}.dmr-share-layout{display:grid;grid-template-columns:196px minmax(0,1fr);gap:16px;align-items:center}.dmr-qr-card{box-sizing:border-box;display:flex;flex-direction:column;align-items:center;gap:5px;padding:10px;border-radius:12px;background:#fff;color:#111827;text-align:center}.dmr-qr-card img{display:block;width:176px;height:176px;max-width:100%;object-fit:contain}.dmr-qr-card strong{font-size:12px}.dmr-qr-card small{color:#4b5563;font-size:10px}.dmr-qr-loading{display:grid;place-items:center;width:176px;height:176px;color:#64748b;font-size:12px}.dmr-share-detail{min-width:0;display:flex;flex-direction:column;gap:10px}.dmr-share-detail>div:first-child{display:flex;flex-direction:column;gap:4px}.dmr-link{padding:10px 11px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;word-break:break-all;color:var(--dsw-alias-label-secondary)}.dmr-share-actions{display:flex;gap:8px;flex-wrap:wrap}.dmr-share-actions .dmr-primary{align-self:auto}.dmr-overview{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.dmr-overview-card{display:flex;flex-direction:column;gap:3px;padding:12px;border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}.dmr-overview-card>span{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dmr-overview-card strong{font-size:16px;letter-spacing:-.01em}.dmr-section{display:flex;flex-direction:column;gap:9px}.dmr-section-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.dmr-section-head small{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dmr-text-button{border:0;background:transparent;color:var(--dmr-accent);padding:8px;min-height:36px;font-weight:600}.dmr-task-list{display:flex;flex-direction:column;gap:8px}.dmr-task{display:flex;align-items:flex-start;gap:9px;padding:10px 11px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}.dmr-task>div{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}.dmr-task strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dmr-phase{flex:none;border-radius:999px;padding:3px 8px;font-size:11px;background:var(--dmr-accent-soft);color:var(--dmr-accent)}.dmr-phase.write{color:var(--dsw-alias-state-success-primary)}.dmr-phase.code{color:var(--dk-warn)}.dmr-phase.search{color:#0d9488}.dmr-empty{padding:18px 12px;text-align:center;border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-secondary);font-size:12px}.dmr-note{display:flex;flex-direction:column;gap:9px;padding-top:2px}.dmr-note blockquote{margin:0;padding:10px 12px;border-left:3px solid var(--dmr-accent);border-radius:0 9px 9px 0;background:var(--dmr-accent-soft);white-space:pre-wrap;font-size:12px}.dmr-note textarea{min-height:88px;resize:vertical;line-height:1.5}.dmr-note .dmr-secondary{align-self:flex-start}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media (max-width:680px){.dmr{gap:14px;font-size:14px}.dmr h3{font-size:18px}.dmr-steps{grid-template-columns:1fr;gap:8px}.dmr-steps li{padding:10px}.dmr-steps small,.dmr-hero p,.dmr-status-head p,.dmr-field small,.dmr-share small,.dmr-overview-card small,.dmr-task small,.dmr-note p{font-size:12px}.dmr-network-grid{grid-template-columns:1fr;gap:10px}.dmr-field input,.dmr-field select{font-size:16px}.dmr-primary,.dmr-secondary{width:100%;font-size:14px}.dmr-share-layout{grid-template-columns:1fr;gap:12px}.dmr-qr-card{width:min(240px,100%);margin:0 auto}.dmr-qr-card img,.dmr-qr-loading{width:210px;height:210px}.dmr-share-actions{flex-direction:column}.dmr-share-actions .dmr-primary{width:100%}.dmr-overview{gap:8px}.dmr-overview-card{padding:11px}.dmr-status-head{gap:8px}.dmr-link{font-size:11px}.dmr-note textarea{font-size:16px;min-height:104px}.dmr-note .dmr-secondary{align-self:stretch}.dmr-compact .dmr-hero-icon{width:40px;height:40px;border-radius:13px}}@media (prefers-reduced-motion:reduce){.dmr-primary,.dmr-secondary,.dmr-text-button,.dmr-icon-button,.dmr-field input,.dmr-field select,.dmr-note textarea{transition:none}}.dmr-lan{flex:none;margin-top:6px;padding-top:16px;border-top:1px solid var(--dsw-alias-border-l1)}.dmr-lan-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.dmr-lan-title{min-width:0;display:flex;flex-direction:column;gap:4px}.dmr-lan-title p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}.dmr-lan-badge{flex:none;display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:5px 10px;font-size:11px;font-weight:700;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}.dmr-lan-badge.on{border-color:color-mix(in srgb,var(--dmr-accent) 45%,transparent);background:var(--dmr-accent-soft);color:var(--dmr-accent)}.dmr-lan-active,.dmr-lan-idle{display:flex;flex-direction:column;gap:12px}.dmr-lan-active .dmr-share-detail small strong{color:var(--dsw-alias-state-error-primary)}.dmr-danger{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,var(--dsw-alias-border-l2));color:var(--dsw-alias-state-error-primary)}.dmr-danger:hover{border-color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}`,
|
|
173
|
-
|
|
295
|
+
.dmr{--dmr-accent:var(--dk-accent,#2f6fed);--dmr-accent-soft:color-mix(in srgb,var(--dmr-accent) 13%,transparent);display:flex;flex-direction:column;gap:16px;max-width:720px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}.dmr h3,.dmr h4,.dmr p{margin:0}.dmr h3{font-size:17px;line-height:1.25;letter-spacing:-.01em}.dmr h4{font-size:13px}.dmr-hero,.dmr-status-head{display:flex;align-items:flex-start;gap:12px}.dmr-hero>div,.dmr-status-head>div{min-width:0;display:flex;flex-direction:column;gap:4px}.dmr-hero p,.dmr-status-head p,.dmr-field small,.dmr-share small,.dmr-overview-card small,.dmr-task small,.dmr-note p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}.dmr-hero-icon{width:42px;height:42px;display:inline-flex;align-items:center;justify-content:center;flex:none;border-radius:14px;background:var(--dmr-accent-soft);color:var(--dmr-accent);border:1px solid color-mix(in srgb,var(--dmr-accent) 35%,var(--dsw-alias-border-l1))}.dmr-steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin:0;padding:0;list-style:none}.dmr-steps li{display:flex;gap:9px;padding:11px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:12px}.dmr-steps li>span{width:21px;height:21px;display:inline-flex;align-items:center;justify-content:center;flex:none;border-radius:50%;font-size:11px;font-weight:700;color:var(--dmr-accent);background:var(--dmr-accent-soft)}.dmr-steps div{display:flex;flex-direction:column;gap:3px;min-width:0}.dmr-steps strong{font-size:12px}.dmr-steps small{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.45}.dmr-field{display:flex;flex-direction:column;gap:6px;font-weight:600}.dmr-field input,.dmr-field select,.dmr-note textarea{box-sizing:border-box;width:100%;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:10px;padding:10px 12px;min-height:44px;font:inherit;outline:none;transition:border-color .18s ease,box-shadow .18s ease}.dmr-field input:focus,.dmr-field select:focus,.dmr-note textarea:focus{border-color:var(--dmr-accent);box-shadow:0 0 0 3px var(--dmr-accent-soft)}.dmr-field small{font-weight:400}.dmr-network-grid{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(150px,.6fr);gap:12px}.dmr-security{padding:12px 13px;border:1px solid color-mix(in srgb,var(--dk-warn) 48%,var(--dsw-alias-border-l1));border-radius:12px;background:color-mix(in srgb,var(--dk-warn) 9%,transparent)}.dmr-security strong{display:block;margin-bottom:4px;color:var(--dk-warn);font-size:12px}.dmr-security p{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.55}.dmr-primary,.dmr-secondary,.dmr-text-button,.dmr-icon-button{font:inherit;touch-action:manipulation;cursor:pointer;transition:transform .15s ease,background .18s ease,border-color .18s ease,opacity .18s ease}.dmr-primary,.dmr-secondary{min-height:44px;display:inline-flex;align-items:center;justify-content:center;gap:8px;border-radius:10px;padding:9px 13px;font-weight:600}.dmr-primary{align-self:flex-start;border:1px solid var(--dmr-accent);background:var(--dmr-accent);color:#fff}.dmr-primary:hover{filter:brightness(1.05)}.dmr-secondary{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.dmr-secondary:hover{border-color:var(--dmr-accent);background:var(--dmr-accent-soft)}.dmr-primary:active,.dmr-secondary:active,.dmr-icon-button:active{transform:scale(.98)}.dmr-primary:disabled,.dmr-secondary:disabled,.dmr-text-button:disabled,.dmr-icon-button:disabled{cursor:not-allowed;opacity:.5}.dmr-message{display:flex;align-items:flex-start;gap:7px;padding:10px 12px;border-radius:10px;font-size:12px}.dmr-message.success{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 10%,transparent)}.dmr-message.error{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}.dmr-status-head{justify-content:space-between}.dmr-eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:var(--dmr-accent)}.dmr-eyebrow i{width:7px;height:7px;border-radius:50%;background:currentColor;box-shadow:0 0 0 4px var(--dmr-accent-soft)}.dmr-icon-button{display:inline-flex;align-items:center;justify-content:center;flex:none;width:44px;height:44px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:transparent;color:var(--dsw-alias-label-secondary)}.dmr-icon-button:hover{color:var(--dsw-alias-state-error-primary);border-color:currentColor}.dmr-share{display:flex;flex-direction:column;gap:9px;padding:13px;border:1px solid color-mix(in srgb,var(--dmr-accent) 32%,var(--dsw-alias-border-l1));border-radius:13px;background:var(--dmr-accent-soft)}.dmr-share-layout{display:grid;grid-template-columns:196px minmax(0,1fr);gap:16px;align-items:center}.dmr-qr-card{box-sizing:border-box;display:flex;flex-direction:column;align-items:center;gap:5px;padding:10px;border-radius:12px;background:#fff;color:#111827;text-align:center}.dmr-qr-card img{display:block;width:176px;height:176px;max-width:100%;object-fit:contain}.dmr-qr-card strong{font-size:12px}.dmr-qr-card small{color:#4b5563;font-size:10px}.dmr-qr-loading{display:grid;place-items:center;width:176px;height:176px;color:#64748b;font-size:12px}.dmr-share-detail{min-width:0;display:flex;flex-direction:column;gap:10px}.dmr-share-detail>div:first-child{display:flex;flex-direction:column;gap:4px}.dmr-link{padding:10px 11px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;word-break:break-all;color:var(--dsw-alias-label-secondary)}.dmr-share-actions{display:flex;gap:8px;flex-wrap:wrap}.dmr-share-actions .dmr-primary{align-self:auto}.dmr-overview{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.dmr-overview-card{display:flex;flex-direction:column;gap:3px;padding:12px;border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}.dmr-overview-card>span{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dmr-overview-card strong{font-size:16px;letter-spacing:-.01em}.dmr-section{display:flex;flex-direction:column;gap:9px}.dmr-section-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.dmr-section-head small{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dmr-text-button{border:0;background:transparent;color:var(--dmr-accent);padding:8px;min-height:36px;font-weight:600}.dmr-task-list{display:flex;flex-direction:column;gap:8px}.dmr-task{display:flex;align-items:flex-start;gap:9px;padding:10px 11px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}.dmr-task>div{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}.dmr-task strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dmr-phase{flex:none;border-radius:999px;padding:3px 8px;font-size:11px;background:var(--dmr-accent-soft);color:var(--dmr-accent)}.dmr-phase.write{color:var(--dsw-alias-state-success-primary)}.dmr-phase.code{color:var(--dk-warn)}.dmr-phase.search{color:#0d9488}.dmr-empty{padding:18px 12px;text-align:center;border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-secondary);font-size:12px}.dmr-note{display:flex;flex-direction:column;gap:9px;padding-top:2px}.dmr-note blockquote{margin:0;padding:10px 12px;border-left:3px solid var(--dmr-accent);border-radius:0 9px 9px 0;background:var(--dmr-accent-soft);white-space:pre-wrap;font-size:12px}.dmr-note textarea{min-height:88px;resize:vertical;line-height:1.5}.dmr-note .dmr-secondary{align-self:flex-start}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media (max-width:680px){.dmr{gap:14px;font-size:14px}.dmr h3{font-size:18px}.dmr-steps{grid-template-columns:1fr;gap:8px}.dmr-steps li{padding:10px}.dmr-steps small,.dmr-hero p,.dmr-status-head p,.dmr-field small,.dmr-share small,.dmr-overview-card small,.dmr-task small,.dmr-note p{font-size:12px}.dmr-network-grid{grid-template-columns:1fr;gap:10px}.dmr-field input,.dmr-field select{font-size:16px}.dmr-primary,.dmr-secondary{width:100%;font-size:14px}.dmr-share-layout{grid-template-columns:1fr;gap:12px}.dmr-qr-card{width:min(240px,100%);margin:0 auto}.dmr-qr-card img,.dmr-qr-loading{width:210px;height:210px}.dmr-share-actions{flex-direction:column}.dmr-share-actions .dmr-primary{width:100%}.dmr-overview{gap:8px}.dmr-overview-card{padding:11px}.dmr-status-head{gap:8px}.dmr-link{font-size:11px}.dmr-note textarea{font-size:16px;min-height:104px}.dmr-note .dmr-secondary{align-self:stretch}.dmr-compact .dmr-hero-icon{width:40px;height:40px;border-radius:13px}}@media (prefers-reduced-motion:reduce){.dmr-primary,.dmr-secondary,.dmr-text-button,.dmr-icon-button,.dmr-field input,.dmr-field select,.dmr-note textarea{transition:none}}.dmr-lan{flex:none;margin-top:6px;padding-top:16px;border-top:1px solid var(--dsw-alias-border-l1)}.dmr-lan-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.dmr-lan-title{min-width:0;display:flex;flex-direction:column;gap:4px}.dmr-lan-title p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}.dmr-lan-badge{flex:none;display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:5px 10px;font-size:11px;font-weight:700;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}.dmr-lan-badge.on{border-color:color-mix(in srgb,var(--dmr-accent) 45%,transparent);background:var(--dmr-accent-soft);color:var(--dmr-accent)}.dmr-lan-active,.dmr-lan-idle{display:flex;flex-direction:column;gap:12px}.dmr-lan-active .dmr-share-detail small strong{color:var(--dsw-alias-state-error-primary)}.dmr-danger{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,var(--dsw-alias-border-l2));color:var(--dsw-alias-state-error-primary)}.dmr-danger:hover{border-color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}
|
|
296
|
+
/* 抽屉把手:贴左边缘(同趣味游戏浮标的边缘吸附语言),top 由 place() 动态设定;
|
|
297
|
+
* z 序 70 < 遮罩 75 < 侧栏 80 < overlayLayer 90,面板/弹窗打开时自然被盖。 */
|
|
298
|
+
@media (max-width:700px){.dsh-mobile-drawer-btn{position:fixed;left:0;top:38vh;z-index:70;box-sizing:border-box;width:30px;height:48px;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid var(--dsw-alias-border-l1,rgba(127,139,161,.35));border-left:none;border-radius:0 12px 12px 0;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#1c2230) 88%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:var(--dsw-alias-label-primary,#e6eaf2);box-shadow:4px 0 18px rgba(0,0,0,.28);cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent}.dsh-mobile-drawer-btn:active{transform:scale(.94)}.dsh-mobile-drawer-btn svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.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}}`,
|
|
299
|
+
View: MobileRelayView, HomeStat: MobileRelayHomeStat, Overlay: MobileRelayOverlay,
|
|
174
300
|
}
|
|
@@ -359,7 +359,7 @@ export const feature = {
|
|
|
359
359
|
id: 'models',
|
|
360
360
|
name: '模型设置',
|
|
361
361
|
description: '模型目录读写:编辑输入类型与思考强度,写回官方配置热生效',
|
|
362
|
-
defaultEnabled:
|
|
362
|
+
defaultEnabled: false,
|
|
363
363
|
setup(ctx) {
|
|
364
364
|
const webServer = ctx.get('webServer')
|
|
365
365
|
if (webServer === undefined) throw new Error('webServer 服务不可用,无法提供模型设置路由')
|
package/features/theme/view.js
CHANGED
|
@@ -296,7 +296,7 @@ export const feature = {
|
|
|
296
296
|
id: 'visionproxy',
|
|
297
297
|
name: '图片理解代理',
|
|
298
298
|
description: '纯文本模型收图时自动调用视觉模型识别(多模态模型不受影响)',
|
|
299
|
-
defaultEnabled:
|
|
299
|
+
defaultEnabled: false,
|
|
300
300
|
setup(ctx) {
|
|
301
301
|
// 模型目录的 TTL 缓存:图片代理判定目标模型是否纯文本时用(避免每个请求都全量 describe)
|
|
302
302
|
let dockDirCache = { at: 0, data: { providers: [] } }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-dock",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.6",
|
|
4
4
|
"description": "DeepSeek Harness 功能坞插件(dsh-dock):模块化功能中枢——每个功能一个 features/ 模块(可单独提取成包发布,又可经 dockBridge 装回面板)。含用量记录(LLM 调用记账/费用统计)、模型设置、图片理解代理、模型余额、任务动画(19 种运行动画 + 桌面伙伴 3D 场景、完成/需确认通知与提示音、钉钉/飞书推送,开关独立、配置持久化);侧栏入口 + 可拖拽弹窗(最大化/最小化/缩放)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
package/src/client.jsx
CHANGED
|
@@ -28,7 +28,7 @@ import { feature as fGames } from "../features/games/view.jsx";
|
|
|
28
28
|
import { feature as fMobileRelay } from "../features/mobile-relay/view.jsx";
|
|
29
29
|
|
|
30
30
|
const name = "dsh-dock";
|
|
31
|
-
const DOCK_VERSION = "0.9.
|
|
31
|
+
const DOCK_VERSION = "0.9.5";
|
|
32
32
|
|
|
33
33
|
// ---- 内置功能注册表:新功能 = features/<id>/ 加模块 + 这里 import 一行 ----
|
|
34
34
|
const BUILTIN_FEATURES = [fTokenlog, fModelconfig, fHeartbeat, fTheme, fBalance, fAnimation, fGames, fMobileRelay];
|
package/src/shared.js
CHANGED
|
@@ -44,11 +44,12 @@ function persistFeatureEnabled() {
|
|
|
44
44
|
if (typeof localStorage !== "undefined") localStorage.setItem(FEATURE_STORE_KEY, JSON.stringify(featurePersist.map));
|
|
45
45
|
} catch { /* 持久化失败静默 */ }
|
|
46
46
|
}
|
|
47
|
-
/** 外壳 apply 时初始化内置功能的默认开关(持久化过的值优先于模块默认)。
|
|
47
|
+
/** 外壳 apply 时初始化内置功能的默认开关(持久化过的值优先于模块默认)。
|
|
48
|
+
* 默认全部停用、按需开启:仅当模块显式声明 defaultEnabled: true 才默认启用。 */
|
|
48
49
|
export function initFeatureState(defs) {
|
|
49
50
|
for (const f of defs) {
|
|
50
51
|
if (featureState.has(f.id)) continue;
|
|
51
|
-
let enabled = !f.planned && f.defaultEnabled
|
|
52
|
+
let enabled = !f.planned && f.defaultEnabled === true;
|
|
52
53
|
const saved = featurePersist.map[f.id];
|
|
53
54
|
if (typeof saved === "boolean") enabled = saved;
|
|
54
55
|
featureState.set(f.id, { enabled, error: null });
|