dsh-mobile 0.1.0-alpha.28 → 0.1.0-alpha.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -19,17 +19,15 @@
19
19
 
20
20
  <p align="center"><a href="https://github.com/saya-ch/dsh-mobile/releases"><strong>Download the Android app</strong></a></p>
21
21
 
22
- DSH Mobile adapts DeepSeek Harness for mobile interfaces and interactions with a dedicated layout that reuses its native conversation, Workspace, settings, and plugin components. An authenticated HTTPS LAN gateway keeps desktop, Android, and mobile browsers on the same Workspaces, sessions, messages, tools, settings, and event streams.
23
-
24
- It is a plugin that adapts DeepSeek Harness interfaces and interactions for mobile devices.
22
+ DSH Mobile is a DeepSeek Harness plugin that lets a mobile browser or the Android app connect over a protected LAN and keep using the same sessions, Workspaces, messages, and tools. It is a mobile entry point only; the DeepSeek Harness source is not modified.
25
23
 
26
24
  ## What it does
27
25
 
28
- - Continue using DSH from a phone with the same sessions, Workspaces, messages, tools, and settings as the desktop.
29
- - Provide a dedicated touch layout for the sidebar, composer, tool details, settings, and Workspace picker.
30
- - Connect through Android or a mobile browser using one protected LAN gateway, with discovery and IP-change recovery.
31
- - Edit the mobile layout, interactions, and features directly through a DeepSeek Harness conversation; open pages refresh in about one second.
32
- - Go beyond styling: add shortcuts, status panels, voice, camera, scanning, or other same-origin web features.
26
+ - Continue DSH work from a phone: sessions, tools, settings, and live state stay in sync.
27
+ - Edit the mobile layout, interactions, and features directly through a DeepSeek Harness conversation; open phone pages refresh in about one second — customize your mobile client by talking to DSH, not by writing code.
28
+ - A dedicated touch layout: session drawer, tool details, settings, and composer are reorganized for touch.
29
+ - Auto-discovery on the LAN; Wi-Fi, hotspot, or IP changes normally recover without re-pairing.
30
+ - Pair by scanning a QR code, pasting a pairing link, or entering a key — no 43-character key to type.
33
31
 
34
32
  ## Quick start
35
33
 
@@ -52,38 +50,57 @@ pnpm dsh --profile web
52
50
 
53
51
  `setup` automatically selects and remembers the current LAN. Wi-Fi, hotspot, and IP changes normally recover automatically; use `--address 192.168.x.x` only when automatic selection fails.
54
52
 
55
- Open **Mobile Access** in the lower-left DeepSeek Harness sidebar, enable it, and select **Create and copy key** or **Copy pairing link** to show a pairing QR code. In the Android app, tap **Scan QR code** and point the camera at the screen, or tap **Scan**, select the computer, and paste the key or pairing link — no 43-character key to type. The paired device remains trusted until it is revoked, expires, or its app data is cleared.
53
+ After starting DSH, open **Mobile Access** in the lower-left sidebar, enable it, then:
54
+
55
+ 1. Select **Create and copy key** or **Copy pairing link**; the panel shows a pairing QR code.
56
+ 2. In the Android app, tap **Scan QR code** and point the camera at the screen — or tap **Scan**, select the computer, and paste the key or pairing link.
57
+ 3. Pairing establishes persistent device trust; later launches do not ask again.
58
+
59
+ A paired device is fully trusted and can operate the DSH on the computer. Use this only on a trusted home or office LAN, or a trusted VPN.
60
+
61
+ The plugin does not modify the DeepSeek Harness source. Settings, certificates, devices, and customization files live under `$DSH_HOME/mobile-access/`.
56
62
 
57
- ## Connection options
63
+ ## App or mobile browser
58
64
 
59
65
  | Client | Best for | Notes |
60
66
  | --- | --- | --- |
61
- | Android app | Everyday use | Automatic discovery, no browser chrome, private certificate pinning |
62
- | Mobile browser | Temporary or cross-platform access | Open the HTTPS origin shown by Mobile Access |
67
+ | Android app | Everyday use | Auto-discovery; private certificate pinning inside the app, no manual browser trust step |
68
+ | Mobile browser | Temporary or cross-platform | Open the HTTPS origin shown by Mobile Access; trust the certificate manually on first visit |
63
69
 
64
- Discovery uses mDNS/NSD, UDP announcements and queries, plus HTTPS probing. It publishes only the device name, address, port, protocol version, and stable `instanceId`. Keys and device tokens are never discoverable. IP changes do not require another Android pairing.
70
+ Discovery uses mDNS/NSD, UDP announcements and queries, plus HTTPS probing. It publishes only the device name, address, port, protocol version, and stable `instanceId` — never keys or device tokens. IP changes do not require another pairing.
65
71
 
66
- For a browser's first connection, open pairing on the computer, then select **Copy pairing link** and open that link on the phone browser — the pairing code is filled in automatically. Alternatively, visit `/mobile-access/pair` on the shown HTTPS origin and manually enter the 43-character pairing code after the key's final dot. The browser stores a revocable device credential after pairing.
72
+ For a browser's first connection, open pairing on the computer, then select **Copy pairing link** and open that link on the phone — the pairing code is prefilled. Alternatively, visit `/mobile-access/pair` on the shown HTTPS origin and enter the 43-character pairing code after the key's final dot. The browser stores a revocable device credential after pairing.
67
73
 
68
74
  ## Mobile UI
69
75
 
70
- Phones use a dedicated layout shell, so the main layout no longer depends on the desktop three-column DOM. Native feature components retain a small mobile adaptation layer. You can freely redesign the page structure and extend its interactions through **Customize from DeepSeek Harness**. By default, the session sidebar becomes a drawer, details open as an overlay, settings use a single-column layout, and the native composer is adapted for touch. Adding a Workspace browses computer folders inside the phone page. Images can come from the phone or the computer.
76
+ Phones use a dedicated layout shell that no longer depends on the desktop three-column DOM, while native components keep a small touch-adaptation layer:
71
77
 
72
- The Android app is a thin Kotlin WebView shell and contains no frontend copy.
78
+ - A workspace-and-session drawer opens from the top-left.
79
+ - Conversations, traces, tool details, and Session logs keep their full capabilities.
80
+ - Settings use top-level tabs and a single column.
81
+ - The composer keeps command, permission, model, context, image, and send controls.
82
+ - **Add Workspace** browses computer folders inside the phone page instead of opening a system picker on the computer.
73
83
 
74
- For compatibility diagnosis, append `?frontend=stock` to the browser URL to temporarily use the previous desktop-page adaptation.
84
+ The Android app is a thin Kotlin WebView shell and contains no frontend copy; mobile browsers load the same page. For compatibility diagnosis, append `?frontend=stock` to the browser URL to temporarily use the previous desktop-page adaptation.
75
85
 
76
86
  ## Customize from DeepSeek Harness
77
87
 
78
- Customization is intentionally open-ended: use your own ideas to arrange the mobile layout, interactions, and features instead of staying within the default style.
79
-
80
- Ask DeepSeek Harness to edit:
88
+ Default files:
81
89
 
82
90
  ```text
83
91
  $DSH_HOME/mobile-access/mobile.css
84
92
  $DSH_HOME/mobile-access/mobile.js
85
93
  ```
86
94
 
95
+ Ask DeepSeek Harness to edit them, for example:
96
+
97
+ ```text
98
+ Edit $DSH_HOME/mobile-access/mobile.css and mobile.js to turn the mobile
99
+ client into a one-handed development console: add a bottom shortcut bar,
100
+ a session status panel, and a press-and-hold voice entry. Narrow screens only;
101
+ do not modify the DSH source.
102
+ ```
103
+
87
104
  Changes are applied to open Android and browser pages in about one second. Customization is not limited to colors: `mobile.js` can add navigation, shortcuts, dashboards, camera, voice, scanning, and complete interactions with same-origin DeepSeek Harness APIs.
88
105
 
89
106
  ## How it works
@@ -96,18 +113,22 @@ flowchart LR
96
113
 
97
114
  The Host face owns discovery, pairing, HTTPS, and the proxy. The Client face replaces only the phone layout entry while reusing native feature plugins. Neither the DeepSeek Harness source nor its desktop page on port 3080 is modified.
98
115
 
116
+ ## Security
117
+
118
+ - Use the plugin only on a trusted LAN or trusted VPN; never expose it to the public Internet.
119
+ - A paired device is a fully trusted DeepSeek Harness operator and can run tools on the computer; revoke lost devices from the computer.
120
+ - The LAN gateway listens only while Mobile Access is enabled; with it off, DSH keeps running normally on the computer.
121
+
122
+ See [SECURITY.md](SECURITY.md).
123
+
99
124
  ## Compatibility
100
125
 
101
126
  | DSH Mobile | Verified DeepSeek Harness releases |
102
127
  | --- | --- |
103
- | `0.1.0-alpha.28` | `0.1.0-rc.5`, `0.1.0-rc.6`, `0.1.0-rc.7` |
128
+ | `0.1.0-alpha.29` | `0.1.0-rc.5`, `0.1.0-rc.6`, `0.1.0-rc.7` |
104
129
 
105
130
  At startup, the plugin verifies the DSH Host version and the frontend dependencies required by the mobile layout. An unverified release fails with a clear error instead of serving a broken page. CI also tracks the DSH main branch layout slots and mobile semantic markers. If a DSH upgrade reports an incompatibility, update DSH Mobile first.
106
131
 
107
- ## Security
108
-
109
- Use the plugin only on a trusted LAN or trusted VPN. Never expose it directly to the public Internet. A paired device is a fully trusted DeepSeek Harness operator. Revoke lost devices from the computer. See [SECURITY.md](SECURITY.md).
110
-
111
132
  ## Uninstall
112
133
 
113
134
  ```powershell
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/saya-ch/dsh-mobile/main/assets/brand/repository-hero.png" alt="使用手机连接 DeepSeek Harness" width="100%">
2
+ <img src="https://raw.githubusercontent.com/saya-ch/dsh-mobile/main/assets/brand/repository-hero.png" alt="用手机使用电脑中的 DeepSeek Harness" width="100%">
3
3
  </p>
4
4
 
5
5
  <h1 align="center">DSH Mobile</h1>
@@ -15,8 +15,8 @@
15
15
 
16
16
  <p align="center">
17
17
  <a href="#快速开始">快速开始</a> ·
18
- <a href="#连接方式">连接方式</a> ·
19
- <a href="#自定义移动布局与功能">自定义</a> ·
18
+ <a href="#app-与手机浏览器">App 与浏览器</a> ·
19
+ <a href="#在-dsh-对话中自定义">自定义</a> ·
20
20
  <a href="README.en.md">English</a>
21
21
  </p>
22
22
 
@@ -24,15 +24,15 @@
24
24
 
25
25
  <p align="center"><a href="https://github.com/saya-ch/dsh-mobile/releases"><strong>下载 Android App</strong></a></p>
26
26
 
27
- DSH Mobile 为 DeepSeek Harness 提供移动端界面适配与交互优化,采用专属移动端布局,并复用原生的对话、工作区、设置和插件组件。它通过受保护的局域网 HTTPS 网关,让 Android App 或手机浏览器访问电脑端同一份工作区、会话、消息和运行状态。
27
+ DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App 在受保护的局域网内连接电脑,继续使用同一份会话、工作区、消息和工具。它只作为移动入口接入,不修改 DeepSeek Harness 源码。
28
28
 
29
29
  ## 能做什么
30
30
 
31
- - 在手机上继续使用电脑端的 DSH:会话、工作区、消息、工具和设置实时同步。
32
- - 使用专属移动布局,适配侧边栏、输入栏、工具详情、设置和工作区选择。
33
- - 通过 Android App 或手机浏览器访问同一个安全的局域网网关,自动发现设备并适应 IP 变化。
34
- - 在 DeepSeek Harness 对话中直接修改移动端布局、交互和功能,保存后手机约一秒内刷新。
35
- - 自定义不局限于样式:可以添加快捷操作、状态面板、语音、相机、扫码或其他同源网页功能。
31
+ - 在手机上继续电脑端的 DSH 工作:会话、工具、设置和运行状态实时同步。
32
+ - 在 DSH 对话中直接修改手机端布局、交互和功能,打开的手机页面约一秒内刷新——用对话定制自己的移动端。
33
+ - 专属移动布局:会话抽屉、工具详情、设置和输入栏都按触屏重新组织。
34
+ - 自动发现局域网内的 DSH;切换 Wi-Fi、热点或 IP 后通常自动恢复,无需重新配对。
35
+ - 扫码、配对链接或密钥均可配对。
36
36
 
37
37
  ## 快速开始
38
38
 
@@ -55,32 +55,31 @@ pnpm dsh --profile web
55
55
 
56
56
  `setup` 会自动选择并记住当前局域网。切换 Wi-Fi、热点或 IP 后通常会自动恢复;仅在自动选择失败时使用 `--address 192.168.x.x`。
57
57
 
58
- 启动后:
58
+ 启动后,在 DeepSeek Harness 左下角打开“移动访问”并确认已开启,然后:
59
59
 
60
- 1. 在 DeepSeek Harness 左下角打开“移动访问”,确认功能已开启。
61
- 2. 点击“生成并复制密钥”或“复制配对链接”,面板会显示配对二维码。
62
- 3. Android App 点击“扫码配对”,把手机对准电脑屏幕上的二维码即可完成配对;也可以点击“扫描”选择电脑后粘贴密钥或配对链接,无需手输 43 位密钥。
63
- 4. 配对完成后会建立持久设备信任,以后打开 App 无需重复输入。
60
+ 1. 点击“生成并复制密钥”或“复制配对链接”,面板会显示配对二维码。
61
+ 2. Android App 点击“扫码配对”,把手机对准电脑屏幕上的二维码即可完成配对;也可以点击“扫描”选择电脑后粘贴密钥或配对链接。
62
+ 3. 配对完成后会建立持久设备信任,以后打开 App 无需重复输入。
64
63
 
65
- 插件不会修改 DeepSeek Harness 源码。设置、证书、设备和自定义文件保存在 `$DSH_HOME/mobile-access/`。
64
+ 配对设备被视为完全信任,可以操作电脑上的 DSH;建议只在可信的家庭、办公局域网或可信 VPN 中使用。
66
65
 
67
- ## 连接方式
66
+ 插件不会修改 DeepSeek Harness 源码。设置、证书、设备和自定义文件保存在 `$DSH_HOME/mobile-access/`。
68
67
 
68
+ ## App 与手机浏览器
69
69
 
70
- | 方式 | 适合场景 | 说明 |
71
- | ------------- | ------------------ | ------------------------------------------ |
72
- | Android App | 日常使用 | 自动发现、无浏览器栏、App 内私有证书固定 |
73
- | 手机浏览器 | 临时或跨平台访问 | 打开“移动访问”卡片显示的 HTTPS 地址 |
74
70
 
75
- Android 同时使用 mDNS/NSD、UDP 公告、主动 UDP 查询和 HTTPS 探测发现 DeepSeek Harness。发现广播设备名、地址、端口、协议版本和稳定 `instanceId`,不会广播密钥或令牌。
71
+ | 方式 | 适合场景 | 说明 |
72
+ | ------------- | ------------------ | ------------------------------------------------------------------------- |
73
+ | Android App | 日常使用 | 自动发现;App 内保存私有证书信任,无需在浏览器手动信任证书 |
74
+ | 手机浏览器 | 临时或跨平台访问 | 打开“移动访问”卡片显示的 HTTPS 地址;首次连接需在浏览器手动信任该证书 |
76
75
 
77
- App 配对后保存可撤销的设备凭据和私有证书信任;Wi-Fi、热点或 DHCP 导致 IP 变化时不需要重新配对。手机浏览器不能使用 App 的私有信任,因此需要浏览器本身信任该 HTTPS 证书。
76
+ Android 使用 mDNS/NSD、UDP 公告与主动查询、HTTPS 探测等方式发现 DeepSeek Harness。发现广播设备名、地址、端口、协议版本和稳定 `instanceId`,不会广播密钥或令牌。IP 变化后不需要重新配对。
78
77
 
79
- 新浏览器首次访问时,在电脑端开启配对,然后点击“复制配对链接”并把链接发到手机浏览器打开,配对码会自动填入;也可以打开 HTTPS 地址中的 `/mobile-access/pair`,手动输入密钥最后一段的 43 位配对码。配对完成后,浏览器会保存可撤销的设备凭据。
78
+ 手机浏览器首次连接:在电脑端开启配对后,点击“复制配对链接”并把链接发到手机浏览器打开,配对码会自动填入;也可以打开 HTTPS 地址中的 `/mobile-access/pair`,手动输入密钥最后一段的 43 位配对码。配对完成后,浏览器会保存可撤销的设备凭据。
80
79
 
81
80
  ## 移动端界面
82
81
 
83
- 手机端使用插件自带的独立布局外壳,主布局不再依赖桌面三栏 DOM;原生功能组件保留少量移动端适配。你也可以通过“自定义移动布局与功能”继续调整页面结构并扩展交互:
82
+ 手机端使用独立的移动布局外壳,不再依赖桌面三栏 DOM;原生的对话、工具、设置等组件保留触屏适配:
84
83
 
85
84
  - 左上角打开工作区与会话抽屉。
86
85
  - 对话、轨迹、工具详情和 Session log 保持原有能力。
@@ -88,22 +87,18 @@ App 配对后保存可撤销的设备凭据和私有证书信任;Wi-Fi、热
88
87
  - 输入栏保留命令、权限、模型、上下文、图片和发送控件。
89
88
  - “添加工作区”在手机上展示电脑目录,不会在电脑上弹系统选择器。
90
89
 
91
- Android App 只是 Kotlin WebView 薄壳,不内置另一份网页。手机浏览器访问的也是同一页面。
92
-
93
- 需要排查兼容性时,可在浏览器地址后追加 `?frontend=stock`,临时回到旧的桌面页面适配模式。
90
+ Android App 只是 Kotlin WebView 薄壳,不内置另一份网页;手机浏览器访问的也是同一页面。需要排查兼容性时,可在浏览器地址后追加 `?frontend=stock`,临时回到旧的桌面页面适配模式。
94
91
 
95
- ## 自定义移动布局与功能
92
+ ## 在 DSH 对话中自定义
96
93
 
97
- 自定义程度:你可以自由布置移动端的布局、交互和功能,而不必局限于默认样式。
98
-
99
- 直接在 DeepSeek Harness 对话中提出修改即可。默认文件:
94
+ 默认文件:
100
95
 
101
96
  ```text
