dsh-enhancement 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +98 -51
- package/README.md +93 -84
- package/lib/client.js +33 -1
- package/lib/control.d.ts +2 -2
- package/lib/control.js +12 -2
- package/lib/focus.d.ts +0 -1
- package/lib/focus.js +0 -1
- package/lib/gateway.d.ts +0 -1
- package/lib/gateway.js +9 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.js +9 -2
- package/lib/notify.d.ts +0 -1
- package/lib/notify.js +1 -1
- package/lib/push.d.ts +0 -1
- package/lib/push.js +0 -1
- package/lib/pwa.d.ts +0 -1
- package/lib/pwa.js +0 -1
- package/lib/session-header.d.ts +113 -0
- package/lib/session-header.js +255 -0
- package/lib/state.d.ts +2 -1
- package/lib/state.js +7 -1
- package/lib/tls.d.ts +0 -1
- package/lib/tls.js +0 -1
- package/lib/types.d.ts +12 -1
- package/lib/types.js +0 -1
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +7 -4
- package/src/client/index.tsx +81 -0
- package/src/control.ts +10 -1
- package/src/gateway.ts +9 -0
- package/src/index.ts +9 -1
- package/src/notify.ts +1 -0
- package/src/session-header.ts +366 -0
- package/src/state.ts +9 -0
- package/src/types.ts +13 -0
- package/test/session-header-e2e.mjs +124 -0
- package/test/session-header.test.mjs +366 -0
- package/lib/control.d.ts.map +0 -1
- package/lib/control.js.map +0 -1
- package/lib/focus.d.ts.map +0 -1
- package/lib/focus.js.map +0 -1
- package/lib/gateway.d.ts.map +0 -1
- package/lib/gateway.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/notify.d.ts.map +0 -1
- package/lib/notify.js.map +0 -1
- package/lib/push.d.ts.map +0 -1
- package/lib/push.js.map +0 -1
- package/lib/pwa.d.ts.map +0 -1
- package/lib/pwa.js.map +0 -1
- package/lib/state.d.ts.map +0 -1
- package/lib/state.js.map +0 -1
- package/lib/tls.d.ts.map +0 -1
- package/lib/tls.js.map +0 -1
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -1,89 +1,136 @@
|
|
|
1
1
|
# dsh-enhancement
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[简体中文](README.md) | **English**
|
|
6
|
+
|
|
7
|
+
A standalone password gateway for DSH Web, plus everything needed to use DSH like an app on your phone: PWA installation, desktop notifications, and mobile push.
|
|
8
|
+
|
|
9
|
+
Set a password, open the gateway URL, and log in to DSH without carrying around the `?token=` URL printed by `dsh web`. Add DSH to your phone's home screen and get notified when a task finishes or needs your approval—tap the notification to return to the right session.
|
|
6
10
|
|
|
7
11
|
## Features
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
| Feature | What it does |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| Password gateway | Access DSH through a dedicated port and password instead of `?token=`, suitable for LAN or remote use |
|
|
16
|
+
| HTTPS | Upload a certificate, sign with your own CA, or let the plugin create a local CA |
|
|
17
|
+
| PWA | Install DSH on the desktop or phone home screen, with custom app name, theme color, and icons |
|
|
18
|
+
| Desktop notifications | System notifications for task completion, questions, approvals, and background jobs |
|
|
19
|
+
| Mobile push | PWA Web Push notifications on your phone; tap to return to the matching session |
|
|
20
|
+
| Session header | Send a DSH session-id request header on third-party API routes too, so a gateway can correlate requests by session |
|
|
10
21
|
|
|
11
|
-
|
|
22
|
+
## Password gateway
|
|
12
23
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
24
|
+
- Off by default; listens on port `3088` by default, configurable to another free port.
|
|
25
|
+
- A password must be set before the gateway can be enabled. Only a hash of the password is stored.
|
|
26
|
+
- After login, the gateway keeps its own session; DSH's `?token=` is not needed.
|
|
27
|
+
- Choose which network interface to listen on.
|
|
28
|
+
- Works with normal DSH chat, files, terminal, WebSocket, and live streams.
|
|
29
|
+
- Configurable login session lifetime; changing the password invalidates all gateway sessions.
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
## HTTPS
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
PWA installation, Service Workers, and mobile push require HTTPS (localhost is exempt). Three options are available:
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
| Option | When to use it |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| Off (HTTP) | Temporary local/trusted-network use; PWA and push are unavailable |
|
|
38
|
+
| Use an existing certificate | You already have a domain certificate; upload the chain and private key |
|
|
39
|
+
| Sign with a CA | Upload your own CA, or let the plugin generate a local CA and issue a certificate for the current address |
|
|
25
40
|
|
|
26
|
-
|
|
41
|
+
For a self-signed/private CA, install and trust the CA certificate on each device:
|
|
27
42
|
|
|
28
|
-
|
|
43
|
+
- iOS: Settings → General → VPN & Device Management to install the profile, then General → About → Certificate Trust Settings and enable full trust.
|
|
44
|
+
- Android: Settings → Security → Encryption & credentials → Install a certificate.
|
|
29
45
|
|
|
30
|
-
|
|
46
|
+
The certificate must cover the hostname or IP you actually use (SAN), otherwise browsers will still show a certificate error.
|
|
31
47
|
|
|
32
|
-
|
|
48
|
+
## PWA
|
|
33
49
|
|
|
34
|
-
|
|
50
|
+
- Takes over DSH's manifest, icons, `apple-touch-icon`, Service Worker, and install prompt.
|
|
51
|
+
- Customize the app name, short name, theme color, and replace desktop/Android/iOS icons separately.
|
|
52
|
+
- Chromium / Edge show an install prompt; iPhone / iPad use Safari's “Share → Add to Home Screen”.
|
|
53
|
+
- Installed apps open without browser chrome; HTTPS is required for installation, Service Worker, and push.
|
|
35
54
|
|
|
36
|
-
|
|
55
|
+
## Session header
|
|
37
56
|
|
|
38
|
-
|
|
57
|
+
DSH only sends `x-deepseek-harness-session-id` when talking to the official DeepSeek API; third-party routes through OpenAI-compatible gateways (`llm-pi-ai`) carry no session identifier at all. With this feature on, the plugin injects the current session id as a request header so your gateway can correlate requests by session.
|
|
39
58
|
|
|
40
|
-
|
|
59
|
+
- **Off by default**; the default header name matches the official DeepSeek one: `x-deepseek-harness-session-id`.
|
|
60
|
+
- The header name is configurable for gateways with their own convention; the value is always DSH's raw session id.
|
|
61
|
+
- Applies only to third-party API adapters (`llm-pi-ai`); official DeepSeek routes already send it and are untouched.
|
|
62
|
+
- Takes effect on the next model request after saving — no restart needed.
|
|
63
|
+
- An existing header of the same name set upstream is never overwritten.
|
|
41
64
|
|
|
42
|
-
|
|
43
|
-
dsh plugin --profile web add dsh-enhancement
|
|
44
|
-
```
|
|
65
|
+
> Implementation note: injection happens on the third-party adapter's `streamWithSnapshot` prototype method, because DSH rebuilds the pi-ai options inside it — the `llm/stream` layer cannot change request headers. The feature therefore depends on host internals; if the host shape changes, it bypasses itself and records the reason instead of blocking requests.
|
|
45
66
|
|
|
46
|
-
|
|
67
|
+
### Why it says "not mounted yet"
|
|
47
68
|
|
|
48
|
-
|
|
69
|
+
`dsh-base` mounts `llm-pi-ai` in a **dormant** state: with no provider routes configured it never registers an adapter (`routes.length === 0` returns early), so there is nothing to inject into. That is a normal state, not a failure. The settings page distinguishes the causes:
|
|
49
70
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
| Message | Meaning | What to do |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| Mounted on N third-party API adapters | Working | — |
|
|
74
|
+
| No third-party model routes registered | No OpenAI-compatible provider configured yet | Add one under Settings → Models, or declare `providers` for `llm-pi-ai` in this profile's `cordis.patch.yml` |
|
|
75
|
+
| N adapters registered, none is a third-party API adapter | Only official DeepSeek routes | Same as above: add a third-party provider |
|
|
76
|
+
| Host llm service unavailable or reshaped | Version drift | Plugin has bypassed itself; requests are unaffected |
|
|
77
|
+
|
|
78
|
+
## Notifications
|
|
56
79
|
|
|
57
|
-
|
|
80
|
+
Supported events, all enabled by default and individually selectable:
|
|
58
81
|
|
|
59
|
-
|
|
82
|
+
- Task completed
|
|
83
|
+
- Waiting for your answer
|
|
84
|
+
- Waiting for approval
|
|
85
|
+
- Approval denied
|
|
86
|
+
- Subagent finished
|
|
87
|
+
- Goal completed or blocked
|
|
88
|
+
- Background job finished (not every command)
|
|
60
89
|
|
|
61
|
-
|
|
90
|
+
Desktop notifications and mobile push share the event selection but can be enabled independently.
|
|
62
91
|
|
|
63
|
-
|
|
92
|
+
Clicking a desktop notification opens the matching session. On Linux and Windows, if DSH is already open, it focuses the existing page and switches to that session; on macOS the system notification opens the link. On Linux, the system notification service must support actions (common desktop environments do); if notifications appear but clicks do nothing, check that `notify-send` and an action-capable notification service are available.
|
|
93
|
+
|
|
94
|
+
## Mobile push
|
|
95
|
+
|
|
96
|
+
- Standard Web Push with one subscription per device.
|
|
97
|
+
- Click “Subscribe this device” in settings; the browser asks for notification permission, which must be triggered by your click.
|
|
98
|
+
- iOS / iPadOS 16.4+ is required, and DSH must be added to the Home Screen; regular Safari tabs cannot receive push.
|
|
99
|
+
- Unsubscribe at any time, or send a test push to verify the setup.
|
|
100
|
+
|
|
101
|
+
## Install
|
|
64
102
|
|
|
65
103
|
```bash
|
|
66
|
-
|
|
67
|
-
dsh --profile enhancement-test --host 10.0.0.2 --port 3090 --no-open
|
|
104
|
+
dsh plugin --profile web add dsh-enhancement
|
|
68
105
|
```
|
|
69
106
|
|
|
70
|
-
|
|
107
|
+
Restart DSH Web after installation. The plugin brings in the mobile and portrait-layout dependencies automatically; do not install or enable them separately.
|
|
71
108
|
|
|
72
|
-
##
|
|
109
|
+
## Getting started
|
|
73
110
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
111
|
+
1. Open DSH → Settings → Plugins → `dsh-enhancement`.
|
|
112
|
+
2. Under “Access control”, set a password and save. The gateway enable switch becomes available after a password is set.
|
|
113
|
+
3. Under “Gateway”, choose the listen address and port (default `3088`), enable the gateway, and save.
|
|
114
|
+
4. If you need HTTPS, choose a certificate option in the same section and save. For a self-signed CA, install and trust the CA certificate on your devices.
|
|
115
|
+
5. Open the gateway URL on your phone or another computer and log in with the password; DSH's `?token=` is not required.
|
|
116
|
+
6. To use it as an app, install the PWA in your browser; on iOS use “Share → Add to Home Screen”.
|
|
117
|
+
7. Under “Notifications”, select the events you want and click “Test desktop notification”. For mobile push, enable push and click “Subscribe this device”.
|
|
79
118
|
|
|
80
|
-
##
|
|
119
|
+
## Tips
|
|
81
120
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
121
|
+
- Use HTTPS and a strong password before exposing the gateway to the internet.
|
|
122
|
+
- Do not reuse DSH's own port (default `3080`) for the gateway; choose another free port if needed.
|
|
123
|
+
- Self-signed certificates are best for devices you control; use a proper certificate when sharing with others.
|
|
124
|
+
- If you only use DSH locally, leave the gateway off; the normal DSH access path is unaffected.
|
|
125
|
+
|
|
126
|
+
## Limitations
|
|
127
|
+
|
|
128
|
+
- The gateway, PWA, and push are off by default and require setup; the gateway cannot be enabled before a password is set.
|
|
129
|
+
- PWA installation, Service Worker, and Web Push all require HTTPS.
|
|
130
|
+
- iOS push requires 16.4+ and installation on the Home Screen.
|
|
131
|
+
- A self-signed/private CA must be installed and trusted on every device.
|
|
132
|
+
- Linux notification click support depends on the notification service implementing actions.
|
|
133
|
+
- Changing the password invalidates all gateway sessions, so you will need to log in again.
|
|
87
134
|
|
|
88
135
|
## License
|
|
89
136
|
|
package/README.md
CHANGED
|
@@ -1,128 +1,137 @@
|
|
|
1
1
|
# dsh-enhancement
|
|
2
2
|
|
|
3
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
4
|
+
|
|
3
5
|
**简体中文** | [English](README.en.md)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
给 DSH Web 加一层独立的密码入口,同时把它变成可以在手机主屏使用、会发通知的 Web 应用。
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
设置独立密码后,打开网关地址即可登录 DSH,无需携带 `dsh web` 启动时打印的 `?token=` 地址;装入手机主屏后,任务完成、等待审批等事件会直接推送给你,点通知即可回到对应会话。
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## 功能一览
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
| 功能 | 能做什么 |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| 密码网关 | 用独立端口和密码访问 DSH,无需 `?token=`,适合局域网或远程使用 |
|
|
16
|
+
| HTTPS | 支持上传证书、使用自己的 CA 签发,或由插件自动生成本地 CA |
|
|
17
|
+
| PWA | 把 DSH 添加到桌面/手机主屏,支持自定义应用名、主题色和图标 |
|
|
18
|
+
| 桌面通知 | 任务完成、等待回答、等待审批、后台任务结束等事件弹出系统通知 |
|
|
19
|
+
| 手机推送 | 通过 PWA Web Push 把通知推到手机,点击直接回到对应会话 |
|
|
20
|
+
| 会话请求头 | 让第三方 API 路由也携带 DSH 的 session id 请求头,便于上游网关按会话关联请求 |
|
|
12
21
|
|
|
13
|
-
|
|
22
|
+
## 独立密码网关
|
|
14
23
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
24
|
+
- 默认关闭,监听端口默认 `3088`,可在设置中改到其他空闲端口。
|
|
25
|
+
- 必须先设置访问密码,网关的启用开关才可用;密码只保存哈希,不保存明文。
|
|
26
|
+
- 登录后由网关自己的会话维持访问,不需要 DSH 的 `?token=`;网关会替浏览器完成 DSH 认证。
|
|
27
|
+
- 可以选择监听的网卡地址,默认只在你指定的地址上提供服务。
|
|
28
|
+
- 支持 DSH 的正常聊天、文件、终端、WebSocket 和实时流,不只是静态页面。
|
|
29
|
+
- 可以设置登录会话的有效期;修改密码后,所有已登录的网关会话都会失效。
|
|
21
30
|
|
|
22
|
-
|
|
31
|
+
## HTTPS
|
|
23
32
|
|
|
24
|
-
|
|
33
|
+
PWA 安装、Service Worker 和手机推送都要求 HTTPS(本机 `localhost` 除外)。设置里提供三种方式:
|
|
25
34
|
|
|
26
|
-
|
|
|
35
|
+
| 方式 | 适用场景 |
|
|
27
36
|
| --- | --- |
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
| `selfsigned` | 插件生成本地 CA + leaf,并提供 CA 证书下载 |
|
|
37
|
+
| 关闭(HTTP) | 只在本机或完全可信的网络里临时使用;PWA 和推送不可用 |
|
|
38
|
+
| 使用已有证书 | 已经有域名证书,上传证书链和私钥即可 |
|
|
39
|
+
| 使用 CA 证书自签发 | 上传自己的 CA,或者让插件生成一个本地 CA,再为当前地址签发证书 |
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
- 自签/私有 CA 需要设备安装并信任 CA:iOS 在“设置 → 通用 → VPN 与设备管理”安装描述文件,再到“关于本机 → 证书信任设置”开启完全信任;Android 在“安全 → 加密与凭据 → 安装证书”安装。
|
|
35
|
-
- 上传证书的 SAN 必须覆盖访问地址/IP,否则浏览器会报证书错误。
|
|
41
|
+
使用自签/私有 CA 时,需要把 CA 证书安装到访问设备并设为信任:
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
- iOS:设置 → 通用 → VPN 与设备管理 安装描述文件,再到 关于本机 → 证书信任设置 开启完全信任。
|
|
44
|
+
- Android:设置 → 安全 → 加密与凭据 → 安装证书。
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
- 默认图标集:192/512 `any`、512 `maskable`、1600 WebP、iOS 180。
|
|
41
|
-
- 桌面/安卓/iOS 没有 OS 专用的 manifest 图标字段;效果通过 `any`(桌面)+ `maskable`(Android 自适应)+ `apple-touch-icon`(iOS,优先级最高)组合实现。
|
|
42
|
-
- Chromium/Edge 支持 `beforeinstallprompt`,页面会显示 DSH 风格的安装浮层;iOS 只能显示“分享 → 添加到主屏幕”的手动指引。
|
|
43
|
-
- PWA 安装、Service Worker、Web Push 都要求 HTTPS(localhost 例外)。
|
|
46
|
+
上传的证书必须包含你实际访问用的域名或 IP(SAN),否则浏览器仍会提示证书错误。
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
## PWA
|
|
46
49
|
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
- PWA 推送必须 HTTPS;iOS/iPadOS 16.4+ 且必须“添加到主屏幕”,权限必须由用户点按钮触发。设置页提供“订阅本设备 / 取消订阅 / 发送测试推送”,并显示当前通知权限和订阅数。
|
|
50
|
+
- 接管 DSH 的 manifest、图标、`apple-touch-icon`、Service Worker 和安装提示。
|
|
51
|
+
- 可以自定义应用名称、短名称、主题色,并分别替换桌面/Android/iOS 图标。
|
|
52
|
+
- Chromium / Edge 会显示安装提示;iPhone / iPad 使用 Safari 的“分享 → 添加到主屏幕”。
|
|
53
|
+
- 安装后从主屏启动,不显示浏览器地址栏,使用体验接近原生应用。
|
|
54
|
+
- 只有 HTTPS 下才能安装、注册 Service Worker 和接收推送。
|
|
53
55
|
|
|
56
|
+
## 会话请求头
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
DSH 只在直连官方 DeepSeek API 时发送 `x-deepseek-harness-session-id`;走 OpenAI 兼容网关(`llm-pi-ai`)的第三方路由不会携带任何会话标识。启用本功能后,插件会把当前会话的 session id 注入成请求头,让上游网关也能按会话关联请求。
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
- 默认**关闭**,默认头名与 DeepSeek 官方一致:`x-deepseek-harness-session-id`。
|
|
61
|
+
- 头名可改成上游网关约定的其它名称;值始终是 DSH 的原始 session id。
|
|
62
|
+
- 只作用于第三方 API adapter(`llm-pi-ai`);官方 DeepSeek 路由本来就带该头,不受影响。
|
|
63
|
+
- 保存后下一次模型请求即生效,不需要重启 DSH。
|
|
64
|
+
- 上游若已存在同名头,插件的注入不会覆盖它。
|
|
58
65
|
|
|
59
|
-
|
|
60
|
-
dsh plugin --profile web add dsh-enhancement
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
插件行由包内 `cordis.patch.yml` 自动插入;`dsh-web-mobile`、`dsh-vertical-layout` 也由同一个 patch 一并插入,**不要**在 profile 里再重复启用这两个 bundle。
|
|
66
|
+
> 实现说明:注入点在第三方 adapter 的 `streamWithSnapshot` 原型方法上(DSH 在该方法内重建 pi-ai 的 options,`llm/stream` 层改不动请求头)。因此本功能依赖宿主内部结构,宿主版本结构变化时会自动旁路并记录原因,不会阻断请求。
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
cd ~/dev/projects/github/fengyifan/dsh-plugins
|
|
69
|
-
pnpm --filter dsh-enhancement build
|
|
70
|
-
cd ~/.dsh/profiles/web
|
|
71
|
-
pnpm add link:../../dev/projects/github/fengyifan/dsh-plugins/packages/dsh-enhancement
|
|
72
|
-
```
|
|
68
|
+
### 为什么显示「尚未挂载」
|
|
73
69
|
|
|
74
|
-
|
|
70
|
+
`dsh-base` 以**休眠态**挂载 `llm-pi-ai`:没有配置任何 provider 路由时它不会注册 adapter(`routes.length === 0` 直接返回),此时插件无处注入。这是正常状态,不是故障。设置页按原因区分:
|
|
75
71
|
|
|
76
|
-
|
|
72
|
+
| 提示 | 含义 | 处理 |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| 已挂载到 N 个第三方 API adapter | 正常工作 | — |
|
|
75
|
+
| 当前没有已注册的第三方模型路由 | 尚未配置 OpenAI 兼容 provider | 到「设置 → 模型」添加,或在本 profile 的 `cordis.patch.yml` 写 `llm-pi-ai` 的 `providers` |
|
|
76
|
+
| 已注册 N 个 adapter,但没有一个是第三方 API adapter | 只有官方 DeepSeek 路由 | 同上:需要添加第三方 provider |
|
|
77
|
+
| 宿主 llm 服务不可用或结构已变化 | 版本漂移 | 插件已旁路,不影响正常请求 |
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
## 通知
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
$DSH_HOME/profiles/<profile>/fengyifan-dsh-enhancement/state.json # 仅当前用户可读:密码哈希、网关签名密钥、VAPID、订阅
|
|
82
|
-
$DSH_HOME/profiles/<profile>/fengyifan-dsh-enhancement/tls/ # 仅当前用户可读:证书与私钥
|
|
83
|
-
```
|
|
81
|
+
支持这些事件,默认全部开启,可以按需勾选:
|
|
84
82
|
|
|
85
|
-
|
|
83
|
+
- 任务完成
|
|
84
|
+
- 等待你回答
|
|
85
|
+
- 等待审批
|
|
86
|
+
- 审批被拒绝
|
|
87
|
+
- 子任务结束
|
|
88
|
+
- 目标完成或受阻
|
|
89
|
+
- 后台任务结束(不会为每条命令都弹)
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
桌面通知和手机推送共用同一组事件选择,但可以分别关闭其中一个渠道。
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
- 浏览器原有的 `dsh-auth-*` cookie 在网关代理时会被过滤,只使用插件即时签发的短时 cookie。
|
|
93
|
+
点击桌面通知会打开对应会话。Linux 和 Windows 下,如果 DSH 已经打开,会优先切回已有页面并跳转到会话;macOS 使用系统通知的打开链接。Linux 需要系统通知守护支持 action(常见桌面环境都支持);如果只显示通知、点击没反应,检查系统是否有 `notify-send` 和可用的通知服务。
|
|
91
94
|
|
|
92
|
-
##
|
|
95
|
+
## 手机推送
|
|
93
96
|
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
- iOS
|
|
97
|
-
-
|
|
98
|
-
- PWA 图标上传(桌面/Android/iOS 分别替换)在资产提供后加入;当前使用包内默认图标。
|
|
97
|
+
- 使用标准 Web Push,每台设备单独订阅。
|
|
98
|
+
- 在设置里点“订阅本设备”,浏览器会请求通知权限;权限必须由你主动点击触发。
|
|
99
|
+
- iOS / iPadOS 需要 16.4 或更高版本,并且必须先把 DSH 添加到主屏幕;Safari 普通标签页收不到推送。
|
|
100
|
+
- 可以随时取消订阅,或发送一条测试推送确认链路正常。
|
|
99
101
|
|
|
100
|
-
##
|
|
102
|
+
## 安装
|
|
101
103
|
|
|
102
104
|
```bash
|
|
103
|
-
|
|
104
|
-
pnpm --filter dsh-enhancement build
|
|
105
|
+
dsh plugin --profile web add dsh-enhancement
|
|
105
106
|
```
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
安装后重启 DSH Web。插件会自动带入手机布局和竖屏布局依赖,不需要再单独安装或启用它们。
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
## 开始使用
|
|
111
|
+
|
|
112
|
+
1. 打开 DSH → 设置 → 插件 → `dsh-enhancement`。
|
|
113
|
+
2. 在“访问控制”里设置密码并保存。设置密码后,“网关配置”里的启用开关才会变为可用。
|
|
114
|
+
3. 在“网关配置”里选择监听地址和端口(默认 `3088`),打开“启用网关”,保存并应用。
|
|
115
|
+
4. 如果需要 HTTPS,在同一块里选择证书方式并保存;使用自签 CA 时,把 CA 证书安装到手机/电脑并设为信任。
|
|
116
|
+
5. 在手机或另一台电脑上打开网关地址,用刚设置的密码登录,无需 DSH 的 `?token=`。
|
|
117
|
+
6. 需要主屏应用时,在浏览器里安装 PWA;iOS 使用“分享 → 添加到主屏幕”。
|
|
118
|
+
7. 进入“通知”块,确认事件勾选,点“测试桌面通知”;需要手机推送时,打开推送并点“订阅本设备”。
|
|
119
|
+
|
|
120
|
+
## 使用建议
|
|
118
121
|
|
|
119
|
-
|
|
122
|
+
- 把网关暴露到公网前,务必使用 HTTPS 和强密码。
|
|
123
|
+
- 网关端口不要和 DSH 自身端口(默认 `3080`)重复;冲突时换一个空闲端口。
|
|
124
|
+
- 自签证书只适合自己控制的设备;给多人使用建议上传正式证书。
|
|
125
|
+
- 如果只需要在本机用,保持网关关闭即可,DSH 原本的访问方式不受影响。
|
|
120
126
|
|
|
121
|
-
|
|
127
|
+
## 限制
|
|
122
128
|
|
|
123
|
-
-
|
|
124
|
-
-
|
|
125
|
-
-
|
|
129
|
+
- 网关、PWA 和推送默认关闭,需要手动配置;未设置密码不能启用网关。
|
|
130
|
+
- PWA 安装、Service Worker 和 Web Push 都需要 HTTPS。
|
|
131
|
+
- iOS 推送要求 16.4+ 且已添加到主屏幕。
|
|
132
|
+
- 自签/私有 CA 需要在每台访问设备上安装并信任。
|
|
133
|
+
- Linux 桌面通知的点击依赖系统通知服务的 action 支持。
|
|
134
|
+
- 修改密码会使所有网关登录会话失效,需要重新登录。
|
|
126
135
|
|
|
127
136
|
## License
|
|
128
137
|
|
package/lib/client.js
CHANGED
|
@@ -223,6 +223,8 @@ window.__ModuleLoader__.load({
|
|
|
223
223
|
const [pushCount, setPushCount] = (0, react_1.useState)(0);
|
|
224
224
|
const [deviceSubscribed, setDeviceSubscribed] = (0, react_1.useState)(false);
|
|
225
225
|
const [customIcons, setCustomIcons] = (0, react_1.useState)([]);
|
|
226
|
+
const [sessionHeaderEnabled, setSessionHeaderEnabled] = (0, react_1.useState)(false);
|
|
227
|
+
const [sessionHeaderName, setSessionHeaderName] = (0, react_1.useState)('x-deepseek-harness-session-id');
|
|
226
228
|
const applyStatus = (0, react_1.useCallback)((next) => {
|
|
227
229
|
setStatus(next);
|
|
228
230
|
setEnabled(next.enabled);
|
|
@@ -252,6 +254,8 @@ window.__ModuleLoader__.load({
|
|
|
252
254
|
setPushPublicKey(next.push?.publicKey ?? null);
|
|
253
255
|
setPushCount(next.push?.subscriptions ?? 0);
|
|
254
256
|
setCustomIcons(next.pwa?.customIcons ?? []);
|
|
257
|
+
setSessionHeaderEnabled(next.sessionHeader?.enabled ?? false);
|
|
258
|
+
setSessionHeaderName(next.sessionHeader?.headerName ?? 'x-deepseek-harness-session-id');
|
|
255
259
|
setHttpsChoice(next.https?.mode === 'uploaded' ? 'uploaded' : next.https?.mode === 'off' ? 'off' : 'selfsigned');
|
|
256
260
|
setCaSource(next.https?.mode === 'ca' ? 'uploaded' : 'generated');
|
|
257
261
|
}, []);
|
|
@@ -323,6 +327,13 @@ window.__ModuleLoader__.load({
|
|
|
323
327
|
const next = await api('/config', { method: 'POST', body: JSON.stringify(fields) });
|
|
324
328
|
applyStatus(next);
|
|
325
329
|
}, success);
|
|
330
|
+
const saveSessionHeader = () => runBlock('sessionHeader', async () => {
|
|
331
|
+
const next = await api('/config', {
|
|
332
|
+
method: 'POST',
|
|
333
|
+
body: JSON.stringify({ sessionHeaderEnabled, sessionHeaderName }),
|
|
334
|
+
});
|
|
335
|
+
applyStatus(next);
|
|
336
|
+
}, '会话请求头配置已保存,下一次模型请求即生效。');
|
|
326
337
|
const saveGateway = () => runBlock('gateway', async () => {
|
|
327
338
|
let next = await api('/config', {
|
|
328
339
|
method: 'POST',
|
|
@@ -586,8 +597,29 @@ window.__ModuleLoader__.load({
|
|
|
586
597
|
notifyJobs !== status.notify.jobs ||
|
|
587
598
|
pushEnabled !== status.push.enabled ||
|
|
588
599
|
pushSubject !== status.push.subject;
|
|
600
|
+
const sessionHeaderDirty = sessionHeaderEnabled !== status.sessionHeader.enabled ||
|
|
601
|
+
sessionHeaderName !== status.sessionHeader.headerName;
|
|
589
602
|
const accessDirty = newPassword.length > 0 || confirmPassword.length > 0;
|
|
590
603
|
const iconUrl = (kind) => `/pwa/enhancement/${kind}?v=${iconVersion}`;
|
|
604
|
+
/**
|
|
605
|
+
* 会话请求头的挂载说明。
|
|
606
|
+
* 「没配第三方模型」是正常状态而非故障,必须与真正的结构异常区分开。
|
|
607
|
+
*/
|
|
608
|
+
const sessionHeaderSummary = (errorStyle = false) => {
|
|
609
|
+
const header = status.sessionHeader;
|
|
610
|
+
if (header.patched > 0) {
|
|
611
|
+
return `已挂载到 ${header.patched} 个第三方 API adapter;改动即时生效,无需重启。`;
|
|
612
|
+
}
|
|
613
|
+
if (header.reason === 'no-adapters') {
|
|
614
|
+
return '当前没有已注册的第三方模型路由,因此暂时无处注入。在「设置 → 模型」添加一个 OpenAI 兼容 provider(或在本 profile 的 cordis.patch.yml 配置 llm-pi-ai 路由)后会自动挂载;官方 DeepSeek 路由本来就自带该头,不需要它。';
|
|
615
|
+
}
|
|
616
|
+
if (header.reason === 'no-pi-ai-adapter') {
|
|
617
|
+
return `已注册 ${header.adapterCount} 个 adapter,但没有一个是第三方 API adapter(llm-pi-ai),因此未挂载。`;
|
|
618
|
+
}
|
|
619
|
+
if (errorStyle)
|
|
620
|
+
return '宿主 llm 服务不可用或结构已变化,注入已旁路,不会影响正常请求。';
|
|
621
|
+
return '宿主 llm 服务不可用或结构已变化,注入暂时旁路。';
|
|
622
|
+
};
|
|
591
623
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "enh-root", style: styles.wrap, children: [(0, jsx_runtime_1.jsx)("style", { children: CARD_CSS }), (0, jsx_runtime_1.jsxs)("div", { style: styles.header, children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { style: styles.title, children: "\u63D2\u4EF6\u72B6\u6001" }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "DSH \u589E\u5F3A\u7684\u7F51\u5173\u3001PWA\u3001\u901A\u77E5\u4E0E\u63A8\u9001\u914D\u7F6E\u3002" })] }), (0, jsx_runtime_1.jsxs)("span", { style: styles.pill, children: [(0, jsx_runtime_1.jsx)("span", { style: styles.dot }), "\u8FD0\u884C\u4E2D"] })] }), error ? (0, jsx_runtime_1.jsx)("div", { style: styles.error, children: error }) : null, notice ? (0, jsx_runtime_1.jsx)("div", { style: styles.success, children: notice }) : null, (0, jsx_runtime_1.jsxs)(Block, { order: 2, title: "\u7F51\u5173\u914D\u7F6E", hint: "\u5BC6\u7801\u7F51\u5173\u7684\u76D1\u542C\u3001\u8BBF\u95EE\u4E0E\u4F1A\u8BDD\u8BBE\u7F6E\uFF1BHTTPS \u914D\u7F6E\u4E5F\u5728\u672C\u5757\u5185\uFF0C\u4FDD\u5B58\u65F6\u6309\u987A\u5E8F\u5E94\u7528\u3002", dirty: gatewayDirty, busy: busy, saveLabel: "\u4FDD\u5B58\u5E76\u5E94\u7528", onSave: saveGateway, status: blockStatus.gateway, extra: (0, jsx_runtime_1.jsx)("button", { style: styles.button, disabled: busy || !status.passwordSet, onClick: restart, children: "\u91CD\u542F\u7F51\u5173" }), children: [(0, jsx_runtime_1.jsxs)(InlineRow, { label: "DSH \u4E0A\u6E38", children: [(0, jsx_runtime_1.jsx)("code", { style: { ...styles.code, flex: '1 1 260px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: status.dshAccessUrl || status.dshOrigin }), (0, jsx_runtime_1.jsx)("button", { style: styles.iconButton, title: "\u590D\u5236", "aria-label": "\u590D\u5236 DSH \u4E0A\u6E38\u5730\u5740\uFF08\u542B token\uFF09", onClick: () => copyToClipboard(status.dshAccessUrl || status.dshOrigin), children: (0, jsx_runtime_1.jsx)(CopyIcon, {}) }), (0, jsx_runtime_1.jsx)("a", { style: styles.iconButton, title: "\u6253\u5F00", "aria-label": "\u6253\u5F00 DSH \u4E0A\u6E38\uFF08\u542B token\uFF09", href: status.dshAccessUrl || status.dshOrigin, target: "_blank", rel: "noreferrer", children: (0, jsx_runtime_1.jsx)(OpenIcon, {}) })] }), (0, jsx_runtime_1.jsx)(InlineRow, { label: "\u7F51\u5173\u5730\u5740", children: status.url ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("code", { style: { ...styles.code, flex: '1 1 260px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: status.url }), (0, jsx_runtime_1.jsx)("button", { style: styles.iconButton, title: "\u590D\u5236", "aria-label": "\u590D\u5236\u7F51\u5173\u5730\u5740", onClick: () => copyToClipboard(status.url), children: (0, jsx_runtime_1.jsx)(CopyIcon, {}) }), (0, jsx_runtime_1.jsx)("a", { style: styles.iconButton, title: "\u6253\u5F00", "aria-label": "\u6253\u5F00\u7F51\u5173", href: status.url, target: "_blank", rel: "noreferrer", children: (0, jsx_runtime_1.jsx)(OpenIcon, {}) })] })) : (0, jsx_runtime_1.jsx)("span", { style: styles.muted, children: "\u672A\u8FD0\u884C\uFF1B\u8BBE\u7F6E\u5BC6\u7801\u5E76\u542F\u7528\u540E\u751F\u6210" }) }), !status.dshSecretReady ? (0, jsx_runtime_1.jsx)("div", { style: styles.error, children: "\u7F51\u5173\u4F1A\u8BDD\u6CE8\u5165\u4E0D\u53EF\u7528\uFF1A\u65E0\u6CD5\u8BFB\u53D6 DSH \u6D4F\u89C8\u5668\u7B7E\u540D\u5BC6\u94A5\uFF0C\u7F51\u5173\u4EE3\u7406\u4F1A\u8FD4\u56DE 401\u3002" }) : null, status.error ? (0, jsx_runtime_1.jsxs)("div", { style: styles.error, children: ["\u6700\u8FD1\u9519\u8BEF\uFF1A", status.error] }) : null, (0, jsx_runtime_1.jsx)(Row, { label: "\u542F\u7528\u7F51\u5173", children: (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: enabled, disabled: !status.passwordSet, onChange: (event) => setEnabled(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: !status.passwordSet ? '请先设置密码' : enabled ? '启用' : '停用' })] }) }), enabled ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "enh-grid3", children: [(0, jsx_runtime_1.jsxs)(Row, { label: "\u76D1\u542C\u5730\u5740", children: [(0, jsx_runtime_1.jsx)("select", { style: styles.select, value: bindHost, onChange: (event) => setBindHost(event.target.value), children: interfaceOptions.map((option) => ((0, jsx_runtime_1.jsx)("option", { value: option.value, children: option.label }, option.value))) }), currentInterface ? (0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: ["\u5F53\u524D\uFF1A", currentInterface.address, "\uFF08", currentInterface.name, "\uFF09"] }) : null] }), (0, jsx_runtime_1.jsx)(Row, { label: "\u7AEF\u53E3", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: port, inputMode: "numeric", onChange: (event) => setPort(event.target.value) }) }), (0, jsx_runtime_1.jsxs)(Row, { label: "\u4F1A\u8BDD\u6709\u6548\u671F", children: [(0, jsx_runtime_1.jsx)("input", { style: styles.input, value: sessionDays, inputMode: "numeric", onChange: (event) => setSessionDays(event.target.value) }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5929\uFF1B\u5BC6\u7801\u4FEE\u6539\u540E\u5168\u90E8\u4F1A\u8BDD\u5931\u6548\u3002" })] })] }), (0, jsx_runtime_1.jsx)(Row, { label: "\u767B\u5F55\u5931\u8D25\u8282\u6D41", children: (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: loginThrottle, onChange: (event) => setLoginThrottle(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: loginThrottle ? '开启(5 次 / 10 分钟 → 锁 1 分钟)' : '关闭(不推荐)' })] }) }), (0, jsx_runtime_1.jsx)(Row, { label: "HTTPS \u6A21\u5F0F", children: (0, jsx_runtime_1.jsxs)("select", { style: styles.select, value: httpsChoice, onChange: (event) => setHttpsChoice(event.target.value), children: [(0, jsx_runtime_1.jsx)("option", { value: "off", children: "\u5173\u95ED HTTPS" }), (0, jsx_runtime_1.jsx)("option", { value: "selfsigned", children: "\u4F7F\u7528 CA \u8BC1\u4E66\u81EA\u7B7E\u53D1" }), (0, jsx_runtime_1.jsx)("option", { value: "uploaded", children: "\u4F7F\u7528\u5DF2\u6709\u8BC1\u4E66" })] }) }), httpsChoice === 'off' ? ((0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5173\u95ED\u540E\u6D4F\u89C8\u5668\u4F1A\u63D0\u793A\u4E0D\u5B89\u5168\uFF0CPWA \u5B89\u88C5\u3001Service Worker \u4E0E Web Push \u90FD\u4E0D\u53EF\u7528\u3002\u4FDD\u5B58\u540E\u751F\u6548\u3002" })) : null, httpsChoice === 'selfsigned' ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(Row, { label: "CA \u6765\u6E90", children: (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 16 }, children: [(0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 6 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: "ca-source", checked: caSource === 'generated', onChange: () => setCaSource('generated') }), (0, jsx_runtime_1.jsx)("span", { children: "\u81EA\u52A8\u751F\u6210 CA" })] }), (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 6 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: "ca-source", checked: caSource === 'uploaded', onChange: () => setCaSource('uploaded') }), (0, jsx_runtime_1.jsx)("span", { children: "\u4F7F\u7528\u5DF2\u6709 CA" })] })] }) }), caSource === 'uploaded' ? ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsxs)(Row, { label: "\u5F53\u524D CA", children: [status.https.uploadedCaInfo ? ((0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: [status.https.uploadedCaInfo.subject, "\uFF0C\u6709\u6548\u671F\u81F3 ", status.https.uploadedCaInfo.validTo, "\uFF1B\u6587\u4EF6\u5DF2\u4FDD\u5B58\uFF0C\u53EF\u76F4\u63A5\u4F7F\u7528\uFF0C\u65E0\u9700\u91CD\u65B0\u4E0A\u4F20\u3002"] })) : status.https.caInfo ? ((0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: [status.https.caInfo.subject, "\uFF0C\u6709\u6548\u671F\u81F3 ", status.https.caInfo.validTo, "\uFF1B\u6587\u4EF6\u4FDD\u5B58\u5728\u672C\u673A TLS \u76EE\u5F55\uFF0C\u53EF\u7EE7\u7EED\u7B7E\u53D1 leaf\u3002"] })) : (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5C1A\u672A\u4E0A\u4F20 CA\u3002" }), (0, jsx_runtime_1.jsx)("input", { id: "enh-ca-cert-file", type: "file", accept: ".pem,.crt,.cer", style: { display: 'none' }, onChange: (event) => setCaCertFile(event.target.files?.[0] ?? null) }), (0, jsx_runtime_1.jsx)("input", { id: "enh-ca-key-file", type: "file", accept: ".pem,.key", style: { display: 'none' }, onChange: (event) => setCaKeyFile(event.target.files?.[0] ?? null) }), (0, jsx_runtime_1.jsxs)("div", { style: styles.actions, children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "enh-ca-cert-file", style: { ...styles.button, display: 'inline-flex', alignItems: 'center' }, children: caCertFile ? `已选择:${caCertFile.name}` : status.https.uploadedCaInfo ? '更换 CA 证书' : '选择 CA 证书' }), (0, jsx_runtime_1.jsx)("label", { htmlFor: "enh-ca-key-file", style: { ...styles.button, display: 'inline-flex', alignItems: 'center' }, children: caKeyFile ? `已选择:${caKeyFile.name}` : status.https.uploadedCaInfo ? '更换 CA 私钥' : '选择 CA 私钥' })] }), caCertFile || caKeyFile ? (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u4FDD\u5B58\u65F6\u624D\u4F1A\u5E94\u7528\uFF1B\u53EA\u4FDD\u7559\u6700\u65B0\u4E00\u4EFD\u6587\u4EF6\u3002" }) : null] }) })) : ((0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u4FDD\u5B58\u65F6\u4F1A\u5728\u63D2\u4EF6 TLS \u76EE\u5F55\u751F\u6210\u672C\u5730 CA\uFF0C\u5E76\u7528\u5B83\u7B7E\u53D1\u5F53\u524D\u76D1\u542C\u5730\u5740 + \u989D\u5916\u4E3B\u673A\u540D\u7684\u8BC1\u4E66\u3002" })), (0, jsx_runtime_1.jsxs)(Row, { label: "\u989D\u5916\u4E3B\u673A\u540D", children: [(0, jsx_runtime_1.jsx)("input", { style: styles.input, placeholder: "\u4F8B\u5982 dsh.lan, myhost.local\uFF08\u9017\u53F7\u5206\u9694\uFF09", value: httpsHostnames, onChange: (event) => setHttpsHostnames(event.target.value) }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u76D1\u542C IP\u3001localhost \u4F1A\u81EA\u52A8\u5305\u542B\uFF1B\u8FD9\u91CC\u8865\u5145 DNS \u4E3B\u673A\u540D\u3002" })] }), status.https.caAvailable ? ((0, jsx_runtime_1.jsx)("div", { style: styles.actions, children: (0, jsx_runtime_1.jsx)("a", { style: { ...styles.button, textDecoration: 'none', display: 'inline-flex', alignItems: 'center' }, href: `${API}/tls/ca`, download: "dsh-enhancement-ca.crt", children: "\u4E0B\u8F7D CA \u8BC1\u4E66" }) })) : null, status.https.mode !== 'off' && status.https.cert ? ((0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: ["\u5F53\u524D\u8BC1\u4E66\uFF1A", status.https.cert.subject, "\uFF0C\u6709\u6548\u671F\u81F3 ", status.https.cert.validTo] })) : null, (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "iOS \u5B89\u88C5 CA\uFF1ASafari \u6253\u5F00\u4E0B\u8F7D\u6587\u4EF6 \u2192 \u8BBE\u7F6E \u2192 \u901A\u7528 \u2192 VPN \u4E0E\u8BBE\u5907\u7BA1\u7406 \u2192 \u5B89\u88C5\u63CF\u8FF0\u6587\u4EF6 \u2192 \u8BBE\u7F6E \u2192 \u901A\u7528 \u2192 \u5173\u4E8E\u672C\u673A \u2192 \u8BC1\u4E66\u4FE1\u4EFB\u8BBE\u7F6E \u2192 \u5BF9 \u201CDSH Enhancement Local CA\u201D \u5F00\u542F\u5B8C\u5168\u4FE1\u4EFB\u3002" })] })) : null, httpsChoice === 'uploaded' ? ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsxs)(Row, { label: "\u5F53\u524D\u8BC1\u4E66", children: [status.https.cert && status.https.mode === 'uploaded' ? ((0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: [status.https.cert.subject, "\uFF0C\u6709\u6548\u671F\u81F3 ", status.https.cert.validTo, "\uFF1BSAN\uFF1A", status.https.cert.subjectAltName || '未提供', "\u3002\u8BC1\u4E66\u548C\u79C1\u94A5\u4FDD\u5B58\u5728\u672C\u673A TLS \u76EE\u5F55\u3002"] })) : (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5C1A\u672A\u4E0A\u4F20\u8BC1\u4E66\u3002" }), (0, jsx_runtime_1.jsx)("input", { id: "enh-cert-file", type: "file", accept: ".pem,.crt,.cer", style: { display: 'none' }, onChange: (event) => setCertFile(event.target.files?.[0] ?? null) }), (0, jsx_runtime_1.jsx)("input", { id: "enh-key-file", type: "file", accept: ".pem,.key", style: { display: 'none' }, onChange: (event) => setKeyFile(event.target.files?.[0] ?? null) }), (0, jsx_runtime_1.jsxs)("div", { style: styles.actions, children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "enh-cert-file", style: { ...styles.button, display: 'inline-flex', alignItems: 'center' }, children: certFile ? `已选择:${certFile.name}` : status.https.mode === 'uploaded' ? '更换证书' : '选择证书' }), (0, jsx_runtime_1.jsx)("label", { htmlFor: "enh-key-file", style: { ...styles.button, display: 'inline-flex', alignItems: 'center' }, children: keyFile ? `已选择:${keyFile.name}` : status.https.mode === 'uploaded' ? '更换私钥' : '选择私钥' })] }), certFile || keyFile ? (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u4FDD\u5B58\u65F6\u624D\u4F1A\u5E94\u7528\uFF1B\u8BC1\u4E66 SAN \u5FC5\u987B\u8986\u76D6\u8BBF\u95EE\u5730\u5740/IP\uFF0C\u53EA\u4FDD\u7559\u6700\u65B0\u4E00\u4EFD\u6587\u4EF6\u3002" }) : null] }) })) : null] })) : (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u542F\u7528\u7F51\u5173\u540E\u663E\u793A\u76D1\u542C\u5730\u5740\u3001\u7AEF\u53E3\u3001\u4F1A\u8BDD\u548C HTTPS \u8BBE\u7F6E\u3002" })] }), (0, jsx_runtime_1.jsxs)(Block, { order: 1, title: "\u8BBF\u95EE\u63A7\u5236", hint: status.passwordSet ? '已设置访问密码;修改后全部网关会话失效。' : '尚未设置密码;设置后才能启用网关。', dirty: accessDirty, busy: busy, saveLabel: "\u8BBE\u7F6E\u5BC6\u7801", onSave: savePassword, status: blockStatus.access, extra: (0, jsx_runtime_1.jsx)("button", { style: { ...styles.button, ...styles.danger }, disabled: busy || !status.passwordSet, onClick: logoutAll, children: "\u9000\u51FA\u5168\u90E8\u7F51\u5173\u4F1A\u8BDD" }), children: [(0, jsx_runtime_1.jsxs)("div", { className: "enh-grid2", children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u65B0\u5BC6\u7801", children: (0, jsx_runtime_1.jsx)(PasswordInput, { value: newPassword, onChange: setNewPassword, autoComplete: "new-password", show: showNewPassword, onToggle: () => setShowNewPassword((value) => !value) }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u786E\u8BA4\u65B0\u5BC6\u7801", children: (0, jsx_runtime_1.jsx)(PasswordInput, { value: confirmPassword, onChange: setConfirmPassword, autoComplete: "new-password", show: showConfirmPassword, onToggle: () => setShowConfirmPassword((value) => !value) }) })] }), newPassword.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { style: { ...styles.error, marginTop: -4 }, children: newPassword.length < 6 ? '新密码至少 6 个字符' : confirmPassword.length > 0 && newPassword !== confirmPassword ? '两次输入的新密码不一致' : '' })) : null, (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u81F3\u5C11 6 \u4E2A\u5B57\u7B26\uFF1B\u5BC6\u7801\u7B49\u4EF7\u4E8E\u672C\u673A DSH \u7684\u8BBF\u95EE\u6743\u9650\uFF0C\u8BF7\u4F7F\u7528\u5F3A\u5BC6\u7801\u3002" })] }), (0, jsx_runtime_1.jsxs)(Block, { order: 3, title: "PWA\uFF08\u6DFB\u52A0\u5230\u684C\u9762 / \u4E3B\u5C4F\uFF09", hint: "\u542F\u7528\u540E\u63A5\u7BA1 /manifest.webmanifest\u3001\u56FE\u6807\u3001/sw.js \u4E0E\u5B89\u88C5\u63D0\u793A\uFF1BHTTP \u4E0B\u5B89\u88C5\u5165\u53E3\u548C Service Worker \u4E0D\u751F\u6548\u3002", dirty: pwaDirty, busy: busy, saveLabel: "\u4FDD\u5B58 PWA \u914D\u7F6E", onSave: savePwa, status: blockStatus.pwa, extra: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("a", { style: { ...styles.button, textDecoration: 'none', display: 'inline-flex', alignItems: 'center' }, href: "/manifest.webmanifest", target: "_blank", rel: "noreferrer", children: "\u67E5\u770B manifest" }), (0, jsx_runtime_1.jsx)("a", { style: { ...styles.button, textDecoration: 'none', display: 'inline-flex', alignItems: 'center' }, href: "/sw.js", target: "_blank", rel: "noreferrer", children: "\u67E5\u770B Service Worker" })] }), children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u542F\u7528 PWA", children: (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: pwaEnabled, onChange: (event) => setPwaEnabled(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: pwaEnabled ? '启用' : '停用' })] }) }), status.https.mode === 'off' ? (0, jsx_runtime_1.jsx)("div", { style: styles.error, children: "\u5F53\u524D\u662F HTTP\uFF1A\u5B89\u88C5\u63D0\u793A\u3001Service Worker\u3001Web Push \u90FD\u4E0D\u4F1A\u751F\u6548\u3002\u8BF7\u5148\u5728\u7F51\u5173\u914D\u7F6E\u542F\u7528 HTTPS\u3002" }) : null, status.pwa.error ? (0, jsx_runtime_1.jsxs)("div", { style: styles.error, children: ["PWA \u6CE8\u518C\u5931\u8D25\uFF1A", status.pwa.error] }) : null, pwaEnabled ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "enh-grid2", children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u5E94\u7528\u540D", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pwaName, onChange: (event) => setPwaName(event.target.value) }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u77ED\u540D\u79F0", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pwaShortName, onChange: (event) => setPwaShortName(event.target.value) }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "enh-grid3", children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u663E\u793A\u6A21\u5F0F", children: (0, jsx_runtime_1.jsxs)("select", { style: styles.select, value: pwaDisplay, onChange: (event) => setPwaDisplay(event.target.value), children: [(0, jsx_runtime_1.jsx)("option", { value: "standalone", children: "standalone" }), (0, jsx_runtime_1.jsx)("option", { value: "fullscreen", children: "fullscreen" }), (0, jsx_runtime_1.jsx)("option", { value: "minimal-ui", children: "minimal-ui" })] }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u4E3B\u9898\u8272", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pwaThemeColor, onChange: (event) => setPwaThemeColor(event.target.value) }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u80CC\u666F\u8272", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pwaBackgroundColor, onChange: (event) => setPwaBackgroundColor(event.target.value) }) })] }), (0, jsx_runtime_1.jsx)("div", { style: { ...styles.label, marginTop: 8 }, children: "\u5206\u5E73\u53F0\u56FE\u6807" }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u4E0A\u4F20\u540E\u7ACB\u5373\u751F\u6548\uFF1BAndroid \u81EA\u9002\u5E94\u56FE\u6807\u8BF7\u5355\u72EC\u4E0A\u4F20 maskable \u56FE\u3002\u5EFA\u8BAE 512 \u56FE\u6807\u5185\u5BB9\u7559\u5728\u4E2D\u5FC3 80% \u5B89\u5168\u533A\u3002" }), (0, jsx_runtime_1.jsx)("div", { className: "enh-icon-grid", children: [
|
|
592
624
|
['icon-192.png', '通用 192 × 192', 'any'],
|
|
593
625
|
['icon-512.png', '通用 512 × 512', 'any'],
|
|
@@ -598,7 +630,7 @@ window.__ModuleLoader__.load({
|
|
|
598
630
|
event.target.value = '';
|
|
599
631
|
if (file)
|
|
600
632
|
void uploadIcon(kind, file);
|
|
601
|
-
} }), customIcons.includes(kind) ? (0, jsx_runtime_1.jsx)("button", { style: styles.button, disabled: busy, onClick: () => resetIcon(kind), children: "\u6062\u590D\u9ED8\u8BA4" }) : null] })] })] }, kind))) }), (0, jsx_runtime_1.jsx)("div", { style: styles.actions, children: (0, jsx_runtime_1.jsx)("button", { style: styles.button, disabled: busy || customIcons.length === 0, onClick: () => resetIcon(), children: "\u5168\u90E8\u6062\u590D\u9ED8\u8BA4" }) })] })) : (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u542F\u7528 PWA \u540E\u624D\u663E\u793A\u5E94\u7528\u540D\u3001\u989C\u8272\u4E0E\u56FE\u6807\u8BBE\u7F6E\u3002" })] }), (0, jsx_runtime_1.jsxs)(Block, { order: 4, title: "\u901A\u77E5", hint: "\u4E8B\u4EF6\u52FE\u9009\u540C\u65F6\u63A7\u5236\u684C\u9762\u901A\u77E5\u548C PWA \u63A8\u9001\uFF1B\u4E24\u4E2A\u6E20\u9053\u53EF\u4EE5\u72EC\u7ACB\u5F00\u5173\uFF0C\u5173\u95ED\u67D0\u4E2A\u6E20\u9053\u4E0D\u4F1A\u4E22\u4E8B\u4EF6\u9009\u62E9\u3002", dirty: notifyDirty, busy: busy, saveLabel: "\u4FDD\u5B58\u901A\u77E5\u914D\u7F6E", onSave: saveNotify, status: blockStatus.notify, extra: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { style: styles.button, disabled: busy, onClick: testNotify, children: "\u6D4B\u8BD5\u684C\u9762\u901A\u77E5" }), pushUsable ? (0, jsx_runtime_1.jsx)("button", { style: styles.button, disabled: busy || pushCount === 0, onClick: testPush, children: "\u6D4B\u8BD5 PWA \u63A8\u9001" }) : null] }), children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u901A\u77E5\u4E8B\u4EF6", children: (0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', flexWrap: 'wrap', gap: '8px 18px' }, children: [['notifyTask', '任务完成', notifyTask, setNotifyTask], ['notifyAsk', '等待回答', notifyAsk, setNotifyAsk], ['notifyApproval', '等待审批', notifyApproval, setNotifyApproval], ['notifyDenied', '审批被拒绝', notifyDenied, setNotifyDenied], ['notifySubagent', '子任务结束', notifySubagent, setNotifySubagent], ['notifyGoal', '目标完成/受阻', notifyGoal, setNotifyGoal], ['notifyJobs', '后台任务结束', notifyJobs, setNotifyJobs]].map(([key, label, value, setter]) => ((0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap' }, children: [(0, jsx_runtime_1.jsx)("input", { className: "enh-event-check", type: "checkbox", checked: value, onChange: (event) => setter(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: label })] }, key))) }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u684C\u9762\u901A\u77E5", children: (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: notifyEnabled, onChange: (event) => setNotifyEnabled(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: notifyEnabled ? '启用' : '停用' })] }) }), (0, jsx_runtime_1.jsxs)(Row, { label: "PWA \u63A8\u9001", children: [(0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8, opacity: pushUsable ? 1 : 0.5 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: pushUsable && pushEnabled, disabled: !pushUsable, onChange: (event) => setPushEnabled(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: pushUsable ? (pushEnabled ? '启用' : '停用') : '需要 HTTPS' })] }), !pushUsable ? (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5F53\u524D\u9875\u9762\u4E0D\u662F HTTPS/\u5B89\u5168\u4E0A\u4E0B\u6587\uFF0CPWA \u63A8\u9001\u5F00\u5173\u7F6E\u7070\uFF1B\u8BF7\u5148\u542F\u7528 HTTPS\u3002" }) : null] }), pushUsable && pushEnabled ? ((0, jsx_runtime_1.jsxs)("div", { style: { paddingLeft: 12, borderLeft: '2px solid var(--dsw-alias-border-l4, rgba(0,0,0,.08))' }, children: [(0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: ["\u901A\u77E5\u6743\u9650\uFF1A", typeof Notification !== 'undefined' ? Notification.permission : '不可用', "\uFF1B\u5DF2\u8BA2\u9605\u8BBE\u5907\uFF1A", pushCount, "\u3002"] }), (0, jsx_runtime_1.jsx)("div", { style: styles.actions, children: (0, jsx_runtime_1.jsx)("button", { style: styles.button, disabled: busy, onClick: deviceSubscribed ? disablePush : enablePush, children: deviceSubscribed ? '取消本设备订阅' : '订阅本设备' }) }), (0, jsx_runtime_1.jsxs)("details", { className: "enh-advanced", children: [(0, jsx_runtime_1.jsx)("summary", { children: "\u9AD8\u7EA7\uFF1AVAPID \u8054\u7CFB\u5730\u5740" }), (0, jsx_runtime_1.jsxs)(Row, { label: "VAPID subject", children: [(0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pushSubject, onChange: (event) => setPushSubject(event.target.value), placeholder: "mailto:dsh-enhancement@localhost" }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "Web Push \u534F\u8BAE\u8981\u6C42\u7684\u8054\u7CFB\u65B9\u5F0F\uFF0C\u4E0D\u662F\u767B\u5F55\u90AE\u7BB1\uFF0C\u4E5F\u4E0D\u9700\u8981\u9A8C\u8BC1\uFF1B\u4E2A\u4EBA\u4F7F\u7528\u4FDD\u7559\u9ED8\u8BA4\u5373\u53EF\u3002" })] })] })] })) : pushUsable ? (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5F00\u542F PWA \u63A8\u9001\u540E\u663E\u793A\u8BA2\u9605\u3001\u6D4B\u8BD5\u548C VAPID \u8BBE\u7F6E\u3002" }) : null] }), (0, jsx_runtime_1.jsxs)("div", { style: { ...styles.card, order: 5 }, children: [(0, jsx_runtime_1.jsx)("div", { style: styles.label, children: "\u72B6\u6001\u6587\u4EF6" }), (0, jsx_runtime_1.jsx)("div", { style: styles.code, children: status.statePath }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5BC6\u7801\u53EA\u5B58 scrypt \u54C8\u5E0C\uFF0CDSH \u7B7E\u540D\u5BC6\u94A5\u4E0D\u4F1A\u5199\u5165\u8FD9\u91CC\u3002" })] })] }));
|
|
633
|
+
} }), customIcons.includes(kind) ? (0, jsx_runtime_1.jsx)("button", { style: styles.button, disabled: busy, onClick: () => resetIcon(kind), children: "\u6062\u590D\u9ED8\u8BA4" }) : null] })] })] }, kind))) }), (0, jsx_runtime_1.jsx)("div", { style: styles.actions, children: (0, jsx_runtime_1.jsx)("button", { style: styles.button, disabled: busy || customIcons.length === 0, onClick: () => resetIcon(), children: "\u5168\u90E8\u6062\u590D\u9ED8\u8BA4" }) })] })) : (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u542F\u7528 PWA \u540E\u624D\u663E\u793A\u5E94\u7528\u540D\u3001\u989C\u8272\u4E0E\u56FE\u6807\u8BBE\u7F6E\u3002" })] }), (0, jsx_runtime_1.jsxs)(Block, { order: 4, title: "\u901A\u77E5", hint: "\u4E8B\u4EF6\u52FE\u9009\u540C\u65F6\u63A7\u5236\u684C\u9762\u901A\u77E5\u548C PWA \u63A8\u9001\uFF1B\u4E24\u4E2A\u6E20\u9053\u53EF\u4EE5\u72EC\u7ACB\u5F00\u5173\uFF0C\u5173\u95ED\u67D0\u4E2A\u6E20\u9053\u4E0D\u4F1A\u4E22\u4E8B\u4EF6\u9009\u62E9\u3002", dirty: notifyDirty, busy: busy, saveLabel: "\u4FDD\u5B58\u901A\u77E5\u914D\u7F6E", onSave: saveNotify, status: blockStatus.notify, extra: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { style: styles.button, disabled: busy, onClick: testNotify, children: "\u6D4B\u8BD5\u684C\u9762\u901A\u77E5" }), pushUsable ? (0, jsx_runtime_1.jsx)("button", { style: styles.button, disabled: busy || pushCount === 0, onClick: testPush, children: "\u6D4B\u8BD5 PWA \u63A8\u9001" }) : null] }), children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u901A\u77E5\u4E8B\u4EF6", children: (0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', flexWrap: 'wrap', gap: '8px 18px' }, children: [['notifyTask', '任务完成', notifyTask, setNotifyTask], ['notifyAsk', '等待回答', notifyAsk, setNotifyAsk], ['notifyApproval', '等待审批', notifyApproval, setNotifyApproval], ['notifyDenied', '审批被拒绝', notifyDenied, setNotifyDenied], ['notifySubagent', '子任务结束', notifySubagent, setNotifySubagent], ['notifyGoal', '目标完成/受阻', notifyGoal, setNotifyGoal], ['notifyJobs', '后台任务结束', notifyJobs, setNotifyJobs]].map(([key, label, value, setter]) => ((0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap' }, children: [(0, jsx_runtime_1.jsx)("input", { className: "enh-event-check", type: "checkbox", checked: value, onChange: (event) => setter(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: label })] }, key))) }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u684C\u9762\u901A\u77E5", children: (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: notifyEnabled, onChange: (event) => setNotifyEnabled(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: notifyEnabled ? '启用' : '停用' })] }) }), (0, jsx_runtime_1.jsxs)(Row, { label: "PWA \u63A8\u9001", children: [(0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8, opacity: pushUsable ? 1 : 0.5 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: pushUsable && pushEnabled, disabled: !pushUsable, onChange: (event) => setPushEnabled(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: pushUsable ? (pushEnabled ? '启用' : '停用') : '需要 HTTPS' })] }), !pushUsable ? (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5F53\u524D\u9875\u9762\u4E0D\u662F HTTPS/\u5B89\u5168\u4E0A\u4E0B\u6587\uFF0CPWA \u63A8\u9001\u5F00\u5173\u7F6E\u7070\uFF1B\u8BF7\u5148\u542F\u7528 HTTPS\u3002" }) : null] }), pushUsable && pushEnabled ? ((0, jsx_runtime_1.jsxs)("div", { style: { paddingLeft: 12, borderLeft: '2px solid var(--dsw-alias-border-l4, rgba(0,0,0,.08))' }, children: [(0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: ["\u901A\u77E5\u6743\u9650\uFF1A", typeof Notification !== 'undefined' ? Notification.permission : '不可用', "\uFF1B\u5DF2\u8BA2\u9605\u8BBE\u5907\uFF1A", pushCount, "\u3002"] }), (0, jsx_runtime_1.jsx)("div", { style: styles.actions, children: (0, jsx_runtime_1.jsx)("button", { style: styles.button, disabled: busy, onClick: deviceSubscribed ? disablePush : enablePush, children: deviceSubscribed ? '取消本设备订阅' : '订阅本设备' }) }), (0, jsx_runtime_1.jsxs)("details", { className: "enh-advanced", children: [(0, jsx_runtime_1.jsx)("summary", { children: "\u9AD8\u7EA7\uFF1AVAPID \u8054\u7CFB\u5730\u5740" }), (0, jsx_runtime_1.jsxs)(Row, { label: "VAPID subject", children: [(0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pushSubject, onChange: (event) => setPushSubject(event.target.value), placeholder: "mailto:dsh-enhancement@localhost" }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "Web Push \u534F\u8BAE\u8981\u6C42\u7684\u8054\u7CFB\u65B9\u5F0F\uFF0C\u4E0D\u662F\u767B\u5F55\u90AE\u7BB1\uFF0C\u4E5F\u4E0D\u9700\u8981\u9A8C\u8BC1\uFF1B\u4E2A\u4EBA\u4F7F\u7528\u4FDD\u7559\u9ED8\u8BA4\u5373\u53EF\u3002" })] })] })] })) : pushUsable ? (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5F00\u542F PWA \u63A8\u9001\u540E\u663E\u793A\u8BA2\u9605\u3001\u6D4B\u8BD5\u548C VAPID \u8BBE\u7F6E\u3002" }) : null] }), (0, jsx_runtime_1.jsxs)("div", { style: { ...styles.card, order: 5 }, children: [(0, jsx_runtime_1.jsx)("div", { style: styles.label, children: "\u72B6\u6001\u6587\u4EF6" }), (0, jsx_runtime_1.jsx)("div", { style: styles.code, children: status.statePath }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5BC6\u7801\u53EA\u5B58 scrypt \u54C8\u5E0C\uFF0CDSH \u7B7E\u540D\u5BC6\u94A5\u4E0D\u4F1A\u5199\u5165\u8FD9\u91CC\u3002" })] }), (0, jsx_runtime_1.jsxs)(Block, { order: 5, title: "\u4F1A\u8BDD\u8BF7\u6C42\u5934", hint: "\u8BA9\u975E\u5B98\u65B9 API \u8DEF\u7531\uFF08\u5982 OpenAI \u517C\u5BB9\u7F51\u5173\uFF09\u4E5F\u643A\u5E26 DSH \u7684 session id\u3002\u5B98\u65B9 DeepSeek API \u672C\u6765\u5C31\u5E26\uFF0C\u4E0D\u53D7\u5F71\u54CD\u3002", dirty: sessionHeaderDirty, busy: busy, saveLabel: "\u4FDD\u5B58\u4F1A\u8BDD\u8BF7\u6C42\u5934", onSave: saveSessionHeader, status: blockStatus.sessionHeader, children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u542F\u7528\u6CE8\u5165", children: (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: sessionHeaderEnabled, onChange: (event) => setSessionHeaderEnabled(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: sessionHeaderEnabled ? '启用' : '停用' })] }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u8BF7\u6C42\u5934\u540D\u79F0", children: (0, jsx_runtime_1.jsx)("input", { value: sessionHeaderName, onChange: (event) => setSessionHeaderName(event.target.value), placeholder: "x-deepseek-harness-session-id", style: { ...styles.input, width: '100%', maxWidth: 360 }, spellCheck: false, autoCapitalize: "off", autoCorrect: "off" }) }), (0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: ["\u9ED8\u8BA4\u503C\u4E0E DeepSeek \u5B98\u65B9\u53D1\u9001\u7684\u5934\u540D\u4E00\u81F4\uFF08", (0, jsx_runtime_1.jsx)("code", { children: "x-deepseek-harness-session-id" }), "\uFF09\uFF0C\u4E5F\u53EF\u6539\u6210\u4E0A\u6E38\u7F51\u5173\u7EA6\u5B9A\u7684\u5176\u5B83\u540D\u79F0\u3002"] }), status.sessionHeader.reason === 'llm-unavailable' ? ((0, jsx_runtime_1.jsx)("div", { style: styles.error, children: sessionHeaderSummary(true) })) : ((0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: sessionHeaderSummary() }))] })] }));
|
|
602
634
|
}
|
|
603
635
|
const SESSION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,200}$/;
|
|
604
636
|
function openSession(ctx, sessionId) {
|
package/lib/control.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ import type { Pwa } from './pwa.js';
|
|
|
4
4
|
import type { NotificationEngine } from './notify.js';
|
|
5
5
|
import type { PushService } from './push.js';
|
|
6
6
|
import type { HostContextLike } from './types.js';
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import type { SessionHeaderMount } from './session-header.js';
|
|
8
|
+
export declare function registerControl(ctx: HostContextLike, gateway: Gateway, store: StateStore, pwa: Pwa, notify: NotificationEngine, push: PushService, sessionHeader?: SessionHeaderMount): void;
|