Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ What it looks like — the phone shows the exact same UI as your computer, live:
| ⚡ Real-time sync | Streaming output passes through WebSocket untouched — what the computer renders, the phone renders live; fully interactive both ways; built-in WS heartbeat keep-alive (defeats silent NAT/battery link drops with auto-reconnect) |
| 📱 Mobile-adaptive layout | Narrow screens get a drawer layout automatically (ported from dsh-web-mobile, MIT): sidebar drawer, full-width conversation, safe-area insets, touch optimizations |
| 🧭 Optional right sidebar | Shows the native right-sidebar entry on mobile; disable it for a compact phone header or keep it available alongside the terminal dock on an unfolded display |
| 🛠️ Remote settings | Phone/remote pages can **edit plugin config and models** (issue #58). **Off by default** (remote pages are read-only; change settings on the computer itself). |
| 📁 File browser | The mobile "Files" entries need a host-side explorer panel (a dsh-web-ui component); on stock DSH without it the entries are auto-hidden instead of doing nothing |
| 🗜️ Transfer compression | Large JSON responses are gzip/brotli'd on the fly (17MB session history → ~1MB; brotli quality 6: fast and bandwidth-friendly) — faster loads, less mobile data |
| 🔁 Tunnel auto-restore | After a DSH restart the previously-running public tunnel comes back automatically |
Expand Down Expand Up @@ -89,6 +90,8 @@ Settings → **Phone access** → scan the "📶 LAN" QR code → enter the **LA
>
> The LAN PIN is **on by default** (security-first). If you're the only user and find typing it every time annoying, flip "LAN access PIN" to **Off** in the LAN block — LAN scans then connect directly with no PIN (LAN-only devices; the **public tunnel always requires a PIN**, unaffected).
>
> "**Remote settings**" is **off by default**: plugin config and models are read-only on phone/remote pages — change them on the computer itself (127.0.0.1). Flip it **On** in the LAN block and the phone can edit plugin config and models directly.
>
> After logging in once, the phone **won't ask again**: as long as the computer's dsh web keeps running, reopening the phone needs no PIN (**a dsh web restart/update asks for it once more**).
>
> Advanced option: auto-detection may not pick a reachable address for Tailscale/VPN setups. You can select a detected IP from the "LAN address" dropdown; normally no change is needed.
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ DSH Pocket 就是干这个的:**装上它,手机扫个码,就能实时看
| ⚡ 实时同步 | 流式输出走 WebSocket 全透传——**电脑上在输出,手机上同步在滚**,可双向操作;内置心跳保活(防路由器 NAT/省电机制静默断链,断线自动重连) |
| 📱 移动端适配 | 窄屏自动变抽屉布局(移植 dsh-web-mobile,MIT):侧栏抽屉、会话全宽、状态栏安全区、触控优化 |
| 🧭 可选右边栏 | 手机端显示原生右边栏入口;普通手机可在设置中关闭以保持紧凑,折叠屏展开后可更方便地同时使用终端底栏和右边栏 |
| 🛠️ 远程设置 | 手机/远程页面**可编辑设置**(插件配置、模型管理等,issue #58)。默认**关闭**(远程只读,设置仅本机可改);开启后安全边界为访问密码——公网强制、局域网建议开启 |
| 📁 文件浏览 | 移动端「文件浏览」入口需要宿主提供 explorer 面板(dsh-web-ui 组件);官方 DSH 未内置时入口自动隐藏,不会出现"点了没反应" |
| 🗜️ 传输压缩 | 大 JSON 响应自动 gzip/brotli(长会话 17MB → ~1MB,brotli 质量 6:快且省流量),手机加载更快、更省流量 |
| 🔁 隧道自动恢复 | DSH 重启后自动重新拉起之前开着的公网隧道,无需手动重开 |
Expand Down Expand Up @@ -89,6 +90,8 @@ npx @deepseek-ai/dsh web
>
> 局域网密码**默认开启**(安全优先)。如果只有自己用、嫌每次输密码麻烦,可在设置页局域网区块把「局域网访问密码」切到**关**——之后局域网扫码直连、无需密码(仅同一局域网设备可访问;**公网始终要密码**,不受影响)。
>
> 「**远程设置**」默认**关**:远程页面(手机)里的插件配置、模型管理为只读,改动请在电脑本机(127.0.0.1)操作。在设置页局域网区块把它切到**开**,手机即可直接编辑插件配置与模型管理。
>
> 手机登录一次后**长期免输**:只要电脑上的 dsh web 不重启,再次打开手机不用再输入(**dsh web 重启/更新后需重新输入一次**)。
>
> 高级选项:自动识别在 Tailscale/VPN 等场景下可能选不到可达地址。可在「局域网地址」下拉框手动选择已检测到的 IP;一般不需要修改。
Expand Down
1 change: 1 addition & 0 deletions client/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const POCKET_ENDPOINTS = Object.freeze({
restart: 'pocket.restart',
lanTokenRefresh: 'token.lanRefresh',
lanAuthSetEnabled: 'lanAuth.setEnabled',
trustSetEnabled: 'trust.setEnabled',
lanSetOverride: 'lan.setOverride',
lanSetEnabled: 'lan.setEnabled',
mobileRightbarSetEnabled: 'mobile.rightbar.setEnabled',
Expand Down
52 changes: 38 additions & 14 deletions client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ var __copyProps = (to, from, except, desc) => {
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// client/index.jsx
// index.jsx
var index_exports = {};
__export(index_exports, {
apply: () => apply,
Expand All @@ -39,7 +39,7 @@ __export(index_exports, {
module.exports = __toCommonJS(index_exports);
var import_react2 = require("react");

// client/api.js
// api.js
var POCKET_RPC_CHANNEL = "/dsh-pocket";
var MOBILE_RIGHTBAR_ATTRIBUTE = "data-dsh-pocket-mobile-rightbar";
var MOBILE_RIGHTBAR_EVENT = "dsh-pocket:mobile-rightbar";
Expand All @@ -53,6 +53,7 @@ var POCKET_ENDPOINTS = Object.freeze({
restart: "pocket.restart",
lanTokenRefresh: "token.lanRefresh",
lanAuthSetEnabled: "lanAuth.setEnabled",
trustSetEnabled: "trust.setEnabled",
lanSetOverride: "lan.setOverride",
lanSetEnabled: "lan.setEnabled",
mobileRightbarSetEnabled: "mobile.rightbar.setEnabled",
Expand Down Expand Up @@ -108,7 +109,7 @@ function redactStatus(s) {
};
}

// client/mobile/MobileNavToggle.tsx
// mobile/MobileNavToggle.tsx
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
function MobileNavToggle({ toggleSidebar, t }) {
const toggleExplorer = () => {
Expand Down Expand Up @@ -143,11 +144,11 @@ function MobileNavToggle({ toggleSidebar, t }) {
));
}

// client/mobile/MobileNavOverlay.tsx
// mobile/MobileNavOverlay.tsx
var import_react = require("react");
var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");

// client/mobile/nav-targets.mjs
// mobile/nav-targets.mjs
var DRAWER_SELECTOR = '[data-mobile-nav="frame"] > :first-child';
var TOGGLE_SELECTOR = '[data-mobile-nav="toggle"]';
var NAV_TARGETS = [
Expand Down Expand Up @@ -180,7 +181,7 @@ function isOverlayTap(target) {
return target.closest(OVERLAY_SELECTOR) !== null;
}

// client/mobile/MobileNavOverlay.tsx
// mobile/MobileNavOverlay.tsx
var MOBILE_QUERY = "(max-width: 1023px)";
function useMobile() {
const [mobile, setMobile] = (0, import_react.useState)(() => window.matchMedia(MOBILE_QUERY).matches);
Expand Down Expand Up @@ -371,7 +372,7 @@ function MobileNavOverlay({ toggleSidebar, t }) {
));
}

// client/mobile/MobileDrawerFooter.tsx
// mobile/MobileDrawerFooter.tsx
var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primitives");
function MobileDrawerFooter({ useSessions, downloadSessionLog, toggleSidebar, t }) {
const sessionId = useSessions((state) => state.current);
Expand Down Expand Up @@ -407,7 +408,7 @@ function MobileDrawerFooter({ useSessions, downloadSessionLog, toggleSidebar, t
));
}

// client/mobile/fileGuard.ts
// mobile/fileGuard.ts
var GUARD_MSG = "\u624B\u673A\u4E0A\u65E0\u6CD5\u76F4\u63A5\u6253\u5F00\u7535\u8111\u4E0A\u7684\u6587\u4EF6";
var WS_LABELS = ["\u6DFB\u52A0\u5DE5\u4F5C\u533A", "\u6DFB\u52A0\u5DE5\u4F5C\u533A\u2026", "Add workspace", "Add workspace\u2026"];
var COPY_LABEL = "\u590D\u5236";
Expand Down Expand Up @@ -570,7 +571,7 @@ function startFileGuard(readFile) {
};
}

// client/mobile/mobile.css.ts
// mobile/mobile.css.ts
var MOBILE_CSS = `
/* ---------- base control styles (rendered at any width, hidden where unused) ---------- */

Expand Down Expand Up @@ -1561,7 +1562,7 @@ var MOBILE_CSS = `

`;

// client/mobile/locales.ts
// mobile/locales.ts
var NS = "mobileNav";
var zh = {
"open": "\u6253\u5F00\u76EE\u5F55",
Expand All @@ -1578,7 +1579,7 @@ var en = {
"files": "Files"
};

// client/mobile/layout-mode.mjs
// mobile/layout-mode.mjs
function resolveLayout({ urlValue, stored, narrowMatch }) {
const url = String(urlValue ?? "").trim();
if (url === "desktop") return "desktop";
Expand All @@ -1602,7 +1603,7 @@ function persistLayoutFromUrl(urlValue) {
}
}

// client/mobile/mobile-apply.tsx
// mobile/mobile-apply.tsx
function mobileApply(ctx) {
const urlValue = new URL(window.location.href).searchParams.get("dsh-layout") ?? "";
const narrowMQ = window.matchMedia("(max-width: 1023px)");
Expand Down Expand Up @@ -1879,7 +1880,7 @@ function mobileApply(ctx) {
}, MobileDrawerFooter));
}

// client/pocket-locales.js
// pocket-locales.js
var NS2 = "pocket";
var zh2 = {
"section": "\u624B\u673A\u8BBF\u95EE",
Expand Down Expand Up @@ -1943,6 +1944,9 @@ var zh2 = {
"pinInvalid": "\u5BC6\u7801\u5FC5\u987B\u662F 8\u201364 \u4F4D\u82F1\u6587\u5B57\u6BCD\u6216\u6570\u5B57",
"pinCustomHint": "\u81EA\u5B9A\u4E49\u540E\u5F00\u542F\u516C\u7F51\u4E0D\u518D\u81EA\u52A8\u6362\u65B0",
"lanPinOff": "\u{1F513} \u5BC6\u7801\u5DF2\u5173\u95ED\uFF1A\u626B\u7801\u76F4\u8FDE\uFF0C\u65E0\u9700\u5BC6\u7801\uFF08\u4EC5\u540C\u4E00\u5C40\u57DF\u7F51\u8BBE\u5907\u53EF\u8BBF\u95EE\uFF1B\u516C\u7F51\u4ECD\u8981\u5BC6\u7801\uFF09",
"trustClients": "\u8FDC\u7A0B\u8BBE\u7F6E\uFF08\u63D2\u4EF6\u914D\u7F6E / \u6A21\u578B\u7BA1\u7406\uFF09",
"trustClientsOn": "\u2705 \u624B\u673A/\u8FDC\u7A0B\u9875\u9762\u53EF\u7F16\u8F91\u63D2\u4EF6\u914D\u7F6E\u4E0E\u6A21\u578B\u7BA1\u7406\u3002",
"trustClientsOff": "\u{1F512} \u8FDC\u7A0B\u9875\u9762\u53EA\u8BFB\uFF1A\u8BBE\u7F6E\u8BF7\u5728\u7535\u8111\u672C\u673A\uFF08127.0.0.1\uFF09\u4FEE\u6539\u3002",
"lanStarting": "\u4EE3\u7406\u672A\u5C31\u7EEA\u2026",
"mobileRightbar": "\u624B\u673A\u7AEF\u53F3\u8FB9\u680F",
"mobileRightbarHint": "\u663E\u793A\u539F\u751F\u53F3\u8FB9\u680F\u5165\u53E3\uFF1B\u666E\u901A\u624B\u673A\u53EF\u6309\u9700\u5173\u95ED\uFF0C\u6298\u53E0\u5C4F\u5C55\u5F00\u540E\u4F7F\u7528\u66F4\u65B9\u4FBF",
Expand Down Expand Up @@ -2041,6 +2045,9 @@ var en2 = {
"pinInvalid": "PIN must be 8\u201364 characters (letters and digits only)",
"pinCustomHint": "custom PINs are not rotated on tunnel start",
"lanPinOff": "\u{1F513} PIN off \u2014 scan & go, no PIN (LAN devices only; public still requires PIN)",
"trustClients": "Remote settings (plugin config / models)",
"trustClientsOn": "\u2705 Phone/remote pages can edit plugin config and models.",
"trustClientsOff": "\u{1F512} Remote pages are read-only: change settings on the computer itself (127.0.0.1).",
"lanStarting": "Proxy starting\u2026",
"mobileRightbar": "Mobile right sidebar",
"mobileRightbarHint": "Show the native right-sidebar entry; disable it for a compact phone header or keep it on for an unfolded display",
Expand Down Expand Up @@ -2078,7 +2085,7 @@ var en2 = {
"feedback": "\u{1F64F} Questions? Open an issue on GitHub"
};

// client/index.jsx
// index.jsx
var name = "dsh-pocket";
var inject = ["slots", "connection", "layout", "locale", "sessionLogDownload"];
function fmt(t, key, vars) {
Expand Down Expand Up @@ -2305,6 +2312,13 @@ function PocketSettingsTab({ rpcCall, t }) {
} catch {
}
};
const setTrust = async (on) => {
try {
const r = await call(POCKET_ENDPOINTS.trustSetEnabled, { on });
setStatus((s) => ({ ...s, trustProxiedClients: r.trustProxiedClients }));
} catch {
}
};
const setMobileRightbar = async (on) => {
try {
const r = await call(POCKET_ENDPOINTS.mobileRightbarSetEnabled, { on });
Expand Down Expand Up @@ -2515,6 +2529,16 @@ function PocketSettingsTab({ rpcCall, t }) {
status?.lanPinCustom ? (0, import_react2.createElement)("span", { style: { fontSize: 11, color: "var(--dsw-alias-state-warn-primary,#b45309)" } }, t("pinCustomHint")) : null
)
),
// 远程设置开关(issue #58):信任经代理客户端 → 设置页/模型管理远程可编辑
row(
t("trustClients"),
Switch(status?.trustProxiedClients === true, () => setTrust(status?.trustProxiedClients !== true)),
(0, import_react2.createElement)(
"div",
{ style: { ...styles.muted, marginTop: 6 } },
status?.trustProxiedClients === true ? t("trustClientsOn") : t("trustClientsOff")
)
),
// 高级:手动选地址(默认收起)
row(
t("advAddress"),
Expand Down
14 changes: 14 additions & 0 deletions client/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,15 @@ function PocketSettingsTab({ rpcCall, t }) {
} catch { /* 忽略 */ }
};

// 远程设置开关(issue #58):开启后手机/远程页面的插件配置、模型管理恢复可编辑;
// 关闭回到只读(设置仅本机可改)。安全边界为访问密码。
const setTrust = async (on) => {
try {
const r = await call(POCKET_ENDPOINTS.trustSetEnabled, { on });
setStatus((s) => ({ ...s, trustProxiedClients: r.trustProxiedClients }));
} catch { /* 忽略 */ }
};

const setMobileRightbar = async (on) => {
try {
const r = await call(POCKET_ENDPOINTS.mobileRightbarSetEnabled, { on });
Expand Down Expand Up @@ -467,6 +476,11 @@ function PocketSettingsTab({ rpcCall, t }) {
customBtn('lan'),
status?.lanPinCustom ? h('span', { style: { fontSize: 11, color: 'var(--dsw-alias-state-warn-primary,#b45309)' } }, t('pinCustomHint')) : null,
))),
// 远程设置开关(issue #58):信任经代理客户端 → 设置页/模型管理远程可编辑
row(t('trustClients'),
Switch(status?.trustProxiedClients === true, () => setTrust(status?.trustProxiedClients !== true)),
h('div', { style: { ...styles.muted, marginTop: 6 } },
status?.trustProxiedClients === true ? t('trustClientsOn') : t('trustClientsOff'))),
// 高级:手动选地址(默认收起)
row(t('advAddress'),
h('button', { style: { border: 'none', background: 'none', font: 'inherit', cursor: 'pointer', fontSize: 12, color: 'var(--dsw-alias-label-tertiary,#8b93a1)', padding: 0 }, onClick: () => setAdvOpen((v) => !v) },
Expand Down
6 changes: 6 additions & 0 deletions client/pocket-locales.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ export const zh = {
'pinInvalid': '密码必须是 8–64 位英文字母或数字',
'pinCustomHint': '自定义后开启公网不再自动换新',
'lanPinOff': '🔓 密码已关闭:扫码直连,无需密码(仅同一局域网设备可访问;公网仍要密码)',
'trustClients': '远程设置(插件配置 / 模型管理)',
'trustClientsOn': '✅ 手机/远程页面可编辑插件配置与模型管理。',
'trustClientsOff': '🔒 远程页面只读:设置请在电脑本机(127.0.0.1)修改。',
'lanStarting': '代理未就绪…',
'mobileRightbar': '手机端右边栏',
'mobileRightbarHint': '显示原生右边栏入口;普通手机可按需关闭,折叠屏展开后使用更方便',
Expand Down Expand Up @@ -165,6 +168,9 @@ export const en = {
'pinInvalid': 'PIN must be 8–64 characters (letters and digits only)',
'pinCustomHint': 'custom PINs are not rotated on tunnel start',
'lanPinOff': '🔓 PIN off — scan & go, no PIN (LAN devices only; public still requires PIN)',
'trustClients': 'Remote settings (plugin config / models)',
'trustClientsOn': '✅ Phone/remote pages can edit plugin config and models.',
'trustClientsOff': '🔒 Remote pages are read-only: change settings on the computer itself (127.0.0.1).',
'lanStarting': 'Proxy starting…',
'mobileRightbar': 'Mobile right sidebar',
'mobileRightbarHint': 'Show the native right-sidebar entry; disable it for a compact phone header or keep it on for an unfolded display',
Expand Down
6 changes: 5 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { createPocketService } from './service.mjs';
import { installPocketRpc } from './web-rpc.js';
import { restartHost } from './restart.js';
import { advancedNoticeScript, DEFAULT_INJECT, classifyHost } from './proxy.mjs';
import { lanEnabled, setLanEnabled, lanAuthEnabled, setLanAuthEnabled, mobileRightbarEnabled, setMobileRightbarEnabled, lanIpOverride, setLanIpOverride, pinCustom, setPinCustom, tunnelMode, setTunnelMode, tunnelToken, setTunnelToken, tunnelHostname, setTunnelHostname, resetSettings, proxyPort, cloudflaredPath } from './settings.mjs';
import { lanEnabled, setLanEnabled, lanAuthEnabled, setLanAuthEnabled, mobileRightbarEnabled, setMobileRightbarEnabled, lanIpOverride, setLanIpOverride, pinCustom, setPinCustom, tunnelMode, setTunnelMode, tunnelToken, setTunnelToken, tunnelHostname, setTunnelHostname, resetSettings, proxyPort, cloudflaredPath, trustProxiedClients, setTrustProxiedClients } from './settings.mjs';

const name = 'dsh-pocket';
const inject = ['connection', 'webServer'];
Expand Down Expand Up @@ -291,6 +291,8 @@ export function apply(ctx, config = {}, internals = {}) {
internals,
getLanIpOverride: () => lanIpOverride(),
getLanEnabled: () => lanEnabled(),
// 经代理客户端信任开关(issue #58):代理每次请求实时读,切换立即生效
trustProxiedClients: () => trustProxiedClients(),
// 桌面端**不再**注入 dsh-desktop-* 标记(issue #76):
// - 旧版 dsh-plugin-desktop 缺 mode/platform 会抛错,当初正是为此加了补丁(issue #3/#4);
// - 但 2.0.3 起,mode 与 platform **同时缺失**时 parseDesktopClientEnvironment 直接返回
Expand Down Expand Up @@ -355,6 +357,8 @@ export function apply(ctx, config = {}, internals = {}) {
refreshLanToken: () => refreshLanToken(),
getLanAuthEnabled: () => lanAuthEnabled(),
setLanAuthEnabled: (on) => setLanAuthEnabled(on),
getTrustEnabled: () => trustProxiedClients(),
setTrustEnabled: (on) => setTrustProxiedClients(on),
getLanEnabled: () => lanEnabled(),
setLanEnabled: (on) => setLanEnabled(on),
getMobileRightbarEnabled: () => mobileRightbarEnabled(),
Expand Down
Loading