102
97
  $DSH_HOME/mobile-access/mobile.css
103
98
  $DSH_HOME/mobile-access/mobile.js
104
99
  ```
105
100
 
106
- 例如对 DeepSeek Harness 说:
101
+ 直接在 DeepSeek Harness 对话中提出修改即可,例如:
107
102
 
108
103
  ```text
109
104
  请编辑 $DSH_HOME/mobile-access/mobile.css 和 mobile.js,
@@ -111,7 +106,7 @@ $DSH_HOME/mobile-access/mobile.js
111
106
  会话状态面板和长按语音入口。只影响窄屏,不修改 DSH 源码。
112
107
  ```
113
108
 
114
- 保存后,已打开的 App 和浏览器通常会在一秒内应用变化。自定义能力不限于配色:`mobile.js` 可以添加导航、快捷操作、状态面板、相机、语音、扫码,以及调用同源 DSH API 的完整交互。
109
+ 保存后,已打开的 App 和浏览器通常会在约一秒内应用变化。自定义不限于配色:`mobile.js` 可以添加导航、快捷操作、状态面板、相机、语音、扫码,以及调用同源 DSH API 的完整交互。
115
110
 
116
111
  ## 工作原理
117
112
 
@@ -122,26 +117,25 @@ flowchart LR
122
117
  DSH -->|"同一工作区、会话和事件流"| Phone
123
118
  ```
124
119
 
125
- 插件包含两部分:Host face 提供发现、配对、HTTPS 和回环代理;Client face 替换移动端的布局入口,并复用原生功能插件。DeepSeek Harness 的源码和 3080 桌面页面都不会被修改,安装和卸载仍完全通过插件机制完成。
120
+ 插件包含两部分:Host face 提供发现、配对、HTTPS 和回环代理;Client face 替换移动端的布局入口,并复用原生功能插件。DeepSeek Harness 的源码和 3080 桌面页面都不会被修改,安装和卸载完全通过插件机制完成。
121
+
122
+ ## 安全
123
+
124
+ - 仅在可信家庭、办公局域网或可信 VPN 中使用,不要转发到公网。
125
+ - 配对设备拥有控制电脑端 DeepSeek Harness 的能力,应视为完全可信设备;丢失手机后应在电脑端撤销设备。
126
+ - 移动网关开启时才监听局域网;关闭后 DeepSeek Harness 仍正常在电脑本机运行。
127
+
128
+ 完整说明见 [SECURITY.md](SECURITY.md)。
126
129
 
127
130
  ## 兼容性
128
131
 
129
132
 
130
133
  | DSH Mobile | 已验证的 DeepSeek Harness |
131
134
  | ------------------ | ------------------------------------------ |
132
- | `0.1.0-alpha.28` | `0.1.0-rc.5`、`0.1.0-rc.6`、`0.1.0-rc.7` |
135
+ | `0.1.0-alpha.29` | `0.1.0-rc.5`、`0.1.0-rc.6`、`0.1.0-rc.7` |
133
136
 
134
137
  插件会在启动时检查 DSH Host 版本和移动布局所需的前端依赖;遇到未经验证的版本会直接给出错误,不会带着不兼容页面继续启动。CI 也会持续检查 DSH 主分支的布局插槽和移动端语义标记。升级 DSH 后如遇兼容提示,请先升级 DSH Mobile。
135
138
 
136
- ## 安全
137
-
138
- - 仅在可信家庭、办公局域网或可信 VPN 中使用,不要转发到公网。
139
- - 配对设备拥有控制电脑端 DeepSeek Harness 的能力,应视为完全可信设备。
140
- - 移动网关开启时才监听局域网;关闭后 DeepSeek Harness 仍正常在电脑本机运行。
141
- - 丢失手机后应在电脑端撤销设备。
142
-
143
- 完整说明见 [SECURITY.md](SECURITY.md)。
144
-
145
139
  ## 卸载
146
140
 
147
141
  保留设备和自定义数据:
