# dsh-web-ui

> 为 dsh web GUI 增加扫码配对的手机端和 PC 远程访问通道、已授权设备管理与一键公网隧道能力，并附带 dsh-web-ui 全家桶的版本自更新按钮。

## Metadata

- Author: [@zhu1090093659](https://github.com/zhu1090093659)
- Repo: <https://github.com/zhu1090093659/dsh-web-ui.git>
- GitHub: [zhu1090093659/dsh-web-ui](https://github.com/zhu1090093659/dsh-web-ui)
- Stars: 5,125
- Language: TypeScript
- License: [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
- Homepage: <https://gallery.dsh-market.com>
- Topics: `deepseek-harness`, `dsh`, `dsh-plugin`, `web-ui`
- Forks: 310
- Open Issues: 49
- Last push: 2026-08-20T14:37:38.000Z
- Added: 2026-08-20T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:zhu1090093659/dsh-web-ui/packages/dsh-remote-web-ui
```

## Wiki

## 一句话定位
为 dsh web GUI 增加一对扫码配对的远程访问面板：手机通过自己实现的 /m/ 小屏客户端操作工作区，电脑通过门控的 /remote/api 通道运行完整 Web GUI；并在侧边栏附带 dsh-web-ui 全家桶的一键更新按钮。

## 核心能力
- 在侧边栏底部（设置按钮旁）新增手机图标入口，悬停提示"远程访问"；面板显示一枚二维码并发出两条一次性配对链接（手机走 /m/，电脑走 /）以及停止/刷新/复制操作
- 配对机制为一次性令牌 + 设备 session：接受后另一条链接立即失效、令牌不可重用、自动过期；点"停止"立即撤销所有已配对设备的下一次请求
- 已授权设备面板按 User-Agent 推断设备名、显示在线/离线与最近活动时间，可单台取消配对；界面不展示设备 id 与原始 UA 作为凭据
- 默认开启 requirePairingForLan：局域网或公网隧道打开桌面 Web GUI 自动改走门控的 /remote/api，未配对设备直接看到全屏阻断页（仅 127.0.0.1 仍走 /api）
- 手机端独立 /m/ 界面：列表分页加载、消息按需拉取、实时流走 SSE、Markdown 渲染、模型与权限选择器、显示开关与上下文用量 chip，可作为 PWA 安装（仅缓存静态壳与离线页）
- 公网隧道支持两条路径：手动填 publicBaseUrl（如 Cloudflare named tunnel / Tailscale 虚拟接口），或打开 autoTunnel 让插件自带 cloudflared quick tunnel 自动铸出 https://*.trycloudflare.com URL
- 侧边栏提供 dsh-web-ui 全家桶一键自更新：检测 npm registry 上 @linxin666/dsh-* 包的新版本，确认后在所属 dsh profile 内执行 pnpm update --latest 并校验版本确实发生变动

## 技术实现
- **语言**: TypeScript（含 JSX）+ Node.js + React 18 双半区包
- **关键依赖**: `cloudflared`（自动公网隧道二进制）、`qrcode.react`（无 canvas 的 SVG 二维码）、`schemastery`（DSH 配置 schema 校验）、`zod`；运行时注入 React 18 作为 peer
- **架构模式**: 双半区 cordis bundle：`src/index.ts` 是 host 半区（pairing service + /api/pair + /m/api + /remote + /api/update 路由 + gate listener + 姿态探测），`src/client/index.ts` 是浏览器半区（侧边栏入口、配对面板、配对失败提醒、设置卡片、移动端 deep-link + heartbeat + 远程桌面通道）；cordis `inject=['webServer','apiProxy']`、`dsh.client.platform='web'`
- **入口文件**: `src/index.ts`（host 入口，`export const name='remote-web-ui'` + apply 挂载所有路由与 gate）、`src/client/index.ts`（浏览器入口，注册 `sidebar.remote`/`sidebar.footer.action`/`web-ui.plugin.item` 三个 slot）

## 适用场景
- 在通勤、家中或会议中，希望用手机继续处理电脑上的工作区、会话与聊天记录，又不想把电脑留在公共网络；
- 出差或临时在家，希望通过公网隧道从任意地点访问工作室里跑的 dsh web，并保持工作区与会话完全在本地不出网；
- 团队维护 dsh-web-ui 全家桶，希望管理员与终端用户都能在侧边栏一键把插件、皮肤全家桶更新到 latest，不再手动跑 pnpm update。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH | 通过 devDependencies 锁定到 `@deepseek-ai/dsh-client-* ^0.1.0-rc.8` | 需 SDK 暴露 `webServer`、`apiProxy`、`settings.section`、`sidebar.remote`/`sidebar.footer.action`、`api/gate` 等接缝；`api/gate` 监听器在已发布 SDK 线上暂不发出（src/index.ts:41-56） |
| Node | `^22.19.0` 或 `>=24.0.0` | 来自 `package.json:7-9` |
| 平台 | 跨平台 | 浏览器侧 `platform: 'web'`；自动公网隧道在 macOS / Linux / Windows 都可用（Windows 走 cmd.exe 解析 npm 安装的 .cmd shim）|
| 原生模块 | `cloudflared ^0.7.3` 二进制（postinstall 自动下载） | 也可被运行时下载覆盖以跳过 postinstall（README.zh.md:34）|
| 启动参数 | `--host 0.0.0.0` | 在局域网场景下必须；127.0.0.1 单绑定时面板显示明确提示不可用（除非配置 publicBaseUrl）|

## 安装方式
```bash
dsh plugin --profile web add github:zhu1090093659/dsh-web-ui/packages/dsh-remote-web-ui
```

## 配置项
| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| `enabled` | boolean | 主开关：关闭后移除侧边栏入口并停用配对路由与局域网栅栏 | `true` |
| `tokenTtlMs` | number | 一次性配对令牌有效期；超过后二维码失效 | `600000`（10 分钟）|
| `offlineAfterMs` | number | 已配对设备多久没心跳就判定离线 | `25000` |
| `maxDevices` | number | 同时保留的已配对设备上限；超出淘汰最旧 | `4`（范围 1–64）|
| `idleExpireMs` | number | 多少毫秒没活动就视为空闲过期（会被删除，必须重新扫码）| `604800000`（7 天）|
| `cookieName` | string | 携带已配对设备 id 的 Cookie 名；改名会让旧设备失效 | `dsh_pair` |
| `requirePairingForLan` | boolean | 局域网 / 隧道访问桌面 Web GUI 是否必须配对；关掉后桌面继续走普通 /api | `true` |
| `publicBaseUrl` | string | 公网隧道地址（如 `https://xxx.trycloudflare.com`）；非 http(s) URL 被忽略并告警；`autoTunnel` 打开时本字段被忽略 | 空 |
| `autoTunnel` | boolean | 启动插件自带的 Cloudflare quick tunnel，动态铸出公网地址并写到 QR base 与手机端的信任列表 | `false` |
| `mobileEnterToSend` | boolean | 手机聊天输入框的发送语义：true = Enter 发送、Shift+Enter 换行；false = Enter 换行、只能点发送按钮 | `true` |

## 常见问题

**Q: 这个插件能做什么？**

A: 给 dsh web GUI 增加扫码配对的远程访问——手机打开独立的 /m/ 小屏界面，电脑用完整 Web GUI 通过门控的 /remote/api 通道跑；同时侧边栏右下角多一个"检查更新"按钮，可一键更新 dsh-web-ui 全家桶。

**Q: 在家能用吗？需要什么前提？**

A: 仅本机回环（127.0.0.1）能立即使用；手机要访问电脑需要把 dsh web 启动为绑定全部接口（`dsh web --host 0.0.0.0`），否则面板会显示明确提示并拒绝铸出可扫的二维码（除非你已经在设置里填好公网地址或打开 autoTunnel）。

**Q: 安全模型是什么？配对令牌能反复用吗？**

A: 同一时刻只有一枚有效的一次性令牌；任何一台设备接受后另一条链接立即失效，再次使用已被接受的令牌会被拒绝；超过 tokenTtlMs（默认 10 分钟）也自动失效。点"停止"撤销全部已配对设备的会话，下一次请求即被 403。重启 dsh web 后设备 cookie 仍然有效，但当前二维码会作废，必须刷新。

**Q: 手机界面和小屏幕浏览桌面版有什么区别？**

A: 走的是插件自己实现的 /m/ 移动端小屏客户端，不是把桌面 Web GUI 塞进手机——列表分页、消息按需拉取、对话折叠（深度思考/工具调用变为可展开行）、输入框有模型选择、权限选择、显示开关与上下文用量 chip；能安装为 PWA，但 Service Worker 只缓存静态壳和离线页，运行时仍要求 DSH 在线。

**Q: 局域网里未配对的电脑直接访问会被挡住吗？**

A: 默认会。当 requirePairingForLan 打开（默认），任何从非回环地址（局域网或公网隧道）打开的桌面 Web GUI 会改走门控的 /remote/api，必须携带有有效设备配对 cookie；未配对电脑直接看到全屏阻断页，无任何工作区数据。"停止"按钮会立即切断已配对设备。

**Q: 自动公网隧道会暴露我的服务吗？需要账号吗？**

A: 是的，Cloudflare quick tunnel 让任何拿到 https://\*.trycloudflare.com URL 的人都能加载静态页，但所有数据通道（/m/api 与 /remote/api）都以已配对设备 cookie + 方法白名单作为真正的围栏——未配对调用一律 403。配置不需要账号或域名，cloudflared 二进制随包发布（依赖 `cloudflared ^0.7.3`）。

**Q: 一台手机能同时连吗？上限是多少？**

A: 可以，但有上限。maxDevices 配置项（默认 4，范围 1–64）控制同时保留的已配对设备会话数，超出时按最旧淘汰；7 天没有任何心跳或受门控请求的设备会自动失效。

**Q: 把 dsh web 部署在公网要注意什么？**

A: 插件会主动探测 SDK 的 /api 信任围栏——一旦发现 --trusted-host 让某主机绕过围栏、配对之外的来源也能读 /api，会在面板用红色横幅加 CRITICAL 日志告警；建议移除对应来源的 --trusted-host 并改用本插件的配对通道。Cloudflare quick tunnel / Tailscale Serve 等不转发 SSE 的隧道会让手机聊天实时消息降级为短间隔轮询；用 cloudflared named tunnel 或 ssh -L 端口转发才能保留 SSE。

## 上手难度
入门 — 安装后侧边栏底部出现手机图标，点开即得二维码；从本机到局域网再到公网的每一步都有面板提示与一键开关，普通用户按提示即可完成首次配对；如果需要配置 autoTunnel、publicBaseUrl 或自定义设备上限，则需要进入设置页"远程访问设置"卡片编辑。

## 已知问题与限制
- **撤销是逐请求生效的**：停止时已在途的设备请求继续完成，下一次请求即被 403（README.zh.md:173）
- **已配对设备会话默认落盘**：设备会话（不含一次性二维码 token）写入 `$DSH_HOME/remote-web-ui-devices.json`（0600，临时文件 + 原子 rename）；重启 dsh web 后 cookie 仍有效，但当前二维码需刷新。idleExpireMs 默认 7 天（README.zh.md:174）
- **Quick tunnel hostname 每次运行变化**：Cloudflare quick tunnel URL 每次 cloudflared 启动都不同，`publicBaseUrl` 必须随隧道重启更新；用 cloudflared named tunnel（域名托管在 Cloudflare）可获得稳定 hostname，也是持久安装 PWA 的必备（README.zh.md:120-122）
- **不转发 SSE 的隧道让实时消息降级为轮询**：Cloudflare quick tunnel / Tailscale Serve / 单端口 `tailscale serve` 都不转发 Server-Sent Events，手机在公网路径上的聊天会短暂轮询；手机其他功能不受影响（README.zh.md:121-122、src/mobile/mux.ts:277）
- **局域网 HTTP 无法安装 /m/ PWA**：手机要把 /m/ 装为 PWA 需要 secure context；`localhost` / `127.0.0.1` 可用于本机，但手机安装需 HTTPS；普通局域网 HTTP 仍可使用移动端远程控制，只是不能注册 Service Worker（README.zh.md:35）
- **pnpm 11 minimumReleaseAge 门禁会让一键更新静默跳过当日发布**：更新命令绿色退出但版本未变会报告"未更新成功"并给出 minimumReleaseAgeExclude 配置指引，而非误报成功（src/client/locales.ts:155-158、src/index.ts:336-353）
- **手机端 Markdown 不支持 KaTeX 公式**：移动端 bundle 用零依赖的 GFM Markdown 渲染器（标题/加粗/斜体/行内码/代码块/列表/表格/引用/链接/图片，先转义再白名单协议），KaTeX 公式暂不支持（README.zh.md:67、README.md:193）
- **姿态探测发现 /api 围栏敞开会发出 CRITICAL 日志**：任何非 403 的探测结果都会在插件日志打 CRITICAL 并在面板用红色横幅呈现（src/index.ts:394-398）

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-web-ui](https://deepseek-plugin.org/plugins/zhu1090093659/dsh-web-ui/packages/dsh-remote-web-ui)
Wiki generated by AI (model: `MiniMax-M3`)
