@yanglaofish/dsh-proxy-pro 1.0.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/LICENSE +21 -0
- package/README.md +153 -0
- package/cordis.patch.yml +14 -0
- package/docs/ACCEPTANCE.md +61 -0
- package/docs/LESSONS.md +551 -0
- package/docs/SPEC.md +381 -0
- package/lib/client.js +457 -0
- package/lib/index.js +693 -0
- package/lib/proxy-core.js +268 -0
- package/lib/tool-schemas.js +103 -0
- package/package.json +62 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-proxy-pro — browser face.
|
|
3
|
+
*
|
|
4
|
+
* Two contributions:
|
|
5
|
+
* 1. A capsule proxy button in the conversation header
|
|
6
|
+
* (`conversation.session.header.utilities`): green/red status dot with a
|
|
7
|
+
* live label, one click toggles the master `enabled` switch.
|
|
8
|
+
* 2. A dedicated Settings page (`settings.section`, label 代理管理): runtime
|
|
9
|
+
* facts (active / url / reason — from the host's /dsh-proxy-pro/api,
|
|
10
|
+
* NOT derived from settings), the editable config fields (mode / custom
|
|
11
|
+
* URL / NO_PROXY), and a per-URL route diagnostic.
|
|
12
|
+
*
|
|
13
|
+
* Both read and write the `proxy` settings namespace through the shared
|
|
14
|
+
* `ctx.settingsScope` transport (same as dsh-plugin-proxy); runtime facts
|
|
15
|
+
* come from the same-origin /dsh-proxy-pro/api endpoints served by the host
|
|
16
|
+
* half (browser-trust fenced, see lib/index.js).
|
|
17
|
+
*
|
|
18
|
+
* This hand-written module mirrors the loader format the in-repo client
|
|
19
|
+
* bundles emit (window.__ModuleLoader__.load with a CommonJS factory): react
|
|
20
|
+
* is required through the app's module table, no JSX, no bundler, no TS.
|
|
21
|
+
*/
|
|
22
|
+
window.__ModuleLoader__.load({
|
|
23
|
+
id: "dsh-proxy-pro/client",
|
|
24
|
+
factory: (require) => {
|
|
25
|
+
var module = { exports: {} };
|
|
26
|
+
var exports = module.exports;
|
|
27
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
28
|
+
var react = require("react");
|
|
29
|
+
var useState = react.useState;
|
|
30
|
+
var useEffect = react.useEffect;
|
|
31
|
+
var useRef = react.useRef;
|
|
32
|
+
var useSyncExternalStore = react.useSyncExternalStore;
|
|
33
|
+
|
|
34
|
+
var cssId = "dsh-proxy-pro/client";
|
|
35
|
+
var css = [
|
|
36
|
+
".dsxprx{display:flex;flex-direction:column;gap:14px}",
|
|
37
|
+
".dsxprx-status{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-specific-tip)}",
|
|
38
|
+
".dsxprx-dot{flex:none;width:10px;height:10px;border-radius:50%;background:var(--dsw-alias-state-error-primary)}",
|
|
39
|
+
".dsxprx-status[data-active=true] .dsxprx-dot{background:var(--dsw-alias-state-success-primary)}",
|
|
40
|
+
".dsxprx-statusbody{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}",
|
|
41
|
+
".dsxprx-statusline{font-size:13px;font-weight:600;line-height:18px;color:var(--dsw-alias-label-primary)}",
|
|
42
|
+
".dsxprx-subline{font-size:11px;line-height:15px;color:var(--dsw-alias-label-caption);word-break:break-all}",
|
|
43
|
+
".dsxprx-switchwrap{flex:none;display:flex;align-items:center;gap:6px;cursor:pointer}",
|
|
44
|
+
".dsxprx-switch{position:relative;width:36px;height:20px;border-radius:999px;background:var(--dsw-alias-border-l1);transition:background .15s ease;flex:none}",
|
|
45
|
+
".dsxprx-switch[data-on=true]{background:#4d6bfe}",
|
|
46
|
+
".dsxprx-switch::after{content:'';position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .15s ease}",
|
|
47
|
+
".dsxprx-switch[data-on=true]::after{transform:translateX(16px)}",
|
|
48
|
+
".dsxprx-switchwrap input{position:absolute;opacity:0;width:0;height:0}",
|
|
49
|
+
".dsxprx-switchwrap[data-disabled=true]{opacity:.5;cursor:not-allowed}",
|
|
50
|
+
".dsxprx-switch-sm{width:30px;height:17px}",
|
|
51
|
+
".dsxprx-switch-sm::after{width:13px;height:13px;top:2px;left:2px}",
|
|
52
|
+
".dsxprx-switch-sm[data-on=true]::after{transform:translateX(13px)}",
|
|
53
|
+
".dsxprx-field{display:flex;flex-direction:column;gap:4px}",
|
|
54
|
+
".dsxprx-field>label{font-size:12px;font-weight:500;color:var(--dsw-alias-label-secondary)}",
|
|
55
|
+
".dsxprx-field input[type=text],.dsxprx-field select{box-sizing:border-box;width:100%;padding:6px 8px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-specific-input);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}",
|
|
56
|
+
".dsxprx-field input:disabled{opacity:.5}",
|
|
57
|
+
".dsxprx-hint{font-size:11px;line-height:15px;color:var(--dsw-alias-label-caption)}",
|
|
58
|
+
".dsxprx-radios{display:flex;gap:6px;flex-wrap:wrap}",
|
|
59
|
+
".dsxprx-radio{flex:none;display:flex;align-items:center;gap:6px;padding:5px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;font-size:12px;line-height:16px;cursor:pointer;color:var(--dsw-alias-label-secondary)}",
|
|
60
|
+
".dsxprx-radio[data-on=true]{background:rgba(77,107,254,.12);border-color:rgba(77,107,254,.45);color:var(--dsw-alias-label-primary);font-weight:600}",
|
|
61
|
+
".dsxprx-radio input{position:absolute;opacity:0;width:0;height:0}",
|
|
62
|
+
".dsxprx-diag{display:flex;flex-direction:column;gap:6px}",
|
|
63
|
+
".dsxprx-diagbar{display:flex;gap:8px}",
|
|
64
|
+
".dsxprx-diagbar input[type=text]{flex:1;min-width:0;box-sizing:border-box;padding:6px 8px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-specific-input);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px}",
|
|
65
|
+
".dsxprx-diagbtn{flex:none;padding:6px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-specific-tip);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}",
|
|
66
|
+
".dsxprx-diagbtn:disabled{opacity:.5;cursor:not-allowed}",
|
|
67
|
+
".dsxprx-diagout{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-specific-tip);font-size:12px;line-height:17px;color:var(--dsw-alias-label-primary)}",
|
|
68
|
+
".dsxprx-title{font-size:16px;font-weight:700;line-height:22px;color:var(--dsw-alias-label-primary)}",
|
|
69
|
+
".dsxprx-diagcheck{flex:none;display:flex;align-items:center;gap:5px;font-size:12px;line-height:16px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap}",
|
|
70
|
+
".dsxprx-diagcheck input{width:14px;height:14px;accent-color:#4d6bfe;margin:0}",
|
|
71
|
+
".dsxprx-hdr{display:flex;align-items:center;gap:6px;max-width:180px;height:26px;padding:0 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;line-height:16px;cursor:pointer;white-space:nowrap;overflow:hidden}",
|
|
72
|
+
".dsxprx-hdr:hover{background:var(--dsw-alias-state-hover)}",
|
|
73
|
+
".dsxprx-hdr .dsxprx-dot{width:8px;height:8px}",
|
|
74
|
+
".dsxprx-hdr[data-active=true] .dsxprx-dot{background:var(--dsw-alias-state-success-primary)}",
|
|
75
|
+
".dsxprx-hdrlabel{min-width:0;overflow:hidden;text-overflow:ellipsis;font-weight:500}",
|
|
76
|
+
".dsxprx-hdr[data-active=false] .dsxprx-hdrlabel{color:var(--dsw-alias-label-secondary)}",
|
|
77
|
+
].join("");
|
|
78
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + cssId + "\"]") === null) {
|
|
79
|
+
var tag = document.createElement("style");
|
|
80
|
+
tag.dataset.plugin = "dsh-proxy-pro";
|
|
81
|
+
tag.dataset.pluginCss = cssId;
|
|
82
|
+
tag.textContent = css;
|
|
83
|
+
document.head.appendChild(tag);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
var API_BASE = "/dsh-proxy-pro/api";
|
|
87
|
+
|
|
88
|
+
/** Same-origin JSON helper toward the host API; resolves null on failure. */
|
|
89
|
+
function fetchJson(path, options) {
|
|
90
|
+
return fetch(API_BASE + path, options)
|
|
91
|
+
.then(function (res) { return res.json().catch(function () { return {}; }); })
|
|
92
|
+
.catch(function () { return null; });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---- reactive settings scope (the `proxy` namespace) ----
|
|
96
|
+
/** @returns `{ value, writable, ready }` from a bound SettingsScope. */
|
|
97
|
+
function useProxySnapshot(scope) {
|
|
98
|
+
var snapshot = useSyncExternalStore(
|
|
99
|
+
function (onChange) { return scope.subscribe(onChange); },
|
|
100
|
+
function () { return scope.getSnapshot(); }
|
|
101
|
+
);
|
|
102
|
+
var ready = snapshot.status === "ready" && snapshot.value !== undefined;
|
|
103
|
+
return {
|
|
104
|
+
value: ready ? snapshot.value : undefined,
|
|
105
|
+
writable: snapshot.writable === true && ready,
|
|
106
|
+
ready: snapshot.status === "unavailable" ? false : snapshot.status === "ready",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---- runtime facts (from /dsh-proxy-pro/api/status) ----
|
|
111
|
+
/** Fetch the host status snapshot on mount and whenever `refreshKey`
|
|
112
|
+
* changes (config edits / mode switches). No timed polling: each pull
|
|
113
|
+
* makes the host re-read the system facts (pull-triggered refresh,
|
|
114
|
+
* idempotent — never reinstalls unless the proxy actually changed), so
|
|
115
|
+
* a viewer always sees fresh facts while the host's own 30s poll keeps
|
|
116
|
+
* following system-proxy changes unattended (§17). */
|
|
117
|
+
function useStatusOnce(refreshKey) {
|
|
118
|
+
var state = useState({ loading: true, status: null, error: "" });
|
|
119
|
+
var setState = state[1];
|
|
120
|
+
useEffect(function () {
|
|
121
|
+
var alive = true;
|
|
122
|
+
fetchJson("/status").then(function (data) {
|
|
123
|
+
if (!alive) return;
|
|
124
|
+
if (data === null || data.ok !== true) {
|
|
125
|
+
setState({ loading: false, status: null, error: (data && data.error) || "status API unavailable" });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
setState({ loading: false, status: data.status, error: "" });
|
|
129
|
+
});
|
|
130
|
+
return function () { alive = false; };
|
|
131
|
+
}, [refreshKey]);
|
|
132
|
+
return state[0];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// A text input that drafts locally and commits on blur (external value
|
|
136
|
+
// wins only when it differs from the last committed draft).
|
|
137
|
+
function DraftInput(props) {
|
|
138
|
+
var textState = useState(props.value || "");
|
|
139
|
+
var text = textState[0];
|
|
140
|
+
var setText = textState[1];
|
|
141
|
+
var committed = useRef(props.value || "");
|
|
142
|
+
useEffect(function () {
|
|
143
|
+
var next = props.value || "";
|
|
144
|
+
if (next !== committed.current) {
|
|
145
|
+
committed.current = next;
|
|
146
|
+
setText(next);
|
|
147
|
+
}
|
|
148
|
+
}, [props.value]);
|
|
149
|
+
return react.createElement("input", {
|
|
150
|
+
type: "text",
|
|
151
|
+
value: text,
|
|
152
|
+
disabled: props.disabled === true,
|
|
153
|
+
placeholder: props.placeholder || "",
|
|
154
|
+
onChange: function (event) { setText(event.target.value); },
|
|
155
|
+
onBlur: function () {
|
|
156
|
+
if (text === committed.current) return;
|
|
157
|
+
committed.current = text;
|
|
158
|
+
props.onCommit(text);
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---- conversation header capsule button ----
|
|
164
|
+
function ProxyHeaderButton(props) {
|
|
165
|
+
var scope = props.scope;
|
|
166
|
+
var settings = useProxySnapshot(scope);
|
|
167
|
+
var busy = useState(false);
|
|
168
|
+
var setBusy = busy[1];
|
|
169
|
+
// Configuration-driven: the capsule mirrors the switch, resolved locally
|
|
170
|
+
// with zero requests. The host applies the change instantly when the
|
|
171
|
+
// settings revision lands (and follows external system-proxy changes on
|
|
172
|
+
// its own 30s poll), so polling here buys nothing (§17).
|
|
173
|
+
var config = settings.value || {};
|
|
174
|
+
var active = config.enabled === true && config.mode !== "none";
|
|
175
|
+
var enabled = config.enabled === true;
|
|
176
|
+
var mode = config.mode || "system";
|
|
177
|
+
var modeLabel = mode === "system" ? "system" : mode === "custom" ? "custom" : "none";
|
|
178
|
+
function toggle() {
|
|
179
|
+
if (!settings.writable) return;
|
|
180
|
+
setBusy(true);
|
|
181
|
+
var next = !enabled;
|
|
182
|
+
scope.set("enabled", next).catch(function (error) {
|
|
183
|
+
console.error("[dsh-proxy-pro] toggle failed:", error);
|
|
184
|
+
}).finally(function () { setBusy(false); });
|
|
185
|
+
// Toggle doubles as a manual "recheck now" even when the settings
|
|
186
|
+
// panel is not mounted: pulling /status makes the host re-read the
|
|
187
|
+
// system facts (idempotent — snapshot refresh unless the proxy really
|
|
188
|
+
// changed). The settings hook already re-syncs on scope.set; this
|
|
189
|
+
// pull is a visible, UI-independent follow-up so 关闭→打开 re-applies
|
|
190
|
+
// and re-verifies the proxy from the header alone.
|
|
191
|
+
fetchJson("/status");
|
|
192
|
+
}
|
|
193
|
+
var title = active
|
|
194
|
+
? "代理已开启(界面开关)· 点击关闭"
|
|
195
|
+
: "代理已关闭 · 点击开启";
|
|
196
|
+
return react.createElement(
|
|
197
|
+
"button",
|
|
198
|
+
{
|
|
199
|
+
type: "button",
|
|
200
|
+
className: "dsxprx-hdr",
|
|
201
|
+
"data-active": active ? "true" : "false",
|
|
202
|
+
"data-proxy-toggle": true,
|
|
203
|
+
onClick: toggle,
|
|
204
|
+
disabled: !settings.writable || busy[0],
|
|
205
|
+
title: title,
|
|
206
|
+
"aria-pressed": active,
|
|
207
|
+
},
|
|
208
|
+
react.createElement("span", { className: "dsxprx-dot" }),
|
|
209
|
+
react.createElement("span", { className: "dsxprx-hdrlabel" }, "代理 · " + (active ? "ON " + modeLabel : "OFF"))
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---- Settings page: 代理管理 ----
|
|
214
|
+
function ProxyManagerPanel(props) {
|
|
215
|
+
var scope = props.scope;
|
|
216
|
+
var settings = useProxySnapshot(scope);
|
|
217
|
+
// Re-fetch the host snapshot when the configuration changes (enabled /
|
|
218
|
+
// mode / customUrl / noProxy are the only inputs that move the effKey);
|
|
219
|
+
// no timed polling, mount + change-driven only (§17.3d).
|
|
220
|
+
var valueKey = settings.ready && settings.value
|
|
221
|
+
? JSON.stringify([
|
|
222
|
+
!!settings.value.enabled,
|
|
223
|
+
settings.value.mode || "",
|
|
224
|
+
settings.value.customUrl || "",
|
|
225
|
+
settings.value.noProxy || "",
|
|
226
|
+
])
|
|
227
|
+
: "pending";
|
|
228
|
+
var runtime = useStatusOnce(valueKey);
|
|
229
|
+
var diagState = useState("");
|
|
230
|
+
var diagUrl = diagState[0];
|
|
231
|
+
var setDiagUrl = diagState[1];
|
|
232
|
+
var diagBusy = useState(false);
|
|
233
|
+
var setDiagBusy = diagBusy[1];
|
|
234
|
+
var diagResult = useState(null);
|
|
235
|
+
var setDiagResult = diagResult[1];
|
|
236
|
+
|
|
237
|
+
if (!settings.ready) {
|
|
238
|
+
return react.createElement("div", { className: "dsxprx", "data-proxy-settings": true },
|
|
239
|
+
react.createElement("p", { className: "dsxprx-hint" }, "代理设置加载中…"));
|
|
240
|
+
}
|
|
241
|
+
var value = settings.value || { enabled: false, mode: "system", customUrl: "", noProxy: "" };
|
|
242
|
+
var writable = settings.writable;
|
|
243
|
+
var status = runtime.status;
|
|
244
|
+
// The status line follows the SWITCH (configuration-driven): turning
|
|
245
|
+
// the toggle off must immediately read 「代理未启用」. The host snapshot
|
|
246
|
+
// only supplements the effective URL/source and surfaces a mismatch when
|
|
247
|
+
// the switch is on but the proxy did not take effect (LESSONS §17.3c).
|
|
248
|
+
var cfgActive = value.enabled === true && value.mode !== "none";
|
|
249
|
+
var active = cfgActive;
|
|
250
|
+
var modeNow = value.mode || "system";
|
|
251
|
+
// Address is configuration-driven: custom mode shows the custom URL
|
|
252
|
+
// immediately (no stale host snapshot), system mode shows the URL the
|
|
253
|
+
// host read from Windows.
|
|
254
|
+
var shownUrl = modeNow === "custom"
|
|
255
|
+
? (value.customUrl || "—")
|
|
256
|
+
: ((status && status.url) || "—");
|
|
257
|
+
var statusLine = cfgActive
|
|
258
|
+
? "代理生效中 · " + shownUrl
|
|
259
|
+
: "代理未启用";
|
|
260
|
+
var subLine = "";
|
|
261
|
+
if (cfgActive) {
|
|
262
|
+
if (modeNow === "custom") {
|
|
263
|
+
subLine = "自定义地址(立即生效)" + (status && status.url && status.url !== value.customUrl ? " · 已应用: " + status.url : "");
|
|
264
|
+
} else if (status !== null && status !== undefined) {
|
|
265
|
+
subLine = status.active === true
|
|
266
|
+
? "跟随 Windows 系统代理(自动刷新)"
|
|
267
|
+
: "实际未生效 · " + (status.reason || "未知原因") + "。可检查下面测试或系统代理设置。";
|
|
268
|
+
} else if (runtime.error !== "") {
|
|
269
|
+
subLine = "无法读取 host 状态(" + runtime.error + ")。";
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
var effectiveNoProxy = status && status.noProxy
|
|
273
|
+
? status.noProxy
|
|
274
|
+
: (value.noProxy || "").trim();
|
|
275
|
+
// "含系统 ProxyOverride 并入" only makes sense in system mode with the
|
|
276
|
+
// proxy ACTUALLY active: custom mode keeps its own list and a disabled/
|
|
277
|
+
// unmatched state returns only the always-local entries — comparing raw
|
|
278
|
+
// strings alone mislabeled those as a system merge (review fix, 2026-09-17).
|
|
279
|
+
var systemMerged = modeNow === "system"
|
|
280
|
+
&& status !== null && status !== undefined && status.active === true && status.noProxy
|
|
281
|
+
&& status.noProxy !== (value.noProxy || "").trim();
|
|
282
|
+
|
|
283
|
+
function setField(field) {
|
|
284
|
+
return function (next) {
|
|
285
|
+
scope.set(field, next).catch(function (error) {
|
|
286
|
+
console.error("[dsh-proxy-pro] save failed:", error);
|
|
287
|
+
});
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
var diagUseProxy = useState(true);
|
|
291
|
+
var setDiagUseProxy = diagUseProxy[1];
|
|
292
|
+
function runDiag() {
|
|
293
|
+
var target = diagUrl.trim();
|
|
294
|
+
if (!target) return;
|
|
295
|
+
setDiagBusy(true);
|
|
296
|
+
setDiagResult(null);
|
|
297
|
+
fetchJson("/route?url=" + encodeURIComponent(target) + "&useProxy=" + (diagUseProxy[0] ? "1" : "0")).then(function (data) {
|
|
298
|
+
setDiagResult(data === null ? { error: "诊断接口不可用" } : data);
|
|
299
|
+
}).finally(function () { setDiagBusy(false); });
|
|
300
|
+
}
|
|
301
|
+
var diag = diagResult[0];
|
|
302
|
+
|
|
303
|
+
return react.createElement("div", { className: "dsxprx", "data-proxy-settings": true },
|
|
304
|
+
|
|
305
|
+
// --- title: 代理配置 ---
|
|
306
|
+
react.createElement("div", { className: "dsxprx-title" }, "代理配置"),
|
|
307
|
+
|
|
308
|
+
// --- status line first, with a compact enable switch on its right ---
|
|
309
|
+
react.createElement("div", { className: "dsxprx-status", "data-active": active ? "true" : "false" },
|
|
310
|
+
react.createElement("span", { className: "dsxprx-dot" }),
|
|
311
|
+
react.createElement("div", { className: "dsxprx-statusbody" },
|
|
312
|
+
react.createElement("span", { className: "dsxprx-statusline" }, statusLine),
|
|
313
|
+
subLine ? react.createElement("span", { className: "dsxprx-subline" }, subLine) : null),
|
|
314
|
+
react.createElement("label", { className: "dsxprx-switchwrap", "data-disabled": writable ? undefined : "true", title: "启用代理" },
|
|
315
|
+
react.createElement("span", { className: "dsxprx-switch dsxprx-switch-sm", "data-on": value.enabled === true ? "true" : "false" }),
|
|
316
|
+
react.createElement("input", {
|
|
317
|
+
type: "checkbox",
|
|
318
|
+
checked: value.enabled === true,
|
|
319
|
+
disabled: !writable,
|
|
320
|
+
onChange: function (event) { setField("enabled")(event.target.checked); },
|
|
321
|
+
}))
|
|
322
|
+
),
|
|
323
|
+
|
|
324
|
+
// --- mode ---
|
|
325
|
+
react.createElement("div", { className: "dsxprx-field" },
|
|
326
|
+
react.createElement("label", { htmlFor: "dsxprx-mode" }, "代理地址来源"),
|
|
327
|
+
react.createElement("div", { className: "dsxprx-radios" },
|
|
328
|
+
[["system", "系统代理(Windows)"], ["custom", "自定义地址"]].map(function (entry) {
|
|
329
|
+
var modeValue = entry[0];
|
|
330
|
+
var modeLabel = entry[1];
|
|
331
|
+
var on = (value.mode || "system") === modeValue;
|
|
332
|
+
return react.createElement("label", { key: modeValue, className: "dsxprx-radio", "data-on": on ? "true" : "false", title: modeValue === "system" ? "读取 Windows ·Internet 设置· 中的代理" : "使用下方自定义地址" },
|
|
333
|
+
react.createElement("input", {
|
|
334
|
+
type: "radio",
|
|
335
|
+
name: "dsxprx-mode",
|
|
336
|
+
checked: on,
|
|
337
|
+
disabled: !writable,
|
|
338
|
+
onChange: function () { setField("mode")(modeValue); },
|
|
339
|
+
}),
|
|
340
|
+
modeLabel);
|
|
341
|
+
})
|
|
342
|
+
),
|
|
343
|
+
react.createElement("span", { className: "dsxprx-hint" }, "关闭代理请用上面的「启用代理」开关。")
|
|
344
|
+
),
|
|
345
|
+
|
|
346
|
+
// --- custom url ---
|
|
347
|
+
(value.mode || "system") === "custom"
|
|
348
|
+
? react.createElement("div", { className: "dsxprx-field" },
|
|
349
|
+
react.createElement("label", { htmlFor: "dsxprx-url" }, "自定义代理地址"),
|
|
350
|
+
react.createElement(DraftInput, {
|
|
351
|
+
value: value.customUrl || "",
|
|
352
|
+
disabled: !writable,
|
|
353
|
+
placeholder: "http://127.0.0.1:7890",
|
|
354
|
+
onCommit: setField("customUrl"),
|
|
355
|
+
}),
|
|
356
|
+
react.createElement("span", { className: "dsxprx-hint" }, "支持 http:// 或 https:// 开头的地址。")
|
|
357
|
+
)
|
|
358
|
+
: null,
|
|
359
|
+
|
|
360
|
+
// --- no_proxy ---
|
|
361
|
+
react.createElement("div", { className: "dsxprx-field" },
|
|
362
|
+
react.createElement("label", { htmlFor: "dsxprx-noproxy" }, "直连名单(NO_PROXY)"),
|
|
363
|
+
react.createElement(DraftInput, {
|
|
364
|
+
value: value.noProxy || "",
|
|
365
|
+
disabled: !writable,
|
|
366
|
+
placeholder: "localhost,127.0.0.1,::1",
|
|
367
|
+
onCommit: setField("noProxy"),
|
|
368
|
+
}),
|
|
369
|
+
react.createElement("span", { className: "dsxprx-hint" }, "逗号分隔的主机名;localhost / 127.0.0.1 / ::1 始终直连。"),
|
|
370
|
+
effectiveNoProxy
|
|
371
|
+
? react.createElement("span", { className: "dsxprx-subline" },
|
|
372
|
+
(systemMerged ? "当前生效(含 Windows 系统 ProxyOverride 并入):" : "当前生效:") + effectiveNoProxy)
|
|
373
|
+
: null
|
|
374
|
+
),
|
|
375
|
+
|
|
376
|
+
// --- diagnostics ---
|
|
377
|
+
react.createElement("div", { className: "dsxprx-diag" },
|
|
378
|
+
react.createElement("label", { htmlFor: "dsxprx-diagurl" }, "路由诊断"),
|
|
379
|
+
react.createElement("div", { className: "dsxprx-diagbar" },
|
|
380
|
+
react.createElement("input", {
|
|
381
|
+
id: "dsxprx-diagurl",
|
|
382
|
+
type: "text",
|
|
383
|
+
value: diagUrl,
|
|
384
|
+
placeholder: "https://github.com/foo/bar",
|
|
385
|
+
onChange: function (event) { setDiagUrl(event.target.value); },
|
|
386
|
+
onKeyDown: function (event) { if (event.key === "Enter") runDiag(); },
|
|
387
|
+
}),
|
|
388
|
+
react.createElement("label", { className: "dsxprx-diagcheck" },
|
|
389
|
+
react.createElement("input", {
|
|
390
|
+
type: "checkbox",
|
|
391
|
+
checked: diagUseProxy[0],
|
|
392
|
+
onChange: function (event) { setDiagUseProxy(event.target.checked); },
|
|
393
|
+
}),
|
|
394
|
+
"使用代理"),
|
|
395
|
+
react.createElement("button", { type: "button", className: "dsxprx-diagbtn", onClick: runDiag, disabled: diagBusy[0] || !diagUrl.trim() }, "测试路由")
|
|
396
|
+
),
|
|
397
|
+
react.createElement("span", { className: "dsxprx-hint" }, "勾选「使用代理」= 强制走代理测通不通;不勾 = 强制直连测。对比可判断该域名该不该加进 NO_PROXY。"),
|
|
398
|
+
diag !== null
|
|
399
|
+
? react.createElement("div", { className: "dsxprx-diagout" },
|
|
400
|
+
diag.error
|
|
401
|
+
? react.createElement("span", null, "诊断失败:" + diag.error)
|
|
402
|
+
: [
|
|
403
|
+
react.createElement("span", { key: "c" }, "测试通道: " + (diag.channel === "proxy" ? "经代理(强制)" : diag.channel === "direct" ? "直连(强制绕过)" : "按当前策略")),
|
|
404
|
+
react.createElement("span", { key: "r" }, "策略判定: " + diag.route + (diag.bypassed ? "(NO_PROXY 命中)" : "")),
|
|
405
|
+
react.createElement("span", { key: "p" }, "连通探测: " + (diag.probe && diag.probe.ok ? "可达(HTTP " + diag.probe.status + ")" : (diag.probe && diag.probe.short) || "未知")),
|
|
406
|
+
diag.hint ? react.createElement("span", { key: "h" }, "提示: " + diag.hint) : null,
|
|
407
|
+
]
|
|
408
|
+
)
|
|
409
|
+
: null
|
|
410
|
+
),
|
|
411
|
+
|
|
412
|
+
react.createElement("span", { className: "dsxprx-hint" }, "对话中的模型会实时感知代理状态(系统提示中标注),并可用 proxy_status / proxy_set / proxy_test / proxy_config 查询或临时切换。")
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Browser plugin body: bind the `proxy` settings scope, then register the
|
|
418
|
+
* conversation header button and the 代理配置 settings section.
|
|
419
|
+
* @param ctx - client cordis context.
|
|
420
|
+
*/
|
|
421
|
+
function apply(ctx) {
|
|
422
|
+
var scope = ctx.settingsScope.bind({ namespace: "proxy" });
|
|
423
|
+
ctx.slots.inject("settings.section", function () {
|
|
424
|
+
return ctx.slots.register({
|
|
425
|
+
name: "settings.section",
|
|
426
|
+
id: "dsh-proxy-pro",
|
|
427
|
+
order: 20,
|
|
428
|
+
label: "代理配置",
|
|
429
|
+
}, function Section() {
|
|
430
|
+
return react.createElement(ProxyManagerPanel, { scope: scope });
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
ctx.slots.inject("conversation.session.header.utilities", function () {
|
|
434
|
+
return ctx.slots.register({
|
|
435
|
+
name: "conversation.session.header.utilities",
|
|
436
|
+
id: "dsh-proxy-pro",
|
|
437
|
+
order: 10,
|
|
438
|
+
label: "代理",
|
|
439
|
+
}, function Header() {
|
|
440
|
+
return react.createElement(ProxyHeaderButton, { scope: scope });
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
exports.name = "dsh-proxy-pro-ui";
|
|
446
|
+
exports.apply = apply;
|
|
447
|
+
// Cordis service names the browser loader must wait for — NOT package
|
|
448
|
+
// names (those belong in package.json's dsh.client.inject). Writing
|
|
449
|
+
// package names here keeps the plugin permanently pending and blocks
|
|
450
|
+
// web boot (see docs/LESSONS.md §6).
|
|
451
|
+
exports.inject = [
|
|
452
|
+
"slots",
|
|
453
|
+
"settingsScope",
|
|
454
|
+
];
|
|
455
|
+
return module.exports;
|
|
456
|
+
},
|
|
457
|
+
});
|