@ghfight/dsh-llm-proxy 0.0.0-stage → 0.4.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-llm-proxy contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,254 @@
1
- # Temporary Holding Version
1
+ # DSH LLM Proxy
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > 原名 `dsh-clash-proxy`,自 v0.4.0 起更名为 **DSH LLM Proxy**;npm 包名为 `@ghfight/dsh-llm-proxy`(npm 认为非 scoped 的 `dsh-llm-proxy` 与现有包 `dsh-llmproxy` 过于相似)。数据目录和 Web 路由保持不变。
4
+
5
+ 给 **DSH 进程自己** 使用的 Clash / Mihomo 代理插件:
6
+
7
+ - 导入 Clash / Mihomo 订阅链接(http(s)、本地 YAML,兼容 base64 包装的 YAML);
8
+ - DSH 自动按延迟选择节点,定期重新测速并切换;
9
+ - 也可以手动指定节点,或一键恢复自动选择;
10
+ - 不修改 Windows 系统代理,不开 TUN,不影响浏览器、游戏、下载器等其它程序;
11
+ - 只把 DSH 主进程发出的网络请求接到本机回环地址上的私有 Mihomo 实例;
12
+ - 自带 Web 控制面板,可直接导入订阅、查看状态、自动/手动选择节点;
13
+ - 支持“全部流量 / 仅模型接口”两种代理范围,以及按域名绕行,默认绕开 DSH Remote 等控制面。
14
+
15
+ > 已在 Windows x64 + DSH 0.2.2-rc.1 上完成端到端验证。其它平台有对应的 Mihomo 核心映射,但未逐一实测。
16
+
17
+ ## 工作原理
18
+
19
+ 插件在 DSH 数据目录(默认 `%USERPROFILE%\.dsh\clash-proxy`)下启动一个独立的 Mihomo 子进程:
20
+
21
+ ```text
22
+ DSH 主进程 fetch
23
+ │
24
+ ▼
25
+ 127.0.0.1:<随机混合端口> ← 只监听回环地址
26
+ │
27
+ ▼
28
+ Mihomo(私有配置、私有数据目录)
29
+ │
30
+ ├─ DSH-AUTO:url-test 自动测速组
31
+ └─ DSH:select 组,默认指向 DSH-AUTO,也可手动选节点
32
+ ```
33
+
34
+ - Mihomo 配置只包含订阅里的 `proxies`、`DSH-AUTO`、`DSH` 和 `MATCH,DSH` 规则;
35
+ - `allow-lan: false`,不监听局域网;`dns.enable: false`;不设置系统代理、不创建 TUN 网卡;
36
+ - 外部控制端口使用随机端口 + 随机 secret,仅绑定 `127.0.0.1`;
37
+ - 插件替换 DSH 主进程的 `globalThis.fetch`:回环地址和私有网段直接走原 fetch,其它地址走本机 Mihomo 的混合端口;
38
+ - 节点切换发生在 Mihomo 内部,因此切换节点不需要重装 dispatcher,也不会断开 DSH 的其它连接。
39
+
40
+ ## 环境要求
41
+
42
+ - DSH `0.2.2-rc.1` 或兼容版本(插件清单版本 1);
43
+ - Node.js `>= 20`;
44
+ - 订阅必须是 **包含内联 `proxies` 列表的 Clash / Mihomo YAML**;
45
+ - 支持直接返回 YAML,也支持整段 base64 包装的 YAML;
46
+ - 只返回 `proxy-providers`、没有内联 `proxies` 的订阅暂不支持,请换一个返回完整配置的订阅链接。
47
+
48
+ ## 安装
49
+
50
+ ### 方式 A:从 npm 安装(推荐)
51
+
52
+ ```bat
53
+ dsh plugin --profile desktop add @ghfight/dsh-llm-proxy --config.auto-install-peers=false
54
+ ```
55
+
56
+ > 如果 `npm view @ghfight/dsh-llm-proxy` 仍返回 404,说明 npm 还在处理首次发布,请改用方式 B。
57
+
58
+ ### 方式 B:安装 Release 中的 tarball
59
+
60
+ 1. 从 [Releases](https://github.com/Ghfight-bilibili/dsh-llm-proxy/releases) 下载
61
+ `dsh-llm-proxy-0.4.1.tgz`;
62
+ 2. 在下载目录执行:
63
+
64
+ ```bat
65
+ dsh plugin --profile desktop add .\dsh-llm-proxy-0.4.1.tgz --config.auto-install-peers=false
66
+ ```
67
+
68
+ 然后把下面这段加到当前 profile 的用户 patch 文件
69
+ `C:\Users\<用户名>\.dsh\profiles\desktop\cordis.patch.yml` 末尾:
70
+
71
+ ```yaml
72
+ - insert:
73
+ - id: dsh-llm-proxy
74
+ name: "@ghfight/dsh-llm-proxy"
75
+ config:
76
+ autoStart: true
77
+ ```
78
+
79
+ > 插件自己的 `cordis.patch.yml` 也会插入同名条目。**不要同时**把
80
+ > `@ghfight/dsh-llm-proxy` 加进 `dsh.profile.bundles` 再手写 patch,否则可能重复加载。
81
+ > 二选一:用上面的手写 patch,或把包名加入 `dsh.profile.bundles` 让插件自带 patch 生效。
82
+
83
+ ### 方式 C:从源码打包
84
+
85
+ ```bat
86
+ git clone https://github.com/Ghfight-bilibili/dsh-llm-proxy.git
87
+ cd dsh-llm-proxy
88
+ npm pack
89
+ dsh plugin --profile desktop add .\ghfight-dsh-llm-proxy-0.4.1.tgz --config.auto-install-peers=false
90
+ ```
91
+
92
+ 然后按方式 B 的 YAML 增加 patch 条目。
93
+
94
+ 安装完成后重启 DSH。重启后在会话里应该能看到 `clash_*` 工具。
95
+
96
+ ### 从旧名 `dsh-clash-proxy` 迁移
97
+
98
+ 新旧包共用数据目录 `%USERPROFILE%\.dsh\clash-proxy`(或 `$DSH_HOME/clash-proxy`),订阅、核心和节点状态都不会丢。升级时不要同时保留两个 patch 条目:
99
+
100
+ 1. 删除 profile patch 里 `id: dsh-clash-proxy` 的 `insert` 条目;
101
+ 2. `dsh plugin --profile desktop remove dsh-clash-proxy`;
102
+ 3. 按上面的方式安装 `@ghfight/dsh-llm-proxy`,把 patch 条目改成 `id: dsh-llm-proxy` / `name: "@ghfight/dsh-llm-proxy"`;
103
+ 4. 如果装过非 scoped 的 `dsh-llm-proxy@0.4.0`,先 `dsh plugin --profile desktop remove dsh-llm-proxy`,再安装 scoped 包;两者不能同时存在;
104
+ 5. 重启 DSH。
105
+
106
+ ## Web 控制面板
107
+
108
+ 插件自带一个控制面板,不用记工具名也能操作:
109
+
110
+ - 在 DSH 的 **设置 → DSH LLM Proxy** 中直接打开;
111
+ - 或浏览器访问 `http://127.0.0.1:<DSH Web 端口>/clash-proxy`(端口以你的 DSH Web 地址为准)。
112
+
113
+ 面板支持:
114
+
115
+ - 粘贴订阅链接并一键导入、启动;
116
+ - 查看运行状态、当前出口节点、端口和核心版本;
117
+ - 自动选择 / 手动切换节点,按延迟排序和搜索;
118
+ - 测速、更新订阅、安装/修复核心、启动/停止代理;
119
+ - “绕过与范围”选项卡:切换“全部流量 / 仅模型接口”,编辑按域名绕行列表,保存后立即生效。
120
+ - “打开完整页面”点击后立即在 DSH 内显示全屏面板,同时由 host 调用系统默认浏览器;即使浏览器被挡在后面,DSH 内也能直接操作。
121
+
122
+ 面板里的所有操作都只作用于 DSH,不会修改系统代理或其它程序。
123
+
124
+ ## 配置
125
+
126
+ 在 profile patch 的 `config` 中配置,全部字段都可省略:
127
+
128
+ | 字段 | 默认值 | 说明 |
129
+ | --- | --- | --- |
130
+ | `autoStart` | `true` | DSH 启动时,如果已有订阅和核心,就自动启动代理 |
131
+ | `subscriptionUrl` | `''` | 首次启动自动导入的订阅链接;为空则等待手动 `clash_subscribe` |
132
+ | `corePath` | `''` | 自定义 `mihomo` 可执行文件路径;为空则使用数据目录下自动安装的核心 |
133
+ | `testUrl` | `https://www.gstatic.com/generate_204` | 自动测速 URL |
134
+ | `testInterval` | `300` | 自动测速间隔,秒,范围 30–3600 |
135
+ | `mixedPort` | `0` | 混合代理端口;`0` 表示自动选择空闲端口 |
136
+ | `dataDir` | `''` | 数据目录;为空使用 `$DSH_HOME/clash-proxy` 或 `%USERPROFILE%\.dsh\clash-proxy` |
137
+ | `downloadCore` | `true` | 核心缺失时是否自动从 npm 下载匹配平台的 Mihomo |
138
+ | `proxyScope` | `all` | 代理范围:`all` 全部外部请求;`models` 仅大模型 API,其余直连 |
139
+ | `bypassHosts` | 默认控制面列表 | 额外绕行域名,逗号或换行分隔;支持 `*`、`*.example.com`;留空则使用默认值 |
140
+
141
+ 示例:
142
+
143
+ ```yaml
144
+ - insert:
145
+ - id: dsh-llm-proxy
146
+ name: "@ghfight/dsh-llm-proxy"
147
+ config:
148
+ autoStart: true
149
+ subscriptionUrl: 'https://example.com/api/v1/client/subscribe?token=...'
150
+ testInterval: 300
151
+ testUrl: 'https://www.gstatic.com/generate_204'
152
+ mixedPort: 0
153
+ downloadCore: true
154
+ proxyScope: all
155
+ bypassHosts: 'dsh.r2049.cn,*.r2049.cn,*.ts.net'
156
+ ```
157
+
158
+ ## 工具
159
+
160
+ | 工具 | 作用 |
161
+ | --- | --- |
162
+ | `clash_subscribe` | 导入订阅并立即启动;参数 `url`(必填,http(s) 或本地 YAML 路径)、`name`(可选显示名) |
163
+ | `clash_status` | 查看运行状态、当前节点、模式、端口、订阅、核心版本、最近错误 |
164
+ | `clash_nodes` | 列出节点和延迟;`test=true` 时立即测速,`limit` 默认 12、最大 50 |
165
+ | `clash_select` | 手动指定节点;`name` 传节点名或 `auto` |
166
+ | `clash_auto` | 恢复自动选择(DSH-AUTO) |
167
+ | `clash_test` | 测试节点延迟;可用 `names` 指定节点,或不传 `names` 测前 `limit` 个 |
168
+ | `clash_update` | 重新拉取当前订阅并热重启;手动节点仍存在时会保留 |
169
+ | `clash_start` | 用已保存的订阅启动 |
170
+ | `clash_stop` | 停止核心并恢复 DSH 直连 |
171
+ | `clash_install_core` | 下载 / 修复当前平台的 Mihomo 核心 |
172
+
173
+ ## 典型使用
174
+
175
+ 首次使用:
176
+
177
+ 1. 对 DSH 说「导入这个 Clash 订阅:https://...」;
178
+ 2. DSH 调用 `clash_subscribe`,自动下载核心(若缺失)、解析节点、启动 Mihomo;
179
+ 3. 用 `clash_status` 确认 `running: true`、`mode: auto`;
180
+ 4. 用 `clash_nodes` 查看节点和延迟。
181
+
182
+ 手动换节点:
183
+
184
+ - 「用香港 01 节点」→ `clash_select({ name: '香港 01' })`;
185
+ - 「恢复自动选节点」→ `clash_auto()`。
186
+
187
+ 更新订阅:
188
+
189
+ - `clash_update()`。如果之前手动选的节点在新订阅里仍然存在,会继续保留;否则自动回到 `DSH-AUTO`。
190
+
191
+ 临时直连:
192
+
193
+ - `clash_stop()`。DSH 立即恢复原 fetch;其它程序全程不受影响。
194
+
195
+ ## 数据目录
196
+
197
+ 默认 `%USERPROFILE%\.dsh\clash-proxy\`(设置了 `DSH_HOME` 时为 `$DSH_HOME/clash-proxy`):
198
+
199
+ ```text
200
+ clash-proxy/
201
+ state.json # 运行状态、订阅信息、当前选择
202
+ config.yaml # 每次启动生成的 Mihomo 配置
203
+ subscription.yaml # 最近一次原始订阅
204
+ proxies.json # 解析后的节点
205
+ bin/mihomo.exe # 自动下载的核心
206
+ ```
207
+
208
+ 订阅和节点文件以 `0600` 权限写入(Windows 上由 ACL 体现)。
209
+
210
+ ## 自动调度细节
211
+
212
+ - `DSH-AUTO` 是 Mihomo 的 `url-test` 组:
213
+ - `url` 为配置的 `testUrl`;
214
+ - 每 `testInterval` 秒测试全部节点;
215
+ - `tolerance: 50`,延迟差距在 50ms 内不频繁切换;
216
+ - `lazy: false`,即使没有流量也会定期测速。
217
+ - `DSH` 是 `select` 组,默认选中 `DSH-AUTO`;
218
+ - 手动选择写入 `state.json`,并通过 Mihomo 的 `profile.store-selected` 持久化;
219
+ - 代理入口始终是固定的本机混合端口,节点切换不会改变 DSH 的 fetch 配置。
220
+
221
+ ## 不影响其它程序
222
+
223
+ - 不调用 Windows「Internet 选项」、不写系统代理注册表;
224
+ - 不启用 TUN / 虚拟网卡;
225
+ - Mihomo 只监听 `127.0.0.1`,`allow-lan: false`;
226
+ - 只替换 **DSH 主进程** 的 `globalThis.fetch`;其它进程完全不知道这个代理存在;
227
+ - 回环地址、`localhost`、私有网段(10/8、172.16/12、192.168/16、169.254/16、100.64/10)直接走原 fetch,避免影响本机服务、局域网模型和 DSH 自己的控制面;
228
+ - 默认绕行 `dsh.r2049.cn`、`*.r2049.cn`、`*.ts.net`、`*.local`、`*.lan`,以及 `deepseek.com`、`*.deepseek.com`、`deepseek.ai`、`*.deepseek.ai`、`deepseek-ai.github.io`。DSH Remote 的信令服务器 `https://dsh.r2049.cn` 因此始终直连;
229
+ - 也可以在面板里改用“仅模型接口”,只让大模型 API 走代理,Remote、插件市场和其它网络请求全部直连。
230
+
231
+ ## 已知限制
232
+
233
+ - 只覆盖 DSH 主进程的 `globalThis.fetch`。Node `worker_threads`、`node:https`、WebSocket、Electron `net` 等不会自动经过代理;当前 DSH 的 LLM 请求使用 `fetch`,主链路已覆盖。
234
+ - 如果另一个插件在 DSH 启动后再次替换 `globalThis.fetch`,可能绕过本插件;卸载/停用本插件时会恢复原 fetch。
235
+ - 只支持内联 `proxies` 的订阅;`proxy-providers` 需要先由服务商展开。
236
+ - 需要 DSH 进程有启动子进程的权限。若出现 `spawn ... EPERM`,检查 DSH 是否运行在禁止子进程的沙箱中。
237
+ - Mihomo 核心从 npm 的 `@pkgship/mihomo-*` 包下载;首次安装需要能访问 npm registry。
238
+ - 订阅节点本身的协议支持范围由 Mihomo 决定。
239
+
240
+ ## 卸载
241
+
242
+ 1. 在 profile patch 中删除 `id: dsh-llm-proxy` 的 `insert` 条目;
243
+ 2. 如果通过 pnpm 安装:
244
+
245
+ ```bat
246
+ dsh plugin --profile desktop remove @ghfight/dsh-llm-proxy
247
+ ```
248
+
249
+ 3. 删除数据目录 `%USERPROFILE%\.dsh\clash-proxy`(可选,会同时删除已下载核心和订阅缓存);
250
+ 4. 重启 DSH。
251
+
252
+ ## 许可证
253
+
254
+ MIT,见 [LICENSE](LICENSE)。
@@ -0,0 +1,4 @@
1
+ # DSH LLM Proxy bundle patch: mounts one plugin row when installed as a DSH bundle.
2
+ - insert:
3
+ - id: dsh-llm-proxy
4
+ name: "@ghfight/dsh-llm-proxy"
package/lib/client.js ADDED
@@ -0,0 +1,182 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@ghfight/dsh-llm-proxy",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ var React = require("react");
7
+ var h = React.createElement;
8
+
9
+ function Panel() {
10
+ var origin = window.location && window.location.origin ? window.location.origin : "";
11
+ var fullUrl = origin + "/clash-proxy";
12
+ var frameUrl = fullUrl + "?embed=1";
13
+ var overlayState = React.useState(false);
14
+ var overlay = overlayState[0];
15
+ var setOverlay = overlayState[1];
16
+ var openState = React.useState("");
17
+ var openMessage = openState[0];
18
+ var setOpenMessage = openState[1];
19
+
20
+ var openExternal = function () {
21
+ setOpenMessage("正在用系统浏览器打开…");
22
+ fetch(origin + "/clash-proxy/api/open", {
23
+ method: "POST",
24
+ headers: { "content-type": "application/json" },
25
+ body: JSON.stringify({ url: fullUrl })
26
+ }).then(function (response) {
27
+ return response.json().then(function (data) {
28
+ if (!response.ok || !data || data.ok === false) {
29
+ var detail = data && data.error && data.error.message ? data.error.message : ("HTTP " + response.status);
30
+ throw new Error(detail);
31
+ }
32
+ return data;
33
+ });
34
+ }).then(function () {
35
+ setOpenMessage("已在系统浏览器中打开");
36
+ }).catch(function (error) {
37
+ try {
38
+ window.open(fullUrl, "_blank", "noopener,noreferrer");
39
+ setOpenMessage("已请求系统浏览器打开");
40
+ } catch (fallbackError) {
41
+ setOpenMessage("系统浏览器打开失败:" + (error && error.message ? error.message : error));
42
+ }
43
+ });
44
+ };
45
+
46
+ var openFullPage = function () {
47
+ setOverlay(true);
48
+ setOpenMessage("完整页面已打开");
49
+ openExternal();
50
+ };
51
+
52
+ var closeOverlay = function () {
53
+ setOverlay(false);
54
+ setOpenMessage("");
55
+ };
56
+
57
+ var patchEmbeddedFrame = function (event) {
58
+ try {
59
+ var frame = event && event.currentTarget;
60
+ var doc = frame && frame.contentDocument;
61
+ var button = doc && doc.getElementById("fullBtn");
62
+ if (!button) return;
63
+ var replacement = doc.createElement("button");
64
+ replacement.id = "fullBtn";
65
+ replacement.className = button.className;
66
+ replacement.type = "button";
67
+ replacement.textContent = "打开完整页面";
68
+ replacement.title = "在 DSH 内打开完整页面,并同时尝试系统浏览器";
69
+ replacement.addEventListener("click", function (event) {
70
+ event.preventDefault();
71
+ openFullPage();
72
+ });
73
+ button.replaceWith(replacement);
74
+ } catch (error) {}
75
+ };
76
+
77
+ var overlayView = null;
78
+ if (overlay) {
79
+ overlayView = h("div", { className: "dsh-llm-proxy-overlay", role: "dialog", "aria-modal": "true" },
80
+ h("div", { className: "dsh-llm-proxy-overlay-head" },
81
+ h("div", null,
82
+ h("div", { className: "dsh-llm-proxy-overlay-title" }, "DSH LLM Proxy · 完整页面"),
83
+ h("div", { className: "dsh-llm-proxy-overlay-sub" }, "完整功能已在当前窗口打开;如系统浏览器没有出现,可点右侧按钮重试。")
84
+ ),
85
+ h("div", { className: "dsh-llm-proxy-overlay-actions" },
86
+ h("button", { className: "dsh-llm-proxy-open", type: "button", onClick: openExternal }, "在系统浏览器中打开"),
87
+ h("button", { className: "dsh-llm-proxy-close", type: "button", onClick: closeOverlay }, "关闭")
88
+ )
89
+ ),
90
+ h("iframe", { className: "dsh-llm-proxy-overlay-frame", src: frameUrl, title: "DSH LLM Proxy 完整控制面板" })
91
+ );
92
+ }
93
+
94
+ return h("div", { className: "dsh-llm-proxy-panel" },
95
+ h("style", null,
96
+ ".dsh-llm-proxy-panel{width:100%;color:inherit}" +
97
+ ".dsh-llm-proxy-bar{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:14px 16px;margin-bottom:12px;border-radius:16px;" +
98
+ "background:rgba(255,255,255,.56);border:1px solid rgba(255,255,255,.72);" +
99
+ "box-shadow:0 18px 48px rgba(31,56,102,.12),inset 0 1px 0 rgba(255,255,255,.9);" +
100
+ "-webkit-backdrop-filter:blur(24px) saturate(160%);backdrop-filter:blur(24px) saturate(160%)}" +
101
+ ".dsh-llm-proxy-title{font-weight:700;font-size:14px;letter-spacing:.2px;display:flex;align-items:center;gap:9px}" +
102
+ ".dsh-llm-proxy-dot{width:8px;height:8px;border-radius:50%;background:#4f8cff;box-shadow:0 0 0 4px rgba(79,140,255,.16)}" +
103
+ ".dsh-llm-proxy-sub{opacity:.62;font-size:12px;margin-top:3px}" +
104
+ ".dsh-llm-proxy-actions{display:flex;flex-direction:column;align-items:flex-end;gap:5px;flex:none}" +
105
+ ".dsh-llm-proxy-open{height:30px;padding:0 12px;border:0;cursor:pointer;border-radius:8px;display:inline-flex;align-items:center;justify-content:center;" +
106
+ "font:inherit;font-size:12px;font-weight:650;text-decoration:none;color:#fff;white-space:nowrap;" +
107
+ "background:linear-gradient(135deg,#4f8cff,#6f7cff);box-shadow:0 8px 20px rgba(79,140,255,.28),inset 0 1px 0 rgba(255,255,255,.28)}" +
108
+ ".dsh-llm-proxy-open:hover{filter:brightness(1.04)}" +
109
+ ".dsh-llm-proxy-open:disabled{opacity:.55;cursor:not-allowed}" +
110
+ ".dsh-llm-proxy-msg{max-width:240px;text-align:right;font-size:11px;line-height:1.35;opacity:.68}" +
111
+ ".dsh-llm-proxy-frame{width:100%;height:min(74vh,780px);min-height:540px;display:block;border-radius:16px;" +
112
+ "border:1px solid rgba(127,127,127,.22);background:transparent}" +
113
+ ".dsh-llm-proxy-overlay{position:fixed;inset:0;z-index:2147483000;display:flex;flex-direction:column;gap:12px;padding:18px;" +
114
+ "background:radial-gradient(120% 100% at 50% 0%,rgba(79,140,255,.22),rgba(8,14,26,.86) 58%),rgba(8,14,26,.88);" +
115
+ "-webkit-backdrop-filter:blur(26px) saturate(150%);backdrop-filter:blur(26px) saturate(150%)}" +
116
+ ".dsh-llm-proxy-overlay-head{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 18px;border-radius:16px;" +
117
+ "background:rgba(255,255,255,.10);border:1px solid rgba(255,255,255,.16);color:#fff;" +
118
+ "box-shadow:inset 0 1px 0 rgba(255,255,255,.16)}" +
119
+ ".dsh-llm-proxy-overlay-title{font-size:15px;font-weight:750;letter-spacing:.2px}" +
120
+ ".dsh-llm-proxy-overlay-sub{font-size:12px;line-height:1.4;opacity:.7;margin-top:3px}" +
121
+ ".dsh-llm-proxy-overlay-actions{display:flex;gap:8px;flex:none;align-items:center}" +
122
+ ".dsh-llm-proxy-close{height:30px;padding:0 12px;border-radius:8px;cursor:pointer;font:inherit;font-size:12px;font-weight:650;" +
123
+ "color:#fff;background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.22)}" +
124
+ ".dsh-llm-proxy-close:hover{background:rgba(255,255,255,.18)}" +
125
+ ".dsh-llm-proxy-overlay-frame{flex:1;width:100%;min-height:0;display:block;border:1px solid rgba(255,255,255,.16);border-radius:16px;background:#fff}" +
126
+ "@media (prefers-color-scheme:dark){" +
127
+ ".dsh-llm-proxy-bar{background:rgba(13,21,36,.64);border-color:rgba(255,255,255,.12);" +
128
+ "box-shadow:0 18px 48px rgba(0,0,0,.38),inset 0 1px 0 rgba(255,255,255,.07)}" +
129
+ ".dsh-llm-proxy-frame{border-color:rgba(255,255,255,.14)}" +
130
+ ".dsh-llm-proxy-overlay-frame{background:#0d1524}}"
131
+ ),
132
+ h("div", { className: "dsh-llm-proxy-bar" },
133
+ h("div", null,
134
+ h("div", { className: "dsh-llm-proxy-title" },
135
+ h("span", { className: "dsh-llm-proxy-dot" }),
136
+ "DSH LLM Proxy"
137
+ ),
138
+ h("div", { className: "dsh-llm-proxy-sub" }, "订阅、节点选择、代理范围与绕行规则 · v0.4.1")
139
+ ),
140
+ h("div", { className: "dsh-llm-proxy-actions" },
141
+ h("button", {
142
+ className: "dsh-llm-proxy-open",
143
+ type: "button",
144
+ onClick: openFullPage
145
+ }, "打开完整页面"),
146
+ openMessage ? h("div", { className: "dsh-llm-proxy-msg" }, openMessage) : null
147
+ )
148
+ ),
149
+ h("iframe", {
150
+ className: "dsh-llm-proxy-frame",
151
+ src: frameUrl,
152
+ title: "DSH LLM Proxy 控制面板",
153
+ onLoad: patchEmbeddedFrame
154
+ }),
155
+ overlayView
156
+ );
157
+ }
158
+
159
+ function apply(ctx) {
160
+ ctx.effect(function () {
161
+ try {
162
+ return ctx.slots.inject("settings.section", function () {
163
+ return ctx.slots.register({
164
+ name: "settings.section",
165
+ id: "clash-proxy",
166
+ order: 120,
167
+ label: function () { return "DSH LLM Proxy"; },
168
+ inject: function () { return {}; }
169
+ }, Panel);
170
+ });
171
+ } catch (error) {
172
+ if (typeof console !== "undefined" && console.warn) console.warn("dsh-llm-proxy: settings section unavailable", error);
173
+ return function () {};
174
+ }
175
+ }, "dsh-llm-proxy: settings section");
176
+ }
177
+
178
+ exports.apply = apply;
179
+ exports.inject = ["slots"];
180
+ return module.exports;
181
+ }
182
+ });