package/lib/client.js CHANGED
@@ -394,9 +394,19 @@ window.__ModuleLoader__.load({
394
394
  const linkPair = element("button", "dsh-mobile-control__secondary");
395
395
  linkPair.type = "button";
396
396
  linkPair.textContent = "复制配对链接";
397
+ const manageRow = element("div", "dsh-mobile-control__manage-row");
398
+ const manageDevices = element("button", "dsh-mobile-control__manage");
399
+ manageDevices.type = "button";
400
+ manageDevices.textContent = "管理配对设备";
401
+ const resetAll = element("button", "dsh-mobile-control__manage");
402
+ resetAll.type = "button";
403
+ resetAll.textContent = "清除所有设备";
404
+ manageRow.append(manageDevices, resetAll);
405
+ const devicePanel = element("div", "dsh-mobile-control__devices");
406
+ devicePanel.hidden = true;
397
407
  header.append(title, close);
398
408
  actions.append(toggle, pair, linkPair);
399
- panel.append(header, access, qrBox, status, actions);
409
+ panel.append(header, access, qrBox, status, actions, manageRow, devicePanel);
400
410
  root.append(panel);
401
411
  document.body.append(root);
402
412
  let running = false;
@@ -414,9 +424,12 @@ window.__ModuleLoader__.load({
414
424
  accessLink.title = origin;
415
425
  status.classList.toggle("is-running", running);
416
426
  status.textContent = running ? "移动访问已开启。" : "已关闭。DSH 仍只在本机可用。";
427
+ if (!running) qrBox.hidden = true;
417
428
  toggle.textContent = running ? "关闭移动访问" : "开启移动访问";
418
429
  pair.disabled = !running;
419
430
  linkPair.disabled = !running;
431
+ manageDevices.disabled = !running;
432
+ resetAll.disabled = !running;
420
433
  };
421
434
  const showQr = (svg) => {
422
435
  qrBox.replaceChildren();
@@ -457,6 +470,68 @@ window.__ModuleLoader__.load({
457
470
  linkPair.disabled = !running;
458
471
  });
459
472
  };
473
+ toggle.addEventListener("click", () => {
474
+ toggle.disabled = true;
475
+ requestJson("/api/mobile-access/control", {
476
+ method: "POST",
477
+ body: JSON.stringify({ running: !running })
478
+ }).then(render, (error) => {
479
+ status.textContent = String(error);
480
+ }).finally(() => {
481
+ toggle.disabled = false;
482
+ });
483
+ });
484
+ const formatTime = (ms) => typeof ms === "number" ? new Date(ms).toLocaleString() : "";
485
+ const renderDevices = (data) => {
486
+ const devices = Array.isArray(data.devices) ? data.devices : [];
487
+ devicePanel.replaceChildren();
488
+ if (devices.length === 0) {
489
+ const empty = element("p", "dsh-mobile-control__device-empty");
490
+ empty.textContent = "暂无配对设备。";
491
+ devicePanel.append(empty);
492
+ return;
493
+ }
494
+ for (const device of devices) {
495
+ const row = element("div", "dsh-mobile-control__device");
496
+ const label = element("span", "dsh-mobile-control__device-label");
497
+ label.textContent = typeof device.label === "string" ? device.label : "设备";
498
+ const meta = element("span", "dsh-mobile-control__device-meta");
499
+ meta.textContent = `到期 ${formatTime(device.expiresAt)}`;
500
+ const revoke = element("button", "dsh-mobile-control__device-revoke");
501
+ revoke.type = "button";
502
+ revoke.textContent = "撤销";
503
+ const id = typeof device.id === "string" ? device.id : "";
504
+ revoke.addEventListener("click", () => {
505
+ requestJson("/api/mobile-access/devices/revoke", {
506
+ method: "POST",
507
+ body: JSON.stringify({ deviceId: id })
508
+ }).then(loadDevices, (error) => {
509
+ status.textContent = String(error);
510
+ });
511
+ });
512
+ row.append(label, meta, revoke);
513
+ devicePanel.append(row);
514
+ }
515
+ };
516
+ const loadDevices = () => {
517
+ requestJson("/api/mobile-access/devices").then(renderDevices, (error) => {
518
+ status.textContent = String(error);
519
+ });
520
+ };
521
+ manageDevices.addEventListener("click", () => {
522
+ const show = devicePanel.hidden;
523
+ devicePanel.hidden = !show;
524
+ if (show) loadDevices();
525
+ });
526
+ resetAll.addEventListener("click", () => {
527
+ if (!window.confirm("确定要移除所有配对设备吗?此操作会立即终止已连接设备。")) return;
528
+ requestJson("/api/mobile-access/devices/reset", {
529
+ method: "POST",
530
+ body: JSON.stringify({ confirm: true })
531
+ }).then(loadDevices, (error) => {
532
+ status.textContent = String(error);
533
+ });
534
+ });
460
535
  pair.addEventListener("click", () => {
461
536
  pair.disabled = true;
462
537
  openPairing("key");
@@ -586,8 +661,9 @@ window.__ModuleLoader__.load({
586
661
  .dsh-mobile-control__header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}.dsh-mobile-control__panel h2{margin:0;font-size:17px;line-height:24px}.dsh-mobile-control__close{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:0;border-radius:10px;background:transparent;color:inherit;font-size:24px;line-height:1;cursor:pointer}.dsh-mobile-control__close:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}
587
662
  .dsh-mobile-control__access{display:flex;align-items:baseline;gap:6px;min-width:0;margin:0 0 12px}.dsh-mobile-control__access[hidden]{display:none}.dsh-mobile-control__access-label{flex:none;color:var(--dsw-alias-label-secondary,#606873);white-space:nowrap}.dsh-mobile-control__access-label::after{content:":"}.dsh-mobile-control__access-link{min-width:0;overflow:hidden;color:#2563eb;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__access-link:hover{text-decoration:underline}.dsh-mobile-control__qr{display:flex;justify-content:center;margin:0 0 12px}.dsh-mobile-control__qr[hidden]{display:none}.dsh-mobile-control__qr img{border-radius:12px;background:#fff;padding:8px}
588
663
  .dsh-mobile-control__status{margin:0 0 14px;overflow-wrap:anywhere;color:var(--dsw-alias-label-secondary,#606873)}.dsh-mobile-control__status::before{display:inline-block;width:8px;height:8px;margin-right:7px;border-radius:50%;background:#98a1ad;content:""}.dsh-mobile-control__status.is-running::before{background:#16a36a}.dsh-mobile-control__status.is-key{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;word-break:break-all}
589
- .dsh-mobile-control__actions{display:flex;flex-wrap:wrap;gap:8px}.dsh-mobile-control__actions button{min-height:40px;padding:8px 12px;border-radius:11px;font:inherit;cursor:pointer}.dsh-mobile-control__secondary{border:1px solid var(--dsw-alias-border-normal,#cfd5dd);background:transparent;color:inherit}.dsh-mobile-control__primary{border:1px solid #2563eb;background:#2563eb;color:#fff}.dsh-mobile-control__actions button:disabled{cursor:not-allowed;opacity:.45}
664
+ .dsh-mobile-control__actions{display:flex;flex-wrap:nowrap;gap:6px}.dsh-mobile-control__actions button{flex:1 1 0;min-width:0;min-height:40px;padding:8px 4px;border-radius:10px;font:12px/1.2 system-ui;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dsh-mobile-control__secondary{border:1px solid var(--dsw-alias-border-normal,#cfd5dd);background:transparent;color:inherit}.dsh-mobile-control__primary{border:1px solid #2563eb;background:#2563eb;color:#fff}.dsh-mobile-control__actions button:disabled{cursor:not-allowed;opacity:.45}
590
665
  .dsh-mobile-control__trigger{box-sizing:border-box;display:flex;align-items:center;gap:8px;width:calc(100% + 8px);height:34px;margin:4px -4px;padding:6px 2px 6px 10px;border:0;border-radius:12px;background:transparent;color:var(--dsw-alias-label-primary,#16181d);font:14px/22px system-ui;cursor:pointer}.dsh-mobile-control__trigger:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}.dsh-mobile-control__trigger.is-rail{width:36px;height:36px;margin:8px 0 10px;padding:0;justify-content:center;border-radius:50%}.dsh-mobile-control__trigger-icon{position:relative;box-sizing:border-box;flex:none;width:14px;height:19px;border:1.7px solid currentColor;border-radius:3px}.dsh-mobile-control__trigger-icon::after{position:absolute;right:4px;bottom:2px;width:4px;height:1.5px;border-radius:2px;background:currentColor;content:""}.dsh-mobile-control__trigger-label{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
666
+ .dsh-mobile-control__manage-row{display:flex;justify-content:space-between;gap:8px;margin-top:10px}.dsh-mobile-control__manage{flex:1 1 0;min-width:0;min-height:34px;padding:6px 8px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:transparent;color:inherit;font:12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__devices{margin-top:10px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:10px;padding:8px;max-height:220px;overflow-y:auto}.dsh-mobile-control__device-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px;margin:0}.dsh-mobile-control__device{display:flex;align-items:center;gap:8px;padding:6px 2px}.dsh-mobile-control__device + .dsh-mobile-control__device{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__device-label{flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.dsh-mobile-control__device-meta{flex:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;white-space:nowrap}.dsh-mobile-control__device-revoke{flex:none;min-height:28px;padding:4px 8px;border:1px solid #dc2626;border-radius:8px;background:transparent;color:#dc2626;font:12px/1.2 system-ui;cursor:pointer}
591
667
  `;
592
668
  /** Mount the desktop control or mobile feature enhancements. */
593
669
  function apply(ctx) {
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["createElement"],"sources":["../src/native-mobile.ts","../src/client.ts"],"sourcesContent":["/** Mobile feature and compatibility rules applied to DSH React surfaces. */\nexport const NATIVE_MOBILE_STYLES = `\n@media (max-width:720px) {\n html.dsh-native-mobile-active,html.dsh-native-mobile-active body { width:100%; height:100%; overflow:hidden; }\n html.dsh-native-mobile-active { --dsh-mobile-motion-duration:200ms; --dsh-mobile-motion-ease:cubic-bezier(.22,1,.36,1); }\n html.dsh-native-mobile-active :is(a,button,[role=\"button\"],[role=\"tab\"],[tabindex]) { -webkit-tap-highlight-color:transparent; }\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [role=\"treeitem\"] { -webkit-tap-highlight-color:transparent; touch-action:manipulation; }\n html.dsh-native-mobile-active[data-dsh-mobile-input=\"touch\"] :is(a,button,[role=\"button\"],[role=\"tab\"],[tabindex]):focus { outline:none !important; }\n html.dsh-native-mobile-active [role=\"tooltip\"] { display:none !important; }\n /* Touch has no persistent hover affordance: keep workspace rows neutral after a tap. */\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] { --dsw-alias-interactive-bg-hover:transparent !important; }\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [role=\"treeitem\"]:is(:hover,:active,:focus,[aria-selected=\"true\"]),\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*=\"_sessionRow\"][class*=\"_selected\"],\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*=\"_searchResultRow\"][class*=\"_selected\"] { background:transparent !important; outline:0 !important; box-shadow:none !important; }\n /* Sidebar row menus are hover-only on desktop. Touch has no hover, so keep\n the ellipsis action visible and give it a reliable hit target. */\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*=\"_rowActions\"] { display:inline-flex !important; align-items:center !important; gap:8px !important; }\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*=\"_sessionRow\"] [class*=\"_time\"] { display:none !important; }\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*=\"_rowActions\"] button { box-sizing:border-box !important; width:32px !important; min-width:32px !important; height:32px !important; min-height:32px !important; }\n [data-dsh-mobile-frame] { grid-template-columns:0 minmax(0,1fr) 0 !important; width:100% !important; height:100dvh !important; overflow:hidden !important; }\n [data-dsh-mobile-center] { grid-column:2 !important; width:100vw !important; min-width:0 !important; }\n [data-dsh-mobile-center] > * { min-width:0 !important; }\n [data-dsh-mobile-header] { box-sizing:border-box !important; width:calc(100% - 16px) !important; margin:0 8px !important; min-width:0; padding-top:4px !important; padding-right:8px !important; padding-left:42px !important; }\n [data-dsh-mobile-header] [class*=\"_titleRow\"] { box-sizing:border-box !important; display:flex !important; align-items:center !important; min-width:0; min-height:32px !important; height:32px !important; gap:6px !important; padding:0 6px !important; }\n [data-dsh-mobile-header] [class*=\"_titleCluster\"] { min-width:0; }\n [data-dsh-mobile-header] [class*=\"_crumbs\"] { min-width:0; overflow:hidden; }\n [data-dsh-mobile-header] [class*=\"_crumb\"] { max-width:46vw; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n [data-dsh-mobile-header] [class*=\"_headerActions\"] { min-width:0; overflow-x:auto; scrollbar-width:none; }\n [data-dsh-mobile-header] [class*=\"_headerActions\"]::-webkit-scrollbar { display:none; }\n [data-dsh-mobile-header] [class*=\"_headerUtilities\"] { gap:2px !important; }\n [data-dsh-mobile-header] [class*=\"_sessionLogButton\"] { width:40px; min-width:40px; padding:0 !important; overflow:hidden; color:transparent; font-size:0 !important; }\n [data-dsh-mobile-header] [class*=\"_sessionLogButton\"] > * { display:none !important; }\n [data-dsh-mobile-header] [class*=\"_sessionLogButton\"]::after { color:var(--dsw-text, #171a21); content:\"日志\"; font-size:11px; font-weight:600; }\n [data-dsh-mobile-header] [class*=\"_tabs\"] { box-sizing:border-box !important; width:max-content !important; max-width:calc(100% - 58px) !important; min-height:28px !important; height:28px !important; margin-top:0 !important; padding-left:6px !important; padding-right:6px !important; overflow-x:auto; scrollbar-width:none; }\n [data-dsh-mobile-header] [class*=\"_tab\"] { padding-bottom:5px !important; }\n [data-dsh-mobile-header] [class*=\"_tabs\"]::-webkit-scrollbar { display:none; }\n [data-dsh-mobile-sidebar] { position:fixed !important; z-index:240 !important; inset:0 auto 0 0 !important; width:0 !important; overflow:visible !important; }\n [data-dsh-mobile-sidebar-root] { position:fixed !important; z-index:241 !important; inset:0 auto 0 0 !important; height:100dvh !important; transition:width 180ms var(--dsh-mobile-motion-ease),box-shadow 180ms ease !important; }\n [data-dsh-mobile-sidebar][data-open=\"true\"] [data-dsh-mobile-sidebar-root] { width:min(88vw,340px) !important; box-shadow:18px 0 46px rgb(15 23 42 / 18%); }\n [data-dsh-mobile-sidebar][data-open=\"false\"] [data-dsh-mobile-sidebar-root] { width:0 !important; border:0 !important; background:transparent !important; box-shadow:none !important; overflow:visible !important; }\n [data-dsh-mobile-sidebar][data-open=\"false\"] [data-dsh-mobile-sidebar-root] > :not(:has([data-dsh-mobile-toggle])) { display:none !important; }\n [data-dsh-mobile-sidebar][data-open=\"false\"] [data-dsh-mobile-sidebar-root] > :has([data-dsh-mobile-toggle]) { position:fixed !important; z-index:244 !important; top:env(safe-area-inset-top) !important; left:0 !important; box-sizing:border-box !important; width:50px !important; height:52px !important; padding:4px !important; border:0 !important; background:transparent !important; }\n [data-dsh-mobile-sidebar][data-open=\"false\"] [data-dsh-mobile-sidebar-root] > :has([data-dsh-mobile-toggle]) > :not([data-dsh-mobile-toggle]) { display:none !important; }\n [data-dsh-mobile-toggle] { width:44px !important; height:44px !important; min-width:44px !important; min-height:44px !important; }\n [data-dsh-mobile-sidebar][data-open=\"false\"] [data-dsh-mobile-toggle] > svg[class*=\"_railFish\"] { transform:translateY(-4px) !important; }\n .dsh-native-mobile-backdrop { position:fixed; z-index:235; inset:0; border:0; background:rgb(15 23 42 / 32%); }\n .dsh-native-mobile-backdrop:not([hidden]) { animation:dsh-mobile-fade-in var(--dsh-mobile-motion-duration) ease-out; }\n .dsh-native-mobile-backdrop[hidden] { display:none; }\n [data-dsh-mobile-details] { position:fixed !important; z-index:250 !important; inset:0 0 0 auto !important; width:min(94vw,460px) !important; max-width:none !important; transform:translateX(100%); transition:transform var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); background:var(--dsw-bg, #fff); box-shadow:-18px 0 46px rgb(15 23 42 / 18%); }\n [data-dsh-mobile-details][data-open=\"true\"] { transform:translateX(0); }\n [data-dsh-mobile-handle] { display:none !important; }\n [data-dsh-mobile-settings] { flex-direction:column !important; width:100vw !important; height:100dvh !important; max-width:none !important; border-radius:0 !important; animation:dsh-mobile-panel-in var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); }\n [data-dsh-mobile-settings-nav] { flex:none !important; width:100% !important; padding:max(14px,env(safe-area-inset-top)) 12px 8px !important; gap:10px !important; border-bottom:1px solid var(--dsw-alias-border-subtle,#e8ebef); }\n [data-dsh-mobile-settings-nav] [class*=\"_navTitle\"] { padding:0 8px !important; font-size:18px !important; line-height:28px !important; }\n [data-dsh-mobile-settings-list] { flex-direction:row !important; gap:4px !important; overflow-x:auto !important; scrollbar-width:none; }\n [data-dsh-mobile-settings-list]::-webkit-scrollbar { display:none; }\n [data-dsh-mobile-settings-list] [class*=\"_navCell\"] { flex:0 0 auto !important; min-width:max-content !important; height:44px !important; padding:10px 12px !important; }\n [data-dsh-mobile-settings-list] [aria-current=\"true\"] { border-color:transparent !important; outline:0 !important; box-shadow:none !important; }\n [data-dsh-mobile-settings-content] { flex:1 1 auto !important; width:100% !important; min-height:0 !important; }\n [data-dsh-mobile-settings-header] { height:48px !important; min-height:48px !important; padding:10px 12px 6px !important; }\n [data-dsh-mobile-settings-header] [class*=\"_close\"] { width:36px !important; height:36px !important; }\n [data-dsh-mobile-settings-options] { box-sizing:border-box !important; width:100% !important; padding:4px 16px max(24px,env(safe-area-inset-bottom)) !important; overflow-x:hidden !important; }\n [data-dsh-mobile-settings-options] > * { width:100% !important; min-width:0 !important; }\n [data-dsh-mobile-settings-options] [data-slot=\"settings.general.item\"] > [class*=\"_row\"] { flex-direction:column !important; align-items:stretch !important; gap:12px !important; }\n [data-dsh-mobile-settings-options] [data-slot=\"settings.general.item\"] [class*=\"_rowText\"] { width:100% !important; padding-right:0 !important; }\n [data-dsh-mobile-settings-options] [data-slot=\"settings.general.item\"] [class*=\"_selector\"] { box-sizing:border-box !important; align-self:flex-start !important; justify-content:space-between !important; min-width:0 !important; min-height:44px !important; max-width:100% !important; }\n [data-dsh-mobile-settings-options] :is(input,select,textarea,button) { max-width:100%; }\n [data-dsh-mobile-settings-options] :is(input,select,textarea) { box-sizing:border-box; width:100%; min-width:0; }\n [data-dsh-mobile-settings-options] [class*=\"_head\"] { min-width:0; flex-wrap:wrap; }\n [data-dsh-mobile-settings-content][data-dsh-mobile-view-transition=\"true\"],\n [data-dsh-mobile-view][data-dsh-mobile-view-transition=\"true\"] { animation:dsh-mobile-view-in var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); }\n [data-dsh-mobile-center] textarea { font-size:16px !important; }\n /* Markdown tables use content-sized columns. Small tables fill the phone;\n wider tables keep readable cells and scroll inside their own region. */\n [data-dsh-mobile-table-scroll] { box-sizing:border-box; width:100%; max-width:100%; overflow-x:auto; overscroll-behavior-x:contain; -webkit-overflow-scrolling:touch; }\n [data-dsh-mobile-table-scroll] table { display:table !important; width:max-content !important; min-width:100% !important; max-width:none !important; table-layout:auto !important; }\n [data-dsh-mobile-table-scroll] :is(th,td) { box-sizing:border-box; min-width:8ch; max-width:32ch; overflow-wrap:anywhere; word-break:break-word; vertical-align:top; }\n [data-dsh-mobile-center] pre { max-width:100%; overflow-x:auto; }\n [data-dsh-mobile-center] :is(img,video,canvas,svg) { max-width:100%; }\n [data-dsh-mobile-message-scroll] { box-sizing:border-box !important; width:100% !important; padding:8px 10px 20px !important; }\n [data-dsh-mobile-message-column] { box-sizing:border-box !important; width:100% !important; max-width:none !important; margin:0 !important; padding:0 !important; gap:10px !important; }\n [data-dsh-mobile-message-column] > * { width:100% !important; max-width:100% !important; }\n [data-dsh-mobile-message-column] [data-disclosure-row] { box-sizing:border-box !important; display:grid !important; grid-template-columns:16px minmax(0,1fr) !important; grid-auto-rows:auto !important; align-items:center !important; column-gap:6px !important; width:100% !important; height:auto !important; min-height:40px !important; padding:4px 0 !important; }\n [data-dsh-mobile-message-column] [data-disclosure-row] > [class*=\"_leading\"] { grid-column:1 !important; grid-row:1 !important; margin-right:0 !important; }\n [data-dsh-mobile-message-column] [data-disclosure-row] > [class*=\"_title\"] { grid-column:2 !important; grid-row:1 !important; min-width:0 !important; overflow:hidden !important; text-overflow:ellipsis !important; white-space:nowrap !important; }\n [data-dsh-mobile-message-column] [data-disclosure-row] > :is([class*=\"_sep\"],[class*=\"_separator\"]) { display:none !important; }\n [data-dsh-mobile-message-column] [data-disclosure-row] > :is([class*=\"_summary\"],[class*=\"_fileLink\"]) { grid-column:2 !important; grid-row:2 !important; width:100% !important; min-width:0 !important; max-width:100% !important; overflow:hidden !important; line-height:19px !important; text-overflow:ellipsis !important; white-space:nowrap !important; }\n [data-dsh-mobile-message-column] [data-disclosure-row] > [class*=\"_summarySuffix\"] { grid-column:2 !important; grid-row:3 !important; margin-left:0 !important; }\n [data-dsh-mobile-message-column] [data-context-fields] > * { display:grid !important; grid-template-columns:minmax(72px,30%) minmax(0,1fr) !important; gap:4px 10px !important; }\n [data-dsh-mobile-message-column] [class*=\"_ioSection\"] { grid-template-columns:1fr !important; row-gap:4px !important; }\n [data-dsh-mobile-message-column] [class*=\"_body\"] { max-width:100% !important; overflow-wrap:anywhere; }\n [data-dsh-mobile-center] [data-composer-card] ~ [class*=\"_root\"],\n [data-dsh-mobile-center] [data-composer-card] ~ * [class*=\"_root\"] { box-sizing:border-box !important; width:100% !important; max-width:100% !important; margin-bottom:-6px !important; padding:3px 4px 0 !important; font-size:11px !important; line-height:18px !important; white-space:normal !important; overflow:visible !important; text-overflow:clip !important; }\n [data-dsh-mobile-center] [data-composer-card] ~ [class*=\"_root\"] [class*=\"_sep\"],\n [data-dsh-mobile-center] [data-composer-card] ~ * [class*=\"_root\"] [class*=\"_sep\"] { margin:0 6px !important; }\n /* Message runtime details are inline on desktop. Give the clock/runtime\n label its own wrapping row on narrow screens so TTFT and throughput do\n not push the action buttons or clip at the viewport edge. */\n [data-dsh-mobile-center] [class*=\"_actions\"]:has(> [class*=\"_timeStart\"]),\n [data-dsh-mobile-center] [class*=\"_actions\"]:has(> [class*=\"_timeEnd\"]) { box-sizing:border-box !important; width:100% !important; flex-wrap:wrap !important; justify-content:flex-end !important; height:auto !important; min-height:28px !important; row-gap:2px !important; }\n [data-dsh-mobile-center] [class*=\"_timeStart\"],\n [data-dsh-mobile-center] [class*=\"_timeEnd\"] { box-sizing:border-box !important; flex:1 1 100% !important; order:2 !important; min-width:0 !important; max-width:100% !important; padding:0 !important; line-height:20px !important; text-align:center !important; white-space:normal !important; overflow-wrap:anywhere !important; }\n [data-dsh-mobile-center] [class*=\"_timeStart\"] { box-sizing:border-box !important; flex:1 1 100% !important; order:2 !important; min-width:0 !important; max-width:100% !important; padding:0 !important; line-height:20px !important; text-align:center !important; white-space:normal !important; overflow-wrap:anywhere !important; }\n [data-dsh-mobile-center] [class*=\"_timeStart\"] [class*=\"_runTimeDot\"],\n [data-dsh-mobile-center] [class*=\"_timeEnd\"] [class*=\"_runTimeDot\"] { margin:0 6px !important; }\n /* Keep the context meter's legend rows as readable label/value pairs.\n Generic mobile flex rules can otherwise place the rows side by side and\n break Chinese labels in the middle of a word. */\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"上下文\"],\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"Context\"] { width:min(264px,calc(100vw - 32px)) !important; min-width:0 !important; max-width:calc(100vw - 32px) !important; }\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"上下文\"] [class*=\"_rows\"],\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"Context\"] [class*=\"_rows\"] { display:block !important; }\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"上下文\"] [class*=\"_rows\"] > [class*=\"_row\"],\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"Context\"] [class*=\"_rows\"] > [class*=\"_row\"] { display:flex !important; align-items:center !important; justify-content:space-between !important; width:100% !important; min-width:0 !important; white-space:nowrap !important; }\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"上下文\"] :is(dt,dd),\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"Context\"] :is(dt,dd) { white-space:nowrap !important; word-break:keep-all !important; }\n .dsh-mobile-branch-toast { position:fixed; z-index:300; top:max(12px,env(safe-area-inset-top)); left:50%; max-width:calc(100vw - 32px); box-sizing:border-box; padding:7px 14px; border:1px solid rgb(15 23 42 / 10%); border-radius:999px; background:rgb(15 23 42 / 92%); color:#fff; font-size:13px; line-height:20px; text-align:center; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; opacity:0; pointer-events:none; transform:translate(-50%,-8px); transition:opacity 160ms ease,transform 160ms ease; }\n .dsh-mobile-branch-toast[data-visible=\"true\"] { opacity:1; transform:translate(-50%,0); }\n [data-dsh-mobile-center] [class*=\"_composer\"] { padding-left:8px !important; padding-right:8px !important; padding-bottom:max(8px,env(safe-area-inset-bottom)) !important; }\n [data-dsh-mobile-center] [class*=\"_card\"]:has(textarea) [class*=\"_row\"] { display:flex !important; align-items:center !important; gap:4px !important; }\n [data-dsh-mobile-center] [class*=\"_card\"]:has(textarea) [class*=\"_tools\"] { flex:0 0 auto !important; width:auto !important; min-width:0 !important; gap:6px !important; }\n [data-dsh-mobile-center] [class*=\"_card\"]:has(textarea) [class*=\"_trailing\"] { flex:1 1 auto !important; min-width:0 !important; gap:6px !important; margin-left:auto !important; justify-content:flex-end !important; }\n [data-dsh-mobile-center] [class*=\"_card\"]:has(textarea) [class*=\"_trailing\"] [class*=\"_root\"]:has(button[aria-label^=\"选择模型\"]) { flex:0 1 auto !important; width:auto !important; max-width:min(55vw,220px) !important; min-width:0 !important; }\n [data-dsh-mobile-center] [class*=\"_card\"]:has(textarea) button[aria-label^=\"选择模型\"] { width:auto !important; max-width:100% !important; min-width:0 !important; padding-left:6px !important; padding-right:4px !important; }\n [data-dsh-mobile-center] [class*=\"_card\"]:has(textarea) button[aria-label^=\"选择模型\"] [class*=\"_triggerLabel\"] { max-width:clamp(120px,36vw,210px); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n [data-dsh-mobile-center] [class*=\"_root\"]:has(> [class*=\"_card\"] textarea) { box-sizing:border-box !important; width:100% !important; padding:0 0 8px !important; }\n [data-dsh-mobile-center] [class*=\"_root\"]:has(> [class*=\"_card\"] textarea) > [class=\"\"]:last-child { display:none !important; }\n}\n@keyframes dsh-mobile-fade-in { from { opacity:0; } }\n@keyframes dsh-mobile-panel-in { from { opacity:.72; transform:translateY(6px); } }\n@keyframes dsh-mobile-view-in { from { opacity:.58; transform:translateY(5px); } }\n@media (max-width:420px) {\n [data-dsh-mobile-header] [class*=\"_headerActions\"] { max-width:42vw; }\n [data-dsh-mobile-settings-options] [data-slot=\"settings.general.item\"] [class*=\"_selector\"] { align-self:stretch !important; width:100% !important; }\n [data-dsh-mobile-message-column] [data-context-fields] > * { grid-template-columns:1fr !important; }\n}\n@media (prefers-reduced-motion:reduce) {\n [data-dsh-mobile-sidebar-root],[data-dsh-mobile-details] { transition:none !important; }\n .dsh-native-mobile-backdrop:not([hidden]),[data-dsh-mobile-settings],\n [data-dsh-mobile-settings-content][data-dsh-mobile-view-transition=\"true\"],\n [data-dsh-mobile-view][data-dsh-mobile-view-transition=\"true\"] { animation:none !important; }\n}\n`\n\nfunction classToken(element: Element, suffix: string): boolean {\n return Array.from(element.classList).some(value => value.endsWith(suffix))\n}\n\nfunction firstByClassSuffix(root: ParentNode, suffix: string): HTMLElement | undefined {\n return Array.from(root.querySelectorAll<HTMLElement>('[class]')).find(element => classToken(element, suffix))\n}\n\n/** Add mobile semantics without replacing feature trees. */\nexport function installNativeMobileSurface(): () => void {\n document.documentElement.classList.add('dsh-native-mobile-active')\n const setInputMode = (mode: 'keyboard' | 'touch'): void => {\n document.documentElement.dataset.dshMobileInput = mode\n }\n const onPointerDown = (event: PointerEvent): void => {\n if (event.pointerType === 'touch' || event.pointerType === 'pen') setInputMode('touch')\n }\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Tab' || event.key.startsWith('Arrow')) setInputMode('keyboard')\n }\n document.addEventListener('pointerdown', onPointerDown, true)\n document.addEventListener('keydown', onKeyDown, true)\n const backdrop = document.createElement('button')\n backdrop.type = 'button'\n backdrop.className = 'dsh-native-mobile-backdrop'\n backdrop.hidden = true\n backdrop.setAttribute('aria-label', '关闭工作区导航')\n document.body.append(backdrop)\n const branchToast = document.createElement('div')\n branchToast.className = 'dsh-mobile-branch-toast'\n branchToast.setAttribute('role', 'status')\n branchToast.setAttribute('aria-live', 'polite')\n document.body.append(branchToast)\n let branchToastTimer = 0\n const showBranchToast = (): void => {\n const header = document.querySelector<HTMLElement>('[data-dsh-mobile-header]')\n const title = header === null ? undefined : header.querySelector<HTMLElement>('[class*=\"_crumbCurrent\"]')?.textContent?.trim()\n branchToast.textContent = title === undefined ? '当前分支' : `当前分支:${title}`\n branchToast.dataset.visible = 'true'\n if (branchToastTimer !== 0) window.clearTimeout(branchToastTimer)\n branchToastTimer = window.setTimeout(() => {\n branchToast.removeAttribute('data-visible')\n branchToastTimer = 0\n }, 1600)\n }\n const onBranchClick = (event: MouseEvent): void => {\n if (!(event.target instanceof Element)) return\n const branch = event.target.closest<HTMLButtonElement>('button[aria-label*=\"分支\"],button[aria-label*=\"Branch\"],button[aria-label*=\"branch\"]')\n if (branch === null || branch.hasAttribute('disabled') || branch.getAttribute('aria-disabled') === 'true') return\n window.setTimeout(showBranchToast, 80)\n }\n document.addEventListener('click', onBranchClick, true)\n let frame: HTMLElement | undefined\n let sidebar: HTMLElement | undefined\n let sidebarRoot: HTMLElement | undefined\n let toggle: HTMLButtonElement | undefined\n let viewArea: HTMLElement | undefined\n let scheduled = 0\n let transitionFrame = 0\n let transitionRestartFrame = 0\n let transitionTimer = 0\n let transitionTarget: HTMLElement | undefined\n const animateNavigation = (event: MouseEvent): void => {\n if (!(event.target instanceof Element)) return\n const trigger = event.target.closest<HTMLElement>('button,a,[role=\"tab\"],[aria-selected]')\n if (trigger === null || trigger.hasAttribute('disabled') || trigger.getAttribute('aria-disabled') === 'true') return\n if (trigger.getAttribute('aria-selected') === 'true' || trigger.getAttribute('aria-current') === 'true') return\n const settingsNavigation = trigger.closest('[data-dsh-mobile-settings-list]') !== null\n const conversationNavigation = trigger.matches('[role=\"tab\"]')\n const sidebarNavigation = trigger.closest('[data-dsh-mobile-sidebar-root]') !== null\n && trigger.closest('[data-dsh-mobile-toggle]') === null\n if (!settingsNavigation && !conversationNavigation && !sidebarNavigation) return\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return\n if (transitionFrame !== 0) cancelAnimationFrame(transitionFrame)\n if (transitionRestartFrame !== 0) cancelAnimationFrame(transitionRestartFrame)\n transitionFrame = requestAnimationFrame(() => {\n transitionFrame = 0\n const target = settingsNavigation\n ? document.querySelector<HTMLElement>('[data-dsh-mobile-settings-content]')\n : viewArea\n if (target === null || target === undefined) return\n transitionTarget?.removeAttribute('data-dsh-mobile-view-transition')\n target.removeAttribute('data-dsh-mobile-view-transition')\n transitionRestartFrame = requestAnimationFrame(() => {\n transitionRestartFrame = 0\n transitionTarget = target\n target.dataset.dshMobileViewTransition = 'true'\n if (transitionTimer !== 0) clearTimeout(transitionTimer)\n transitionTimer = window.setTimeout(() => {\n target.removeAttribute('data-dsh-mobile-view-transition')\n if (transitionTarget === target) transitionTarget = undefined\n transitionTimer = 0\n }, 240)\n })\n })\n }\n document.addEventListener('click', animateNavigation)\n\n const sync = (): void => {\n scheduled = 0\n frame = firstByClassSuffix(document, '_frame')\n const dedicatedCenter = document.querySelector<HTMLElement>('.dshm-main') ?? undefined\n if (frame !== undefined) frame.dataset.dshMobileFrame = 'true'\n sidebar = frame === undefined\n ? document.querySelector<HTMLElement>('.dshm-drawer') ?? undefined\n : firstByClassSuffix(frame, '_sidebarCol')\n const center = frame === undefined ? dedicatedCenter : firstByClassSuffix(frame, '_centerCol')\n const details = frame === undefined ? undefined : firstByClassSuffix(frame, '_detailsCol')\n const handle = frame === undefined ? undefined : firstByClassSuffix(frame, '_handle')\n if (center === undefined) return\n if (center !== undefined) {\n center.dataset.dshMobileCenter = 'true'\n center.querySelector<HTMLElement>('header')?.setAttribute('data-dsh-mobile-header', 'true')\n viewArea = firstByClassSuffix(center, '_viewArea')\n if (viewArea !== undefined) viewArea.dataset.dshMobileView = 'true'\n const conversation = center.querySelector<HTMLElement>('[data-conversation-scroll]')\n const messageColumn = conversation === null ? undefined : firstByClassSuffix(conversation, '_column')\n const messageScroll = messageColumn?.parentElement\n if (messageColumn !== undefined && messageScroll !== null && messageScroll !== undefined && classToken(messageScroll, '_scroll')) {\n messageColumn.dataset.dshMobileMessageColumn = 'true'\n messageScroll.dataset.dshMobileMessageScroll = 'true'\n }\n for (const table of center.querySelectorAll<HTMLTableElement>('table')) {\n const parent = table.parentElement\n if (parent?.dataset.dshMobileTableScroll === 'true') continue\n const wrapper = document.createElement('div')\n wrapper.dataset.dshMobileTableScroll = 'true'\n table.before(wrapper)\n wrapper.append(table)\n }\n }\n if (handle !== undefined) handle.dataset.dshMobileHandle = 'true'\n if (details !== undefined) {\n details.dataset.dshMobileDetails = 'true'\n const lastColumn = frame?.style.gridTemplateColumns.trim().split(/\\s+/).at(-1)\n details.dataset.open = String(lastColumn !== undefined && lastColumn !== '0px' && lastColumn !== '0')\n }\n const settings = Array.from(document.querySelectorAll<HTMLElement>('[role=\"dialog\"]')).find(dialog => {\n const directNav = Array.from(dialog.children).find(child => child instanceof HTMLElement && classToken(child, '_nav'))\n return directNav !== undefined\n })\n if (settings !== undefined) {\n settings.dataset.dshMobileSettings = 'true'\n const nav = Array.from(settings.children).find(child => child instanceof HTMLElement && classToken(child, '_nav')) as HTMLElement | undefined\n const content = Array.from(settings.children).find(child => child instanceof HTMLElement && classToken(child, '_content')) as HTMLElement | undefined\n if (nav !== undefined) {\n nav.dataset.dshMobileSettingsNav = 'true'\n firstByClassSuffix(nav, '_navList')?.setAttribute('data-dsh-mobile-settings-list', 'true')\n }\n if (content !== undefined) {\n content.dataset.dshMobileSettingsContent = 'true'\n firstByClassSuffix(content, '_header')?.setAttribute('data-dsh-mobile-settings-header', 'true')\n firstByClassSuffix(content, '_options')?.setAttribute('data-dsh-mobile-settings-options', 'true')\n }\n }\n if (sidebar === undefined) return\n sidebar.dataset.dshMobileSidebar = 'true'\n toggle = firstByClassSuffix(sidebar, '_toggle') as HTMLButtonElement | undefined\n let candidate = toggle?.parentElement\n while (candidate !== undefined && candidate !== null && candidate !== sidebar && !classToken(candidate, '_root')) candidate = candidate.parentElement\n sidebarRoot = candidate !== sidebar && candidate !== null ? candidate : undefined\n if (sidebarRoot === undefined) return\n sidebarRoot.dataset.dshMobileSidebarRoot = 'true'\n if (toggle !== undefined) toggle.dataset.dshMobileToggle = 'true'\n const collapsed = classToken(sidebarRoot, '_collapsed')\n sidebar.dataset.open = String(!collapsed)\n backdrop.hidden = collapsed\n }\n const schedule = (): void => { if (scheduled === 0) scheduled = requestAnimationFrame(sync) }\n const observer = new MutationObserver(schedule)\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] })\n backdrop.addEventListener('click', () => { if (sidebar?.dataset.open === 'true') toggle?.click() })\n sync()\n return () => {\n observer.disconnect()\n document.removeEventListener('click', onBranchClick, true)\n if (branchToastTimer !== 0) window.clearTimeout(branchToastTimer)\n branchToast.remove()\n if (scheduled !== 0) cancelAnimationFrame(scheduled)\n if (transitionFrame !== 0) cancelAnimationFrame(transitionFrame)\n if (transitionRestartFrame !== 0) cancelAnimationFrame(transitionRestartFrame)\n if (transitionTimer !== 0) clearTimeout(transitionTimer)\n transitionTarget?.removeAttribute('data-dsh-mobile-view-transition')\n document.removeEventListener('pointerdown', onPointerDown, true)\n document.removeEventListener('keydown', onKeyDown, true)\n document.removeEventListener('click', animateNavigation)\n backdrop.remove()\n document.documentElement.classList.remove('dsh-native-mobile-active')\n delete document.documentElement.dataset.dshMobileInput\n }\n}\n","import { createElement } from 'react'\nimport { installNativeMobileSurface, NATIVE_MOBILE_STYLES } from './native-mobile.js'\n\ninterface ClientContext {\n effect(effect: () => void | (() => void), label?: string): void\n slots: {\n inject(key: string, callback: () => (() => void)): () => void\n register(options: { name: string; id: string }, component: (props: { wide: boolean }) => unknown): () => void\n }\n}\n\ninterface MobileExtensionContext {\n readonly document: Document\n readonly request: (path: string, init?: RequestInit) => Promise<Response>\n readonly root: HTMLElement\n readonly window: Window\n}\n\ntype MobileExtensionMount = (context: MobileExtensionContext) => void | (() => void)\n\ndeclare global {\n interface Window {\n dshMobile?: { register(mount: MobileExtensionMount): void }\n __DSH_MOBILE_FRONTEND__?: 'dedicated'\n }\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '[::1]'\n}\n\nfunction element<K extends keyof HTMLElementTagNameMap>(tag: K, className?: string): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag)\n if (className !== undefined) node.className = className\n return node\n}\n\nasync function requestJson(path: string, init?: RequestInit): Promise<Record<string, unknown>> {\n const response = await fetch(path, { ...init, headers: { 'content-type': 'application/json', ...init?.headers } })\n const body = await response.json() as Record<string, unknown>\n if (!response.ok) throw new Error(typeof body.error === 'string' ? body.error : `HTTP ${String(response.status)}`)\n return body\n}\n\nfunction installControl(): { remove: () => void; toggle: () => void } {\n const root = element('div', 'dsh-mobile-control')\n const panel = element('section', 'dsh-mobile-control__panel'); panel.hidden = true\n panel.setAttribute('aria-label', '移动访问')\n const header = element('header', 'dsh-mobile-control__header')\n const title = element('h2'); title.textContent = '移动访问'\n const close = element('button', 'dsh-mobile-control__close'); close.type = 'button'; close.textContent = '×'; close.setAttribute('aria-label', '收起移动访问')\n const access = element('div', 'dsh-mobile-control__access'); access.hidden = true\n const accessLabel = element('span', 'dsh-mobile-control__access-label'); accessLabel.textContent = '浏览器访问'\n const accessLink = element('a', 'dsh-mobile-control__access-link'); accessLink.target = '_blank'; accessLink.rel = 'noreferrer'\n access.append(accessLabel, accessLink)\n const qrBox = element('div', 'dsh-mobile-control__qr'); qrBox.hidden = true\n const status = element('p', 'dsh-mobile-control__status'); status.textContent = '正在读取状态…'\n const actions = element('div', 'dsh-mobile-control__actions')\n const toggle = element('button', 'dsh-mobile-control__secondary'); toggle.type = 'button'\n const pair = element('button', 'dsh-mobile-control__primary'); pair.type = 'button'; pair.textContent = '生成并复制密钥'\n const linkPair = element('button', 'dsh-mobile-control__secondary'); linkPair.type = 'button'; linkPair.textContent = '复制配对链接'\n header.append(title, close); actions.append(toggle, pair, linkPair); panel.append(header, access, qrBox, status, actions); root.append(panel); document.body.append(root)\n let running = false\n let origin = ''\n const setOpen = (open: boolean): void => {\n panel.hidden = !open\n for (const trigger of document.querySelectorAll('.dsh-mobile-control__trigger')) trigger.setAttribute('aria-expanded', String(open))\n }\n const render = (data: Record<string, unknown>): void => {\n running = data.running === true\n origin = running && typeof data.origin === 'string' ? data.origin : ''\n access.hidden = origin === ''\n accessLink.href = origin\n accessLink.textContent = origin\n accessLink.title = origin\n status.classList.toggle('is-running', running)\n status.textContent = running ? '移动访问已开启。' : '已关闭。DSH 仍只在本机可用。'\n toggle.textContent = running ? '关闭移动访问' : '开启移动访问'\n pair.disabled = !running\n linkPair.disabled = !running\n }\n const showQr = (svg: string): void => {\n qrBox.replaceChildren()\n if (svg === '') { qrBox.hidden = true; return }\n const image = element('img')\n image.alt = '配对二维码'\n image.width = 176\n image.height = 176\n image.src = `data:image/svg+xml;base64,${btoa(svg)}`\n qrBox.hidden = false\n qrBox.append(image)\n }\n const openPairing = (target: 'key' | 'link'): void => {\n void requestJson('/api/mobile-access/pairing/open', { method: 'POST', body: '{}' }).then(async data => {\n const value = target === 'key'\n ? (typeof data.appKey === 'string' ? data.appKey : '')\n : (typeof data.pairUrl === 'string' ? data.pairUrl : '')\n showQr(typeof data.qrSvg === 'string' ? data.qrSvg : '')\n if (value === '') { status.textContent = '无法生成配对密钥。'; return }\n try {\n await navigator.clipboard.writeText(value)\n status.textContent = target === 'key'\n ? '配对密钥已复制,请粘贴到 Android App。'\n : '配对链接已复制,发给手机后 App 粘贴或浏览器打开即可配对。'\n } catch {\n status.textContent = `请复制${target === 'key' ? '配对密钥' : '配对链接'}:${value}`\n status.classList.add('is-key')\n }\n }, error => { status.textContent = String(error) }).finally(() => {\n pair.disabled = !running\n linkPair.disabled = !running\n })\n }\n pair.addEventListener('click', () => { pair.disabled = true; openPairing('key') })\n linkPair.addEventListener('click', () => { linkPair.disabled = true; openPairing('link') })\n close.addEventListener('click', () => { setOpen(false) })\n const dismiss = (event: PointerEvent): void => {\n if (panel.hidden || !(event.target instanceof Node)) return\n if (!panel.contains(event.target) && !document.querySelector('.dsh-mobile-control__trigger')?.contains(event.target)) setOpen(false)\n }\n document.addEventListener('pointerdown', dismiss)\n void requestJson('/api/mobile-access/control').then(render, error => { status.textContent = String(error) })\n return { remove: () => { document.removeEventListener('pointerdown', dismiss); root.remove() }, toggle: () => { setOpen(panel.hidden !== false) } }\n}\n\nfunction mobileRequest(path: string, init: RequestInit = {}): Promise<Response> {\n const target = new URL(path, location.href)\n if (target.origin !== location.origin) throw new TypeError('mobile extension requests must stay on the DSH origin')\n const headers = new Headers(init.headers)\n const method = (init.method ?? 'GET').toUpperCase()\n if (method !== 'GET' && method !== 'HEAD') {\n const csrf = document.cookie.split(';').map(value => value.trim()).find(value => value.startsWith('dsh_ma_csrf='))?.slice(12)\n if (csrf !== undefined) headers.set('x-dsh-mobile-csrf', csrf)\n }\n return fetch(target, { ...init, headers, credentials: 'same-origin', cache: 'no-store', redirect: 'error' })\n}\n\nfunction installCustomAssets(): () => void {\n const style = element('style'); style.dataset.plugin = 'dsh-mobile-custom'; document.head.append(style)\n let cssLoading = false\n const refreshCss = async (): Promise<void> => { if (cssLoading || document.visibilityState === 'hidden') return; cssLoading = true; try { const response = await fetch('/mobile-access/custom.css', { cache: 'no-store', credentials: 'same-origin' }); if (response.ok) { const css = await response.text(); if (style.textContent !== css) style.textContent = css } } finally { cssLoading = false } }\n let activeRoot: HTMLElement | undefined; let disposeActive: (() => void) | undefined; let source = ''; let pending: MobileExtensionMount | undefined\n const previous = window.dshMobile\n window.dshMobile = Object.freeze({ register: (mount: MobileExtensionMount) => { pending = mount } })\n const registeredMount = (): MobileExtensionMount | undefined => pending\n const refreshJs = async (): Promise<void> => { try { const response = await fetch('/mobile-access/custom.js', { cache: 'no-store', credentials: 'same-origin' }); if (!response.ok) return; const next = await response.text(); if (next === source) return; source = next; pending = undefined; const script = element('script'); script.textContent = `${next}\\n//# sourceURL=dsh-mobile-custom.js`; document.head.append(script); script.remove(); const mount = registeredMount(); if (mount === undefined) return; const nextRoot = element('div'); nextRoot.dataset.dshMobileExtension = 'true'; document.body.append(nextRoot); const nextDispose = mount({ document, request: mobileRequest, root: nextRoot, window }); disposeActive?.(); activeRoot?.remove(); activeRoot = nextRoot; disposeActive = typeof nextDispose === 'function' ? nextDispose : undefined } catch { /* Preserve the last good customization during reconnects. */ } }\n void refreshCss(); void refreshJs()\n const timer = window.setInterval(() => { void refreshCss(); void refreshJs() }, 1_000)\n return () => { clearInterval(timer); disposeActive?.(); activeRoot?.remove(); style.remove(); if (previous === undefined) delete window.dshMobile; else window.dshMobile = previous }\n}\n\nconst CONTROL_STYLES = `\n.dsh-mobile-control{position:fixed;z-index:1000;left:16px;bottom:64px;font:14px/1.45 system-ui;color:var(--dsw-alias-label-primary,#16181d)}\n.dsh-mobile-control__panel{box-sizing:border-box;width:min(328px,calc(100vw - 32px));padding:16px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:18px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 18px 50px rgb(15 23 42 / 18%)}\n.dsh-mobile-control__header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}.dsh-mobile-control__panel h2{margin:0;font-size:17px;line-height:24px}.dsh-mobile-control__close{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:0;border-radius:10px;background:transparent;color:inherit;font-size:24px;line-height:1;cursor:pointer}.dsh-mobile-control__close:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}\n.dsh-mobile-control__access{display:flex;align-items:baseline;gap:6px;min-width:0;margin:0 0 12px}.dsh-mobile-control__access[hidden]{display:none}.dsh-mobile-control__access-label{flex:none;color:var(--dsw-alias-label-secondary,#606873);white-space:nowrap}.dsh-mobile-control__access-label::after{content:\":\"}.dsh-mobile-control__access-link{min-width:0;overflow:hidden;color:#2563eb;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__access-link:hover{text-decoration:underline}.dsh-mobile-control__qr{display:flex;justify-content:center;margin:0 0 12px}.dsh-mobile-control__qr[hidden]{display:none}.dsh-mobile-control__qr img{border-radius:12px;background:#fff;padding:8px}\n.dsh-mobile-control__status{margin:0 0 14px;overflow-wrap:anywhere;color:var(--dsw-alias-label-secondary,#606873)}.dsh-mobile-control__status::before{display:inline-block;width:8px;height:8px;margin-right:7px;border-radius:50%;background:#98a1ad;content:\"\"}.dsh-mobile-control__status.is-running::before{background:#16a36a}.dsh-mobile-control__status.is-key{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;word-break:break-all}\n.dsh-mobile-control__actions{display:flex;flex-wrap:wrap;gap:8px}.dsh-mobile-control__actions button{min-height:40px;padding:8px 12px;border-radius:11px;font:inherit;cursor:pointer}.dsh-mobile-control__secondary{border:1px solid var(--dsw-alias-border-normal,#cfd5dd);background:transparent;color:inherit}.dsh-mobile-control__primary{border:1px solid #2563eb;background:#2563eb;color:#fff}.dsh-mobile-control__actions button:disabled{cursor:not-allowed;opacity:.45}\n.dsh-mobile-control__trigger{box-sizing:border-box;display:flex;align-items:center;gap:8px;width:calc(100% + 8px);height:34px;margin:4px -4px;padding:6px 2px 6px 10px;border:0;border-radius:12px;background:transparent;color:var(--dsw-alias-label-primary,#16181d);font:14px/22px system-ui;cursor:pointer}.dsh-mobile-control__trigger:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}.dsh-mobile-control__trigger.is-rail{width:36px;height:36px;margin:8px 0 10px;padding:0;justify-content:center;border-radius:50%}.dsh-mobile-control__trigger-icon{position:relative;box-sizing:border-box;flex:none;width:14px;height:19px;border:1.7px solid currentColor;border-radius:3px}.dsh-mobile-control__trigger-icon::after{position:absolute;right:4px;bottom:2px;width:4px;height:1.5px;border-radius:2px;background:currentColor;content:\"\"}.dsh-mobile-control__trigger-label{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}\n`\n\n/** Mount the desktop control or mobile feature enhancements. */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => {\n const loopback = isLoopbackHost(location.hostname) && !new URLSearchParams(location.search).has('dsh-mobile-preview')\n const style = element('style'); style.dataset.plugin = 'dsh-mobile'; style.textContent = loopback\n ? CONTROL_STYLES\n : NATIVE_MOBILE_STYLES\n document.head.append(style)\n if (!loopback) {\n const removeCustom = installCustomAssets()\n const removeSurface = installNativeMobileSurface()\n return () => { removeCustom(); removeSurface(); style.remove() }\n }\n const control = installControl()\n const disposeSlot = ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({ name: 'sidebar.footer.action', id: 'dsh-mobile' }, ({ wide }) => createElement('button', {\n 'aria-expanded': false,\n 'aria-label': '移动访问',\n className: `dsh-mobile-control__trigger${wide ? '' : ' is-rail'}`,\n type: 'button',\n title: '移动访问',\n onClick: control.toggle,\n }, createElement('span', { 'aria-hidden': true, className: 'dsh-mobile-control__trigger-icon' }), wide ? createElement('span', { className: 'dsh-mobile-control__trigger-label' }, '移动访问') : undefined)))\n return () => { disposeSlot(); control.remove(); style.remove() }\n }, 'dsh-mobile: stock mobile adaptation and local control')\n}\n\n/** Client services required by the mobile adaptation. */\nexport const inject: readonly string[] = ['slots']\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["createElement"],"sources":["../src/native-mobile.ts","../src/client.ts"],"sourcesContent":["/** Mobile feature and compatibility rules applied to DSH React surfaces. */\nexport const NATIVE_MOBILE_STYLES = `\n@media (max-width:720px) {\n html.dsh-native-mobile-active,html.dsh-native-mobile-active body { width:100%; height:100%; overflow:hidden; }\n html.dsh-native-mobile-active { --dsh-mobile-motion-duration:200ms; --dsh-mobile-motion-ease:cubic-bezier(.22,1,.36,1); }\n html.dsh-native-mobile-active :is(a,button,[role=\"button\"],[role=\"tab\"],[tabindex]) { -webkit-tap-highlight-color:transparent; }\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [role=\"treeitem\"] { -webkit-tap-highlight-color:transparent; touch-action:manipulation; }\n html.dsh-native-mobile-active[data-dsh-mobile-input=\"touch\"] :is(a,button,[role=\"button\"],[role=\"tab\"],[tabindex]):focus { outline:none !important; }\n html.dsh-native-mobile-active [role=\"tooltip\"] { display:none !important; }\n /* Touch has no persistent hover affordance: keep workspace rows neutral after a tap. */\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] { --dsw-alias-interactive-bg-hover:transparent !important; }\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [role=\"treeitem\"]:is(:hover,:active,:focus,[aria-selected=\"true\"]),\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*=\"_sessionRow\"][class*=\"_selected\"],\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*=\"_searchResultRow\"][class*=\"_selected\"] { background:transparent !important; outline:0 !important; box-shadow:none !important; }\n /* Sidebar row menus are hover-only on desktop. Touch has no hover, so keep\n the ellipsis action visible and give it a reliable hit target. */\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*=\"_rowActions\"] { display:inline-flex !important; align-items:center !important; gap:8px !important; }\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*=\"_sessionRow\"] [class*=\"_time\"] { display:none !important; }\n html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*=\"_rowActions\"] button { box-sizing:border-box !important; width:32px !important; min-width:32px !important; height:32px !important; min-height:32px !important; }\n [data-dsh-mobile-frame] { grid-template-columns:0 minmax(0,1fr) 0 !important; width:100% !important; height:100dvh !important; overflow:hidden !important; }\n [data-dsh-mobile-center] { grid-column:2 !important; width:100vw !important; min-width:0 !important; }\n [data-dsh-mobile-center] > * { min-width:0 !important; }\n [data-dsh-mobile-header] { box-sizing:border-box !important; width:calc(100% - 16px) !important; margin:0 8px !important; min-width:0; padding-top:4px !important; padding-right:8px !important; padding-left:42px !important; }\n [data-dsh-mobile-header] [class*=\"_titleRow\"] { box-sizing:border-box !important; display:flex !important; align-items:center !important; min-width:0; min-height:32px !important; height:32px !important; gap:6px !important; padding:0 6px !important; }\n [data-dsh-mobile-header] [class*=\"_titleCluster\"] { min-width:0; }\n [data-dsh-mobile-header] [class*=\"_crumbs\"] { min-width:0; overflow:hidden; }\n [data-dsh-mobile-header] [class*=\"_crumb\"] { max-width:46vw; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n [data-dsh-mobile-header] [class*=\"_headerActions\"] { min-width:0; overflow-x:auto; scrollbar-width:none; }\n [data-dsh-mobile-header] [class*=\"_headerActions\"]::-webkit-scrollbar { display:none; }\n [data-dsh-mobile-header] [class*=\"_headerUtilities\"] { gap:2px !important; }\n [data-dsh-mobile-header] [class*=\"_sessionLogButton\"] { width:40px; min-width:40px; padding:0 !important; overflow:hidden; color:transparent; font-size:0 !important; }\n [data-dsh-mobile-header] [class*=\"_sessionLogButton\"] > * { display:none !important; }\n [data-dsh-mobile-header] [class*=\"_sessionLogButton\"]::after { color:var(--dsw-text, #171a21); content:\"日志\"; font-size:11px; font-weight:600; }\n [data-dsh-mobile-header] [class*=\"_tabs\"] { box-sizing:border-box !important; width:max-content !important; max-width:calc(100% - 58px) !important; min-height:28px !important; height:28px !important; margin-top:0 !important; padding-left:6px !important; padding-right:6px !important; overflow-x:auto; scrollbar-width:none; }\n [data-dsh-mobile-header] [class*=\"_tab\"] { padding-bottom:5px !important; }\n [data-dsh-mobile-header] [class*=\"_tabs\"]::-webkit-scrollbar { display:none; }\n [data-dsh-mobile-sidebar] { position:fixed !important; z-index:240 !important; inset:0 auto 0 0 !important; width:0 !important; overflow:visible !important; }\n [data-dsh-mobile-sidebar-root] { position:fixed !important; z-index:241 !important; inset:0 auto 0 0 !important; height:100dvh !important; transition:width 180ms var(--dsh-mobile-motion-ease),box-shadow 180ms ease !important; }\n [data-dsh-mobile-sidebar][data-open=\"true\"] [data-dsh-mobile-sidebar-root] { width:min(88vw,340px) !important; box-shadow:18px 0 46px rgb(15 23 42 / 18%); }\n [data-dsh-mobile-sidebar][data-open=\"false\"] [data-dsh-mobile-sidebar-root] { width:0 !important; border:0 !important; background:transparent !important; box-shadow:none !important; overflow:visible !important; }\n [data-dsh-mobile-sidebar][data-open=\"false\"] [data-dsh-mobile-sidebar-root] > :not(:has([data-dsh-mobile-toggle])) { display:none !important; }\n [data-dsh-mobile-sidebar][data-open=\"false\"] [data-dsh-mobile-sidebar-root] > :has([data-dsh-mobile-toggle]) { position:fixed !important; z-index:244 !important; top:env(safe-area-inset-top) !important; left:0 !important; box-sizing:border-box !important; width:50px !important; height:52px !important; padding:4px !important; border:0 !important; background:transparent !important; }\n [data-dsh-mobile-sidebar][data-open=\"false\"] [data-dsh-mobile-sidebar-root] > :has([data-dsh-mobile-toggle]) > :not([data-dsh-mobile-toggle]) { display:none !important; }\n [data-dsh-mobile-toggle] { width:44px !important; height:44px !important; min-width:44px !important; min-height:44px !important; }\n [data-dsh-mobile-sidebar][data-open=\"false\"] [data-dsh-mobile-toggle] > svg[class*=\"_railFish\"] { transform:translateY(-4px) !important; }\n .dsh-native-mobile-backdrop { position:fixed; z-index:235; inset:0; border:0; background:rgb(15 23 42 / 32%); }\n .dsh-native-mobile-backdrop:not([hidden]) { animation:dsh-mobile-fade-in var(--dsh-mobile-motion-duration) ease-out; }\n .dsh-native-mobile-backdrop[hidden] { display:none; }\n [data-dsh-mobile-details] { position:fixed !important; z-index:250 !important; inset:0 0 0 auto !important; width:min(94vw,460px) !important; max-width:none !important; transform:translateX(100%); transition:transform var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); background:var(--dsw-bg, #fff); box-shadow:-18px 0 46px rgb(15 23 42 / 18%); }\n [data-dsh-mobile-details][data-open=\"true\"] { transform:translateX(0); }\n [data-dsh-mobile-handle] { display:none !important; }\n [data-dsh-mobile-settings] { flex-direction:column !important; width:100vw !important; height:100dvh !important; max-width:none !important; border-radius:0 !important; animation:dsh-mobile-panel-in var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); }\n [data-dsh-mobile-settings-nav] { flex:none !important; width:100% !important; padding:max(14px,env(safe-area-inset-top)) 12px 8px !important; gap:10px !important; border-bottom:1px solid var(--dsw-alias-border-subtle,#e8ebef); }\n [data-dsh-mobile-settings-nav] [class*=\"_navTitle\"] { padding:0 8px !important; font-size:18px !important; line-height:28px !important; }\n [data-dsh-mobile-settings-list] { flex-direction:row !important; gap:4px !important; overflow-x:auto !important; scrollbar-width:none; }\n [data-dsh-mobile-settings-list]::-webkit-scrollbar { display:none; }\n [data-dsh-mobile-settings-list] [class*=\"_navCell\"] { flex:0 0 auto !important; min-width:max-content !important; height:44px !important; padding:10px 12px !important; }\n [data-dsh-mobile-settings-list] [aria-current=\"true\"] { border-color:transparent !important; outline:0 !important; box-shadow:none !important; }\n [data-dsh-mobile-settings-content] { flex:1 1 auto !important; width:100% !important; min-height:0 !important; }\n [data-dsh-mobile-settings-header] { height:48px !important; min-height:48px !important; padding:10px 12px 6px !important; }\n [data-dsh-mobile-settings-header] [class*=\"_close\"] { width:36px !important; height:36px !important; }\n [data-dsh-mobile-settings-options] { box-sizing:border-box !important; width:100% !important; padding:4px 16px max(24px,env(safe-area-inset-bottom)) !important; overflow-x:hidden !important; }\n [data-dsh-mobile-settings-options] > * { width:100% !important; min-width:0 !important; }\n [data-dsh-mobile-settings-options] [data-slot=\"settings.general.item\"] > [class*=\"_row\"] { flex-direction:column !important; align-items:stretch !important; gap:12px !important; }\n [data-dsh-mobile-settings-options] [data-slot=\"settings.general.item\"] [class*=\"_rowText\"] { width:100% !important; padding-right:0 !important; }\n [data-dsh-mobile-settings-options] [data-slot=\"settings.general.item\"] [class*=\"_selector\"] { box-sizing:border-box !important; align-self:flex-start !important; justify-content:space-between !important; min-width:0 !important; min-height:44px !important; max-width:100% !important; }\n [data-dsh-mobile-settings-options] :is(input,select,textarea,button) { max-width:100%; }\n [data-dsh-mobile-settings-options] :is(input,select,textarea) { box-sizing:border-box; width:100%; min-width:0; }\n [data-dsh-mobile-settings-options] [class*=\"_head\"] { min-width:0; flex-wrap:wrap; }\n [data-dsh-mobile-settings-content][data-dsh-mobile-view-transition=\"true\"],\n [data-dsh-mobile-view][data-dsh-mobile-view-transition=\"true\"] { animation:dsh-mobile-view-in var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); }\n [data-dsh-mobile-center] textarea { font-size:16px !important; }\n /* Markdown tables use content-sized columns. Small tables fill the phone;\n wider tables keep readable cells and scroll inside their own region. */\n [data-dsh-mobile-table-scroll] { box-sizing:border-box; width:100%; max-width:100%; overflow-x:auto; overscroll-behavior-x:contain; -webkit-overflow-scrolling:touch; }\n [data-dsh-mobile-table-scroll] table { display:table !important; width:max-content !important; min-width:100% !important; max-width:none !important; table-layout:auto !important; }\n [data-dsh-mobile-table-scroll] :is(th,td) { box-sizing:border-box; min-width:8ch; max-width:32ch; overflow-wrap:anywhere; word-break:break-word; vertical-align:top; }\n [data-dsh-mobile-center] pre { max-width:100%; overflow-x:auto; }\n [data-dsh-mobile-center] :is(img,video,canvas,svg) { max-width:100%; }\n [data-dsh-mobile-message-scroll] { box-sizing:border-box !important; width:100% !important; padding:8px 10px 20px !important; }\n [data-dsh-mobile-message-column] { box-sizing:border-box !important; width:100% !important; max-width:none !important; margin:0 !important; padding:0 !important; gap:10px !important; }\n [data-dsh-mobile-message-column] > * { width:100% !important; max-width:100% !important; }\n [data-dsh-mobile-message-column] [data-disclosure-row] { box-sizing:border-box !important; display:grid !important; grid-template-columns:16px minmax(0,1fr) !important; grid-auto-rows:auto !important; align-items:center !important; column-gap:6px !important; width:100% !important; height:auto !important; min-height:40px !important; padding:4px 0 !important; }\n [data-dsh-mobile-message-column] [data-disclosure-row] > [class*=\"_leading\"] { grid-column:1 !important; grid-row:1 !important; margin-right:0 !important; }\n [data-dsh-mobile-message-column] [data-disclosure-row] > [class*=\"_title\"] { grid-column:2 !important; grid-row:1 !important; min-width:0 !important; overflow:hidden !important; text-overflow:ellipsis !important; white-space:nowrap !important; }\n [data-dsh-mobile-message-column] [data-disclosure-row] > :is([class*=\"_sep\"],[class*=\"_separator\"]) { display:none !important; }\n [data-dsh-mobile-message-column] [data-disclosure-row] > :is([class*=\"_summary\"],[class*=\"_fileLink\"]) { grid-column:2 !important; grid-row:2 !important; width:100% !important; min-width:0 !important; max-width:100% !important; overflow:hidden !important; line-height:19px !important; text-overflow:ellipsis !important; white-space:nowrap !important; }\n [data-dsh-mobile-message-column] [data-disclosure-row] > [class*=\"_summarySuffix\"] { grid-column:2 !important; grid-row:3 !important; margin-left:0 !important; }\n [data-dsh-mobile-message-column] [data-context-fields] > * { display:grid !important; grid-template-columns:minmax(72px,30%) minmax(0,1fr) !important; gap:4px 10px !important; }\n [data-dsh-mobile-message-column] [class*=\"_ioSection\"] { grid-template-columns:1fr !important; row-gap:4px !important; }\n [data-dsh-mobile-message-column] [class*=\"_body\"] { max-width:100% !important; overflow-wrap:anywhere; }\n [data-dsh-mobile-center] [data-composer-card] ~ [class*=\"_root\"],\n [data-dsh-mobile-center] [data-composer-card] ~ * [class*=\"_root\"] { box-sizing:border-box !important; width:100% !important; max-width:100% !important; margin-bottom:-6px !important; padding:3px 4px 0 !important; font-size:11px !important; line-height:18px !important; white-space:normal !important; overflow:visible !important; text-overflow:clip !important; }\n [data-dsh-mobile-center] [data-composer-card] ~ [class*=\"_root\"] [class*=\"_sep\"],\n [data-dsh-mobile-center] [data-composer-card] ~ * [class*=\"_root\"] [class*=\"_sep\"] { margin:0 6px !important; }\n /* Message runtime details are inline on desktop. Give the clock/runtime\n label its own wrapping row on narrow screens so TTFT and throughput do\n not push the action buttons or clip at the viewport edge. */\n [data-dsh-mobile-center] [class*=\"_actions\"]:has(> [class*=\"_timeStart\"]),\n [data-dsh-mobile-center] [class*=\"_actions\"]:has(> [class*=\"_timeEnd\"]) { box-sizing:border-box !important; width:100% !important; flex-wrap:wrap !important; justify-content:flex-end !important; height:auto !important; min-height:28px !important; row-gap:2px !important; }\n [data-dsh-mobile-center] [class*=\"_timeStart\"],\n [data-dsh-mobile-center] [class*=\"_timeEnd\"] { box-sizing:border-box !important; flex:1 1 100% !important; order:2 !important; min-width:0 !important; max-width:100% !important; padding:0 !important; line-height:20px !important; text-align:center !important; white-space:normal !important; overflow-wrap:anywhere !important; }\n [data-dsh-mobile-center] [class*=\"_timeStart\"] { box-sizing:border-box !important; flex:1 1 100% !important; order:2 !important; min-width:0 !important; max-width:100% !important; padding:0 !important; line-height:20px !important; text-align:center !important; white-space:normal !important; overflow-wrap:anywhere !important; }\n [data-dsh-mobile-center] [class*=\"_timeStart\"] [class*=\"_runTimeDot\"],\n [data-dsh-mobile-center] [class*=\"_timeEnd\"] [class*=\"_runTimeDot\"] { margin:0 6px !important; }\n /* Keep the context meter's legend rows as readable label/value pairs.\n Generic mobile flex rules can otherwise place the rows side by side and\n break Chinese labels in the middle of a word. */\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"上下文\"],\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"Context\"] { width:min(264px,calc(100vw - 32px)) !important; min-width:0 !important; max-width:calc(100vw - 32px) !important; }\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"上下文\"] [class*=\"_rows\"],\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"Context\"] [class*=\"_rows\"] { display:block !important; }\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"上下文\"] [class*=\"_rows\"] > [class*=\"_row\"],\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"Context\"] [class*=\"_rows\"] > [class*=\"_row\"] { display:flex !important; align-items:center !important; justify-content:space-between !important; width:100% !important; min-width:0 !important; white-space:nowrap !important; }\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"上下文\"] :is(dt,dd),\n [data-dsh-mobile-center] [role=\"dialog\"][aria-label*=\"Context\"] :is(dt,dd) { white-space:nowrap !important; word-break:keep-all !important; }\n .dsh-mobile-branch-toast { position:fixed; z-index:300; top:max(12px,env(safe-area-inset-top)); left:50%; max-width:calc(100vw - 32px); box-sizing:border-box; padding:7px 14px; border:1px solid rgb(15 23 42 / 10%); border-radius:999px; background:rgb(15 23 42 / 92%); color:#fff; font-size:13px; line-height:20px; text-align:center; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; opacity:0; pointer-events:none; transform:translate(-50%,-8px); transition:opacity 160ms ease,transform 160ms ease; }\n .dsh-mobile-branch-toast[data-visible=\"true\"] { opacity:1; transform:translate(-50%,0); }\n [data-dsh-mobile-center] [class*=\"_composer\"] { padding-left:8px !important; padding-right:8px !important; padding-bottom:max(8px,env(safe-area-inset-bottom)) !important; }\n [data-dsh-mobile-center] [class*=\"_card\"]:has(textarea) [class*=\"_row\"] { display:flex !important; align-items:center !important; gap:4px !important; }\n [data-dsh-mobile-center] [class*=\"_card\"]:has(textarea) [class*=\"_tools\"] { flex:0 0 auto !important; width:auto !important; min-width:0 !important; gap:6px !important; }\n [data-dsh-mobile-center] [class*=\"_card\"]:has(textarea) [class*=\"_trailing\"] { flex:1 1 auto !important; min-width:0 !important; gap:6px !important; margin-left:auto !important; justify-content:flex-end !important; }\n [data-dsh-mobile-center] [class*=\"_card\"]:has(textarea) [class*=\"_trailing\"] [class*=\"_root\"]:has(button[aria-label^=\"选择模型\"]) { flex:0 1 auto !important; width:auto !important; max-width:min(55vw,220px) !important; min-width:0 !important; }\n [data-dsh-mobile-center] [class*=\"_card\"]:has(textarea) button[aria-label^=\"选择模型\"] { width:auto !important; max-width:100% !important; min-width:0 !important; padding-left:6px !important; padding-right:4px !important; }\n [data-dsh-mobile-center] [class*=\"_card\"]:has(textarea) button[aria-label^=\"选择模型\"] [class*=\"_triggerLabel\"] { max-width:clamp(120px,36vw,210px); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n [data-dsh-mobile-center] [class*=\"_root\"]:has(> [class*=\"_card\"] textarea) { box-sizing:border-box !important; width:100% !important; padding:0 0 8px !important; }\n [data-dsh-mobile-center] [class*=\"_root\"]:has(> [class*=\"_card\"] textarea) > [class=\"\"]:last-child { display:none !important; }\n}\n@keyframes dsh-mobile-fade-in { from { opacity:0; } }\n@keyframes dsh-mobile-panel-in { from { opacity:.72; transform:translateY(6px); } }\n@keyframes dsh-mobile-view-in { from { opacity:.58; transform:translateY(5px); } }\n@media (max-width:420px) {\n [data-dsh-mobile-header] [class*=\"_headerActions\"] { max-width:42vw; }\n [data-dsh-mobile-settings-options] [data-slot=\"settings.general.item\"] [class*=\"_selector\"] { align-self:stretch !important; width:100% !important; }\n [data-dsh-mobile-message-column] [data-context-fields] > * { grid-template-columns:1fr !important; }\n}\n@media (prefers-reduced-motion:reduce) {\n [data-dsh-mobile-sidebar-root],[data-dsh-mobile-details] { transition:none !important; }\n .dsh-native-mobile-backdrop:not([hidden]),[data-dsh-mobile-settings],\n [data-dsh-mobile-settings-content][data-dsh-mobile-view-transition=\"true\"],\n [data-dsh-mobile-view][data-dsh-mobile-view-transition=\"true\"] { animation:none !important; }\n}\n`\n\nfunction classToken(element: Element, suffix: string): boolean {\n return Array.from(element.classList).some(value => value.endsWith(suffix))\n}\n\nfunction firstByClassSuffix(root: ParentNode, suffix: string): HTMLElement | undefined {\n return Array.from(root.querySelectorAll<HTMLElement>('[class]')).find(element => classToken(element, suffix))\n}\n\n/** Add mobile semantics without replacing feature trees. */\nexport function installNativeMobileSurface(): () => void {\n document.documentElement.classList.add('dsh-native-mobile-active')\n const setInputMode = (mode: 'keyboard' | 'touch'): void => {\n document.documentElement.dataset.dshMobileInput = mode\n }\n const onPointerDown = (event: PointerEvent): void => {\n if (event.pointerType === 'touch' || event.pointerType === 'pen') setInputMode('touch')\n }\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Tab' || event.key.startsWith('Arrow')) setInputMode('keyboard')\n }\n document.addEventListener('pointerdown', onPointerDown, true)\n document.addEventListener('keydown', onKeyDown, true)\n const backdrop = document.createElement('button')\n backdrop.type = 'button'\n backdrop.className = 'dsh-native-mobile-backdrop'\n backdrop.hidden = true\n backdrop.setAttribute('aria-label', '关闭工作区导航')\n document.body.append(backdrop)\n const branchToast = document.createElement('div')\n branchToast.className = 'dsh-mobile-branch-toast'\n branchToast.setAttribute('role', 'status')\n branchToast.setAttribute('aria-live', 'polite')\n document.body.append(branchToast)\n let branchToastTimer = 0\n const showBranchToast = (): void => {\n const header = document.querySelector<HTMLElement>('[data-dsh-mobile-header]')\n const title = header === null ? undefined : header.querySelector<HTMLElement>('[class*=\"_crumbCurrent\"]')?.textContent?.trim()\n branchToast.textContent = title === undefined ? '当前分支' : `当前分支:${title}`\n branchToast.dataset.visible = 'true'\n if (branchToastTimer !== 0) window.clearTimeout(branchToastTimer)\n branchToastTimer = window.setTimeout(() => {\n branchToast.removeAttribute('data-visible')\n branchToastTimer = 0\n }, 1600)\n }\n const onBranchClick = (event: MouseEvent): void => {\n if (!(event.target instanceof Element)) return\n const branch = event.target.closest<HTMLButtonElement>('button[aria-label*=\"分支\"],button[aria-label*=\"Branch\"],button[aria-label*=\"branch\"]')\n if (branch === null || branch.hasAttribute('disabled') || branch.getAttribute('aria-disabled') === 'true') return\n window.setTimeout(showBranchToast, 80)\n }\n document.addEventListener('click', onBranchClick, true)\n let frame: HTMLElement | undefined\n let sidebar: HTMLElement | undefined\n let sidebarRoot: HTMLElement | undefined\n let toggle: HTMLButtonElement | undefined\n let viewArea: HTMLElement | undefined\n let scheduled = 0\n let transitionFrame = 0\n let transitionRestartFrame = 0\n let transitionTimer = 0\n let transitionTarget: HTMLElement | undefined\n const animateNavigation = (event: MouseEvent): void => {\n if (!(event.target instanceof Element)) return\n const trigger = event.target.closest<HTMLElement>('button,a,[role=\"tab\"],[aria-selected]')\n if (trigger === null || trigger.hasAttribute('disabled') || trigger.getAttribute('aria-disabled') === 'true') return\n if (trigger.getAttribute('aria-selected') === 'true' || trigger.getAttribute('aria-current') === 'true') return\n const settingsNavigation = trigger.closest('[data-dsh-mobile-settings-list]') !== null\n const conversationNavigation = trigger.matches('[role=\"tab\"]')\n const sidebarNavigation = trigger.closest('[data-dsh-mobile-sidebar-root]') !== null\n && trigger.closest('[data-dsh-mobile-toggle]') === null\n if (!settingsNavigation && !conversationNavigation && !sidebarNavigation) return\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return\n if (transitionFrame !== 0) cancelAnimationFrame(transitionFrame)\n if (transitionRestartFrame !== 0) cancelAnimationFrame(transitionRestartFrame)\n transitionFrame = requestAnimationFrame(() => {\n transitionFrame = 0\n const target = settingsNavigation\n ? document.querySelector<HTMLElement>('[data-dsh-mobile-settings-content]')\n : viewArea\n if (target === null || target === undefined) return\n transitionTarget?.removeAttribute('data-dsh-mobile-view-transition')\n target.removeAttribute('data-dsh-mobile-view-transition')\n transitionRestartFrame = requestAnimationFrame(() => {\n transitionRestartFrame = 0\n transitionTarget = target\n target.dataset.dshMobileViewTransition = 'true'\n if (transitionTimer !== 0) clearTimeout(transitionTimer)\n transitionTimer = window.setTimeout(() => {\n target.removeAttribute('data-dsh-mobile-view-transition')\n if (transitionTarget === target) transitionTarget = undefined\n transitionTimer = 0\n }, 240)\n })\n })\n }\n document.addEventListener('click', animateNavigation)\n\n const sync = (): void => {\n scheduled = 0\n frame = firstByClassSuffix(document, '_frame')\n const dedicatedCenter = document.querySelector<HTMLElement>('.dshm-main') ?? undefined\n if (frame !== undefined) frame.dataset.dshMobileFrame = 'true'\n sidebar = frame === undefined\n ? document.querySelector<HTMLElement>('.dshm-drawer') ?? undefined\n : firstByClassSuffix(frame, '_sidebarCol')\n const center = frame === undefined ? dedicatedCenter : firstByClassSuffix(frame, '_centerCol')\n const details = frame === undefined ? undefined : firstByClassSuffix(frame, '_detailsCol')\n const handle = frame === undefined ? undefined : firstByClassSuffix(frame, '_handle')\n if (center === undefined) return\n if (center !== undefined) {\n center.dataset.dshMobileCenter = 'true'\n center.querySelector<HTMLElement>('header')?.setAttribute('data-dsh-mobile-header', 'true')\n viewArea = firstByClassSuffix(center, '_viewArea')\n if (viewArea !== undefined) viewArea.dataset.dshMobileView = 'true'\n const conversation = center.querySelector<HTMLElement>('[data-conversation-scroll]')\n const messageColumn = conversation === null ? undefined : firstByClassSuffix(conversation, '_column')\n const messageScroll = messageColumn?.parentElement\n if (messageColumn !== undefined && messageScroll !== null && messageScroll !== undefined && classToken(messageScroll, '_scroll')) {\n messageColumn.dataset.dshMobileMessageColumn = 'true'\n messageScroll.dataset.dshMobileMessageScroll = 'true'\n }\n for (const table of center.querySelectorAll<HTMLTableElement>('table')) {\n const parent = table.parentElement\n if (parent?.dataset.dshMobileTableScroll === 'true') continue\n const wrapper = document.createElement('div')\n wrapper.dataset.dshMobileTableScroll = 'true'\n table.before(wrapper)\n wrapper.append(table)\n }\n }\n if (handle !== undefined) handle.dataset.dshMobileHandle = 'true'\n if (details !== undefined) {\n details.dataset.dshMobileDetails = 'true'\n const lastColumn = frame?.style.gridTemplateColumns.trim().split(/\\s+/).at(-1)\n details.dataset.open = String(lastColumn !== undefined && lastColumn !== '0px' && lastColumn !== '0')\n }\n const settings = Array.from(document.querySelectorAll<HTMLElement>('[role=\"dialog\"]')).find(dialog => {\n const directNav = Array.from(dialog.children).find(child => child instanceof HTMLElement && classToken(child, '_nav'))\n return directNav !== undefined\n })\n if (settings !== undefined) {\n settings.dataset.dshMobileSettings = 'true'\n const nav = Array.from(settings.children).find(child => child instanceof HTMLElement && classToken(child, '_nav')) as HTMLElement | undefined\n const content = Array.from(settings.children).find(child => child instanceof HTMLElement && classToken(child, '_content')) as HTMLElement | undefined\n if (nav !== undefined) {\n nav.dataset.dshMobileSettingsNav = 'true'\n firstByClassSuffix(nav, '_navList')?.setAttribute('data-dsh-mobile-settings-list', 'true')\n }\n if (content !== undefined) {\n content.dataset.dshMobileSettingsContent = 'true'\n firstByClassSuffix(content, '_header')?.setAttribute('data-dsh-mobile-settings-header', 'true')\n firstByClassSuffix(content, '_options')?.setAttribute('data-dsh-mobile-settings-options', 'true')\n }\n }\n if (sidebar === undefined) return\n sidebar.dataset.dshMobileSidebar = 'true'\n toggle = firstByClassSuffix(sidebar, '_toggle') as HTMLButtonElement | undefined\n let candidate = toggle?.parentElement\n while (candidate !== undefined && candidate !== null && candidate !== sidebar && !classToken(candidate, '_root')) candidate = candidate.parentElement\n sidebarRoot = candidate !== sidebar && candidate !== null ? candidate : undefined\n if (sidebarRoot === undefined) return\n sidebarRoot.dataset.dshMobileSidebarRoot = 'true'\n if (toggle !== undefined) toggle.dataset.dshMobileToggle = 'true'\n const collapsed = classToken(sidebarRoot, '_collapsed')\n sidebar.dataset.open = String(!collapsed)\n backdrop.hidden = collapsed\n }\n const schedule = (): void => { if (scheduled === 0) scheduled = requestAnimationFrame(sync) }\n const observer = new MutationObserver(schedule)\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] })\n backdrop.addEventListener('click', () => { if (sidebar?.dataset.open === 'true') toggle?.click() })\n sync()\n return () => {\n observer.disconnect()\n document.removeEventListener('click', onBranchClick, true)\n if (branchToastTimer !== 0) window.clearTimeout(branchToastTimer)\n branchToast.remove()\n if (scheduled !== 0) cancelAnimationFrame(scheduled)\n if (transitionFrame !== 0) cancelAnimationFrame(transitionFrame)\n if (transitionRestartFrame !== 0) cancelAnimationFrame(transitionRestartFrame)\n if (transitionTimer !== 0) clearTimeout(transitionTimer)\n transitionTarget?.removeAttribute('data-dsh-mobile-view-transition')\n document.removeEventListener('pointerdown', onPointerDown, true)\n document.removeEventListener('keydown', onKeyDown, true)\n document.removeEventListener('click', animateNavigation)\n backdrop.remove()\n document.documentElement.classList.remove('dsh-native-mobile-active')\n delete document.documentElement.dataset.dshMobileInput\n }\n}\n","import { createElement } from 'react'\nimport { installNativeMobileSurface, NATIVE_MOBILE_STYLES } from './native-mobile.js'\n\ninterface ClientContext {\n effect(effect: () => void | (() => void), label?: string): void\n slots: {\n inject(key: string, callback: () => (() => void)): () => void\n register(options: { name: string; id: string }, component: (props: { wide: boolean }) => unknown): () => void\n }\n}\n\ninterface MobileExtensionContext {\n readonly document: Document\n readonly request: (path: string, init?: RequestInit) => Promise<Response>\n readonly root: HTMLElement\n readonly window: Window\n}\n\ntype MobileExtensionMount = (context: MobileExtensionContext) => void | (() => void)\n\ndeclare global {\n interface Window {\n dshMobile?: { register(mount: MobileExtensionMount): void }\n __DSH_MOBILE_FRONTEND__?: 'dedicated'\n }\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '[::1]'\n}\n\nfunction element<K extends keyof HTMLElementTagNameMap>(tag: K, className?: string): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag)\n if (className !== undefined) node.className = className\n return node\n}\n\nasync function requestJson(path: string, init?: RequestInit): Promise<Record<string, unknown>> {\n const response = await fetch(path, { ...init, headers: { 'content-type': 'application/json', ...init?.headers } })\n const body = await response.json() as Record<string, unknown>\n if (!response.ok) throw new Error(typeof body.error === 'string' ? body.error : `HTTP ${String(response.status)}`)\n return body\n}\n\nfunction installControl(): { remove: () => void; toggle: () => void } {\n const root = element('div', 'dsh-mobile-control')\n const panel = element('section', 'dsh-mobile-control__panel'); panel.hidden = true\n panel.setAttribute('aria-label', '移动访问')\n const header = element('header', 'dsh-mobile-control__header')\n const title = element('h2'); title.textContent = '移动访问'\n const close = element('button', 'dsh-mobile-control__close'); close.type = 'button'; close.textContent = '×'; close.setAttribute('aria-label', '收起移动访问')\n const access = element('div', 'dsh-mobile-control__access'); access.hidden = true\n const accessLabel = element('span', 'dsh-mobile-control__access-label'); accessLabel.textContent = '浏览器访问'\n const accessLink = element('a', 'dsh-mobile-control__access-link'); accessLink.target = '_blank'; accessLink.rel = 'noreferrer'\n access.append(accessLabel, accessLink)\n const qrBox = element('div', 'dsh-mobile-control__qr'); qrBox.hidden = true\n const status = element('p', 'dsh-mobile-control__status'); status.textContent = '正在读取状态…'\n const actions = element('div', 'dsh-mobile-control__actions')\n const toggle = element('button', 'dsh-mobile-control__secondary'); toggle.type = 'button'\n const pair = element('button', 'dsh-mobile-control__primary'); pair.type = 'button'; pair.textContent = '生成并复制密钥'\n const linkPair = element('button', 'dsh-mobile-control__secondary'); linkPair.type = 'button'; linkPair.textContent = '复制配对链接'\n const manageRow = element('div', 'dsh-mobile-control__manage-row')\n const manageDevices = element('button', 'dsh-mobile-control__manage'); manageDevices.type = 'button'; manageDevices.textContent = '管理配对设备'\n const resetAll = element('button', 'dsh-mobile-control__manage'); resetAll.type = 'button'; resetAll.textContent = '清除所有设备'\n manageRow.append(manageDevices, resetAll)\n const devicePanel = element('div', 'dsh-mobile-control__devices'); devicePanel.hidden = true\n header.append(title, close); actions.append(toggle, pair, linkPair); panel.append(header, access, qrBox, status, actions, manageRow, devicePanel); root.append(panel); document.body.append(root)\n let running = false\n let origin = ''\n const setOpen = (open: boolean): void => {\n panel.hidden = !open\n for (const trigger of document.querySelectorAll('.dsh-mobile-control__trigger')) trigger.setAttribute('aria-expanded', String(open))\n }\n const render = (data: Record<string, unknown>): void => {\n running = data.running === true\n origin = running && typeof data.origin === 'string' ? data.origin : ''\n access.hidden = origin === ''\n accessLink.href = origin\n accessLink.textContent = origin\n accessLink.title = origin\n status.classList.toggle('is-running', running)\n status.textContent = running ? '移动访问已开启。' : '已关闭。DSH 仍只在本机可用。'\n if (!running) qrBox.hidden = true\n toggle.textContent = running ? '关闭移动访问' : '开启移动访问'\n pair.disabled = !running\n linkPair.disabled = !running\n manageDevices.disabled = !running\n resetAll.disabled = !running\n }\n const showQr = (svg: string): void => {\n qrBox.replaceChildren()\n if (svg === '') { qrBox.hidden = true; return }\n const image = element('img')\n image.alt = '配对二维码'\n image.width = 176\n image.height = 176\n image.src = `data:image/svg+xml;base64,${btoa(svg)}`\n qrBox.hidden = false\n qrBox.append(image)\n }\n const openPairing = (target: 'key' | 'link'): void => {\n void requestJson('/api/mobile-access/pairing/open', { method: 'POST', body: '{}' }).then(async data => {\n const value = target === 'key'\n ? (typeof data.appKey === 'string' ? data.appKey : '')\n : (typeof data.pairUrl === 'string' ? data.pairUrl : '')\n showQr(typeof data.qrSvg === 'string' ? data.qrSvg : '')\n if (value === '') { status.textContent = '无法生成配对密钥。'; return }\n try {\n await navigator.clipboard.writeText(value)\n status.textContent = target === 'key'\n ? '配对密钥已复制,请粘贴到 Android App。'\n : '配对链接已复制,发给手机后 App 粘贴或浏览器打开即可配对。'\n } catch {\n status.textContent = `请复制${target === 'key' ? '配对密钥' : '配对链接'}:${value}`\n status.classList.add('is-key')\n }\n }, error => { status.textContent = String(error) }).finally(() => {\n pair.disabled = !running\n linkPair.disabled = !running\n })\n }\n toggle.addEventListener('click', () => { toggle.disabled = true; void requestJson('/api/mobile-access/control', { method: 'POST', body: JSON.stringify({ running: !running }) }).then(render, error => { status.textContent = String(error) }).finally(() => { toggle.disabled = false }) })\n const formatTime = (ms: unknown): string => typeof ms === 'number' ? new Date(ms).toLocaleString() : ''\n const renderDevices = (data: Record<string, unknown>): void => {\n const devices = Array.isArray(data.devices) ? data.devices as Record<string, unknown>[] : []\n devicePanel.replaceChildren()\n if (devices.length === 0) {\n const empty = element('p', 'dsh-mobile-control__device-empty'); empty.textContent = '暂无配对设备。'\n devicePanel.append(empty)\n return\n }\n for (const device of devices) {\n const row = element('div', 'dsh-mobile-control__device')\n const label = element('span', 'dsh-mobile-control__device-label')\n label.textContent = typeof device.label === 'string' ? device.label : '设备'\n const meta = element('span', 'dsh-mobile-control__device-meta'); meta.textContent = `到期 ${formatTime(device.expiresAt)}`\n const revoke = element('button', 'dsh-mobile-control__device-revoke'); revoke.type = 'button'; revoke.textContent = '撤销'\n const id = typeof device.id === 'string' ? device.id : ''\n revoke.addEventListener('click', () => {\n void requestJson('/api/mobile-access/devices/revoke', { method: 'POST', body: JSON.stringify({ deviceId: id }) })\n .then(loadDevices, error => { status.textContent = String(error) })\n })\n row.append(label, meta, revoke)\n devicePanel.append(row)\n }\n }\n const loadDevices = (): void => {\n void requestJson('/api/mobile-access/devices').then(renderDevices, error => { status.textContent = String(error) })\n }\n manageDevices.addEventListener('click', () => {\n const show = devicePanel.hidden\n devicePanel.hidden = !show\n if (show) loadDevices()\n })\n resetAll.addEventListener('click', () => {\n if (!window.confirm('确定要移除所有配对设备吗?此操作会立即终止已连接设备。')) return\n void requestJson('/api/mobile-access/devices/reset', { method: 'POST', body: JSON.stringify({ confirm: true }) })\n .then(loadDevices, error => { status.textContent = String(error) })\n })\n pair.addEventListener('click', () => { pair.disabled = true; openPairing('key') })\n linkPair.addEventListener('click', () => { linkPair.disabled = true; openPairing('link') })\n close.addEventListener('click', () => { setOpen(false) })\n const dismiss = (event: PointerEvent): void => {\n if (panel.hidden || !(event.target instanceof Node)) return\n if (!panel.contains(event.target) && !document.querySelector('.dsh-mobile-control__trigger')?.contains(event.target)) setOpen(false)\n }\n document.addEventListener('pointerdown', dismiss)\n void requestJson('/api/mobile-access/control').then(render, error => { status.textContent = String(error) })\n return { remove: () => { document.removeEventListener('pointerdown', dismiss); root.remove() }, toggle: () => { setOpen(panel.hidden !== false) } }\n}\n\nfunction mobileRequest(path: string, init: RequestInit = {}): Promise<Response> {\n const target = new URL(path, location.href)\n if (target.origin !== location.origin) throw new TypeError('mobile extension requests must stay on the DSH origin')\n const headers = new Headers(init.headers)\n const method = (init.method ?? 'GET').toUpperCase()\n if (method !== 'GET' && method !== 'HEAD') {\n const csrf = document.cookie.split(';').map(value => value.trim()).find(value => value.startsWith('dsh_ma_csrf='))?.slice(12)\n if (csrf !== undefined) headers.set('x-dsh-mobile-csrf', csrf)\n }\n return fetch(target, { ...init, headers, credentials: 'same-origin', cache: 'no-store', redirect: 'error' })\n}\n\nfunction installCustomAssets(): () => void {\n const style = element('style'); style.dataset.plugin = 'dsh-mobile-custom'; document.head.append(style)\n let cssLoading = false\n const refreshCss = async (): Promise<void> => { if (cssLoading || document.visibilityState === 'hidden') return; cssLoading = true; try { const response = await fetch('/mobile-access/custom.css', { cache: 'no-store', credentials: 'same-origin' }); if (response.ok) { const css = await response.text(); if (style.textContent !== css) style.textContent = css } } finally { cssLoading = false } }\n let activeRoot: HTMLElement | undefined; let disposeActive: (() => void) | undefined; let source = ''; let pending: MobileExtensionMount | undefined\n const previous = window.dshMobile\n window.dshMobile = Object.freeze({ register: (mount: MobileExtensionMount) => { pending = mount } })\n const registeredMount = (): MobileExtensionMount | undefined => pending\n const refreshJs = async (): Promise<void> => { try { const response = await fetch('/mobile-access/custom.js', { cache: 'no-store', credentials: 'same-origin' }); if (!response.ok) return; const next = await response.text(); if (next === source) return; source = next; pending = undefined; const script = element('script'); script.textContent = `${next}\\n//# sourceURL=dsh-mobile-custom.js`; document.head.append(script); script.remove(); const mount = registeredMount(); if (mount === undefined) return; const nextRoot = element('div'); nextRoot.dataset.dshMobileExtension = 'true'; document.body.append(nextRoot); const nextDispose = mount({ document, request: mobileRequest, root: nextRoot, window }); disposeActive?.(); activeRoot?.remove(); activeRoot = nextRoot; disposeActive = typeof nextDispose === 'function' ? nextDispose : undefined } catch { /* Preserve the last good customization during reconnects. */ } }\n void refreshCss(); void refreshJs()\n const timer = window.setInterval(() => { void refreshCss(); void refreshJs() }, 1_000)\n return () => { clearInterval(timer); disposeActive?.(); activeRoot?.remove(); style.remove(); if (previous === undefined) delete window.dshMobile; else window.dshMobile = previous }\n}\n\nconst CONTROL_STYLES = `\n.dsh-mobile-control{position:fixed;z-index:1000;left:16px;bottom:64px;font:14px/1.45 system-ui;color:var(--dsw-alias-label-primary,#16181d)}\n.dsh-mobile-control__panel{box-sizing:border-box;width:min(328px,calc(100vw - 32px));padding:16px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:18px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 18px 50px rgb(15 23 42 / 18%)}\n.dsh-mobile-control__header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}.dsh-mobile-control__panel h2{margin:0;font-size:17px;line-height:24px}.dsh-mobile-control__close{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:0;border-radius:10px;background:transparent;color:inherit;font-size:24px;line-height:1;cursor:pointer}.dsh-mobile-control__close:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}\n.dsh-mobile-control__access{display:flex;align-items:baseline;gap:6px;min-width:0;margin:0 0 12px}.dsh-mobile-control__access[hidden]{display:none}.dsh-mobile-control__access-label{flex:none;color:var(--dsw-alias-label-secondary,#606873);white-space:nowrap}.dsh-mobile-control__access-label::after{content:\":\"}.dsh-mobile-control__access-link{min-width:0;overflow:hidden;color:#2563eb;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__access-link:hover{text-decoration:underline}.dsh-mobile-control__qr{display:flex;justify-content:center;margin:0 0 12px}.dsh-mobile-control__qr[hidden]{display:none}.dsh-mobile-control__qr img{border-radius:12px;background:#fff;padding:8px}\n.dsh-mobile-control__status{margin:0 0 14px;overflow-wrap:anywhere;color:var(--dsw-alias-label-secondary,#606873)}.dsh-mobile-control__status::before{display:inline-block;width:8px;height:8px;margin-right:7px;border-radius:50%;background:#98a1ad;content:\"\"}.dsh-mobile-control__status.is-running::before{background:#16a36a}.dsh-mobile-control__status.is-key{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;word-break:break-all}\n.dsh-mobile-control__actions{display:flex;flex-wrap:nowrap;gap:6px}.dsh-mobile-control__actions button{flex:1 1 0;min-width:0;min-height:40px;padding:8px 4px;border-radius:10px;font:12px/1.2 system-ui;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dsh-mobile-control__secondary{border:1px solid var(--dsw-alias-border-normal,#cfd5dd);background:transparent;color:inherit}.dsh-mobile-control__primary{border:1px solid #2563eb;background:#2563eb;color:#fff}.dsh-mobile-control__actions button:disabled{cursor:not-allowed;opacity:.45}\n.dsh-mobile-control__trigger{box-sizing:border-box;display:flex;align-items:center;gap:8px;width:calc(100% + 8px);height:34px;margin:4px -4px;padding:6px 2px 6px 10px;border:0;border-radius:12px;background:transparent;color:var(--dsw-alias-label-primary,#16181d);font:14px/22px system-ui;cursor:pointer}.dsh-mobile-control__trigger:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}.dsh-mobile-control__trigger.is-rail{width:36px;height:36px;margin:8px 0 10px;padding:0;justify-content:center;border-radius:50%}.dsh-mobile-control__trigger-icon{position:relative;box-sizing:border-box;flex:none;width:14px;height:19px;border:1.7px solid currentColor;border-radius:3px}.dsh-mobile-control__trigger-icon::after{position:absolute;right:4px;bottom:2px;width:4px;height:1.5px;border-radius:2px;background:currentColor;content:\"\"}.dsh-mobile-control__trigger-label{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}\n.dsh-mobile-control__manage-row{display:flex;justify-content:space-between;gap:8px;margin-top:10px}.dsh-mobile-control__manage{flex:1 1 0;min-width:0;min-height:34px;padding:6px 8px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:transparent;color:inherit;font:12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__devices{margin-top:10px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:10px;padding:8px;max-height:220px;overflow-y:auto}.dsh-mobile-control__device-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px;margin:0}.dsh-mobile-control__device{display:flex;align-items:center;gap:8px;padding:6px 2px}.dsh-mobile-control__device + .dsh-mobile-control__device{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__device-label{flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.dsh-mobile-control__device-meta{flex:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;white-space:nowrap}.dsh-mobile-control__device-revoke{flex:none;min-height:28px;padding:4px 8px;border:1px solid #dc2626;border-radius:8px;background:transparent;color:#dc2626;font:12px/1.2 system-ui;cursor:pointer}\n`\n\n/** Mount the desktop control or mobile feature enhancements. */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => {\n const loopback = isLoopbackHost(location.hostname) && !new URLSearchParams(location.search).has('dsh-mobile-preview')\n const style = element('style'); style.dataset.plugin = 'dsh-mobile'; style.textContent = loopback\n ? CONTROL_STYLES\n : NATIVE_MOBILE_STYLES\n document.head.append(style)\n if (!loopback) {\n const removeCustom = installCustomAssets()\n const removeSurface = installNativeMobileSurface()\n return () => { removeCustom(); removeSurface(); style.remove() }\n }\n const control = installControl()\n const disposeSlot = ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({ name: 'sidebar.footer.action', id: 'dsh-mobile' }, ({ wide }) => createElement('button', {\n 'aria-expanded': false,\n 'aria-label': '移动访问',\n className: `dsh-mobile-control__trigger${wide ? '' : ' is-rail'}`,\n type: 'button',\n title: '移动访问',\n onClick: control.toggle,\n }, createElement('span', { 'aria-hidden': true, className: 'dsh-mobile-control__trigger-icon' }), wide ? createElement('span', { className: 'dsh-mobile-control__trigger-label' }, '移动访问') : undefined)))\n return () => { disposeSlot(); control.remove(); style.remove() }\n }, 'dsh-mobile: stock mobile adaptation and local control')\n}\n\n/** Client services required by the mobile adaptation. */\nexport const inject: readonly string[] = ['slots']\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mobile",
3
- "version": "0.1.0-alpha.28",
3
+ "version": "0.1.0-alpha.29",
4
4
  "private": false,
5
5
  "description": "DeepSeek Harness 移动端适配与安全局域网访问插件,支持 Android App 和手机浏览器。",
6
6
  "type": "module",