@karoc/dsh-proxy 0.1.3 → 0.1.18
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/CHANGELOG.md +495 -0
- package/CONTRIBUTING.md +40 -8
- package/README.md +142 -26
- package/README.zh.md +77 -16
- package/cordis.patch.yml +2 -1
- package/lib/client.js +297 -58
- package/lib/index.js +696 -77
- package/package.json +50 -13
package/lib/client.js
CHANGED
|
@@ -7,6 +7,28 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
//#region src/host-match.ts
|
|
11
|
+
/**
|
|
12
|
+
* Normalize a host so ONE comparison covers every spelling it arrives in.
|
|
13
|
+
* @param host - hostname, possibly bracketed (`[::1]`) or IPv4-mapped.
|
|
14
|
+
* @returns the comparable form (brackets stripped, IPv4-mapped collapsed).
|
|
15
|
+
*/
|
|
16
|
+
function normalizeHostForMatch(host) {
|
|
17
|
+
const bare = String(host ?? "").trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
18
|
+
const dotted = bare.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
|
|
19
|
+
if (dotted !== null) return dotted[1];
|
|
20
|
+
const mapped = bare.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
|
|
21
|
+
if (mapped === null) return bare;
|
|
22
|
+
const hi = Number.parseInt(mapped[1], 16);
|
|
23
|
+
const lo = Number.parseInt(mapped[2], 16);
|
|
24
|
+
return [
|
|
25
|
+
hi >> 8,
|
|
26
|
+
hi & 255,
|
|
27
|
+
lo >> 8,
|
|
28
|
+
lo & 255
|
|
29
|
+
].join(".");
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
10
32
|
//#region src/client/ProxySection.tsx
|
|
11
33
|
/**
|
|
12
34
|
* The "Smoothly Proxy" settings section (external plugin).
|
|
@@ -16,11 +38,11 @@ window.__ModuleLoader__.load({
|
|
|
16
38
|
* host) through an optional upstream proxy, per host. The host half runs the
|
|
17
39
|
* proxy and serves /proxy/api; this page reads and writes it with plain
|
|
18
40
|
* same-origin fetch (no settingsScope — the proxy config lives in
|
|
19
|
-
* <DSH_HOME>/proxy.json, not in dsh settings
|
|
41
|
+
* <DSH_HOME>/proxy.json, not in the dsh settings document).
|
|
20
42
|
*
|
|
21
43
|
* UI mirrors the dsh-desktop settings window: upstream card (enabled /
|
|
22
44
|
* protocol selector / host / port / username / password / test connection),
|
|
23
|
-
* a "model providers" list derived from
|
|
45
|
+
* a "model providers" list derived from the LIVE settings document, and an "other
|
|
24
46
|
* observed hosts" list, each with a per-host checkbox. A search box filters
|
|
25
47
|
* both lists. Saving writes the config; the running proxy re-reads proxy.json
|
|
26
48
|
* on every request, so changes take effect immediately.
|
|
@@ -28,15 +50,15 @@ window.__ModuleLoader__.load({
|
|
|
28
50
|
const PROTOCOLS = [
|
|
29
51
|
{
|
|
30
52
|
id: "http",
|
|
31
|
-
|
|
53
|
+
labelKey: "protocolHttp"
|
|
32
54
|
},
|
|
33
55
|
{
|
|
34
56
|
id: "https",
|
|
35
|
-
|
|
57
|
+
labelKey: "protocolHttps"
|
|
36
58
|
},
|
|
37
59
|
{
|
|
38
60
|
id: "socks5",
|
|
39
|
-
|
|
61
|
+
labelKey: "protocolSocks5"
|
|
40
62
|
}
|
|
41
63
|
];
|
|
42
64
|
/** Fetch helper bound to the same-origin /proxy/api endpoint. */
|
|
@@ -58,7 +80,7 @@ window.__ModuleLoader__.load({
|
|
|
58
80
|
},
|
|
59
81
|
items: PROTOCOLS.map((o) => ({
|
|
60
82
|
id: o.id,
|
|
61
|
-
label: o.
|
|
83
|
+
label: t(o.labelKey)
|
|
62
84
|
})),
|
|
63
85
|
selectedId: value,
|
|
64
86
|
onSelect: (id) => {
|
|
@@ -76,13 +98,36 @@ window.__ModuleLoader__.load({
|
|
|
76
98
|
onClick: () => {
|
|
77
99
|
setOpen((v) => !v);
|
|
78
100
|
},
|
|
79
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: matched?.
|
|
101
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(matched?.labelKey ?? "protocolHttp") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})]
|
|
80
102
|
})
|
|
81
103
|
});
|
|
82
104
|
}
|
|
105
|
+
/** The routing rule: which way the checkboxes read. */
|
|
106
|
+
function RouteModeSelector(props) {
|
|
107
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
108
|
+
className: "dsh-proxy-segmented",
|
|
109
|
+
role: "group",
|
|
110
|
+
"aria-label": props.t("modeLabel"),
|
|
111
|
+
children: [{
|
|
112
|
+
id: "only-listed",
|
|
113
|
+
labelKey: "modeOnlyListed"
|
|
114
|
+
}, {
|
|
115
|
+
id: "except-listed",
|
|
116
|
+
labelKey: "modeExceptListed"
|
|
117
|
+
}].map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
118
|
+
type: "button",
|
|
119
|
+
className: o.id === props.value ? "is-active" : "",
|
|
120
|
+
"aria-pressed": o.id === props.value,
|
|
121
|
+
onClick: () => {
|
|
122
|
+
props.onChange(o.id);
|
|
123
|
+
},
|
|
124
|
+
children: props.t(o.labelKey)
|
|
125
|
+
}, o.id))
|
|
126
|
+
});
|
|
127
|
+
}
|
|
83
128
|
/** One host checkbox row (label + route status). */
|
|
84
129
|
function HostRow(props) {
|
|
85
|
-
const { host, label, checked,
|
|
130
|
+
const { host, label, checked, routed, onToggle, t } = props;
|
|
86
131
|
const labelRef = (0, react.useRef)(null);
|
|
87
132
|
const [overflowing, setOverflowing] = (0, react.useState)(false);
|
|
88
133
|
(0, react.useLayoutEffect)(() => {
|
|
@@ -126,7 +171,7 @@ window.__ModuleLoader__.load({
|
|
|
126
171
|
}) : labelNode,
|
|
127
172
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
128
173
|
className: "dsh-proxy-host-route",
|
|
129
|
-
children:
|
|
174
|
+
children: routed ? t("viaUpstream") : t("direct")
|
|
130
175
|
})
|
|
131
176
|
]
|
|
132
177
|
});
|
|
@@ -146,6 +191,7 @@ window.__ModuleLoader__.load({
|
|
|
146
191
|
const { t } = props;
|
|
147
192
|
const [view, setView] = (0, react.useState)(void 0);
|
|
148
193
|
const [error, setError] = (0, react.useState)(void 0);
|
|
194
|
+
const [loadError, setLoadError] = (0, react.useState)(void 0);
|
|
149
195
|
const [saved, setSaved] = (0, react.useState)(false);
|
|
150
196
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
151
197
|
const [testing, setTesting] = (0, react.useState)(false);
|
|
@@ -157,6 +203,10 @@ window.__ModuleLoader__.load({
|
|
|
157
203
|
const [port, setPort] = (0, react.useState)("");
|
|
158
204
|
const [username, setUsername] = (0, react.useState)("");
|
|
159
205
|
const [password, setPassword] = (0, react.useState)("");
|
|
206
|
+
const [listenPort, setListenPort] = (0, react.useState)("");
|
|
207
|
+
const [routeMode, setRouteMode] = (0, react.useState)("only-listed");
|
|
208
|
+
const [advancedOpen, setAdvancedOpen] = (0, react.useState)(void 0);
|
|
209
|
+
const [adoptedPort, setAdoptedPort] = (0, react.useState)(false);
|
|
160
210
|
const [proxied, setProxied] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
161
211
|
/** Load the config view from /proxy/api. */
|
|
162
212
|
const load = (0, react.useCallback)(async () => {
|
|
@@ -164,15 +214,19 @@ window.__ModuleLoader__.load({
|
|
|
164
214
|
const data = await proxyApi();
|
|
165
215
|
setView(data);
|
|
166
216
|
setError(void 0);
|
|
217
|
+
setLoadError(void 0);
|
|
167
218
|
setEnabled(data.upstream.enabled);
|
|
168
219
|
setProtocol(data.upstream.protocol);
|
|
169
220
|
setHost(data.upstream.host);
|
|
170
221
|
setPort(data.upstream.port > 0 ? String(data.upstream.port) : "");
|
|
171
222
|
setUsername(data.upstream.username);
|
|
172
223
|
setPassword(data.upstream.password);
|
|
224
|
+
setListenPort(data.listenPort > 0 ? String(data.listenPort) : "");
|
|
225
|
+
setAdoptedPort(data.listenPort === 0 && data.portRequested > 0);
|
|
226
|
+
setRouteMode(data.routeMode ?? "only-listed");
|
|
173
227
|
setProxied(new Set(data.proxiedHosts));
|
|
174
228
|
} catch (err) {
|
|
175
|
-
|
|
229
|
+
setLoadError(err instanceof Error ? err.message : String(err));
|
|
176
230
|
}
|
|
177
231
|
}, []);
|
|
178
232
|
(0, react.useEffect)(() => {
|
|
@@ -231,7 +285,9 @@ window.__ModuleLoader__.load({
|
|
|
231
285
|
username: username.trim(),
|
|
232
286
|
password
|
|
233
287
|
},
|
|
234
|
-
proxiedHosts: [...proxied]
|
|
288
|
+
proxiedHosts: [...proxied],
|
|
289
|
+
routeMode,
|
|
290
|
+
listenPort: Number(listenPort) || 0
|
|
235
291
|
})
|
|
236
292
|
})).ok !== true) throw new Error("save rejected");
|
|
237
293
|
setSaved(true);
|
|
@@ -242,6 +298,19 @@ window.__ModuleLoader__.load({
|
|
|
242
298
|
setSaving(false);
|
|
243
299
|
}
|
|
244
300
|
};
|
|
301
|
+
/** Drop the observed-host list (live set + proxy.json) and refresh the view. */
|
|
302
|
+
const clearObserved = async () => {
|
|
303
|
+
try {
|
|
304
|
+
await proxyApi({
|
|
305
|
+
method: "POST",
|
|
306
|
+
headers: { "content-type": "application/json" },
|
|
307
|
+
body: JSON.stringify({ op: "forget" })
|
|
308
|
+
});
|
|
309
|
+
await load();
|
|
310
|
+
} catch (err) {
|
|
311
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
312
|
+
}
|
|
313
|
+
};
|
|
245
314
|
const test = async () => {
|
|
246
315
|
setTesting(true);
|
|
247
316
|
setTestResult(void 0);
|
|
@@ -274,8 +343,17 @@ window.__ModuleLoader__.load({
|
|
|
274
343
|
setTesting(false);
|
|
275
344
|
}
|
|
276
345
|
};
|
|
346
|
+
const selfLoop = view !== void 0 && view.upstream.enabled && /^(127\.\d{1,3}\.\d{1,3}\.\d{1,3}|localhost|::1)$/i.test(view.upstream.host.trim()) && view.upstream.port === view.port;
|
|
347
|
+
const upstreamUsable = view?.upstream.enabled === true && !selfLoop;
|
|
348
|
+
const routedHosts = new Set([...proxied].map((h) => normalizeHostForMatch(h)));
|
|
349
|
+
const isRouted = (host) => upstreamUsable && (routeMode === "except-listed" ? !routedHosts.has(normalizeHostForMatch(host)) : routedHosts.has(normalizeHostForMatch(host)));
|
|
277
350
|
const providerFiltered = providerHosts.filter((e) => match(e.label));
|
|
278
351
|
const otherFiltered = otherHosts.filter((e) => match(e.label));
|
|
352
|
+
const launchPort = (view?.listenPort ?? 0) > 0 ? view.listenPort : view?.port ?? 0;
|
|
353
|
+
const upstreamIsLoopback = /^(127\.\d{1,3}\.\d{1,3}\.\d{1,3}|localhost|::1)$/i.test((view?.upstream.host ?? "").trim());
|
|
354
|
+
const portConflictsUpstream = view !== void 0 && view.listenPort > 0 && upstreamIsLoopback && view.upstream.port === view.listenPort;
|
|
355
|
+
const diagnosticsNeedAttention = view !== void 0 && (portConflictsUpstream || view.egressStatus === void 0 || view.egressStatus !== "self-installed" || !view.portHonored && view.portRequested > 0 || view.listenPort > 0 && view.listenPort !== view.portRequested);
|
|
356
|
+
const diagnosticsOpen = advancedOpen ?? diagnosticsNeedAttention;
|
|
279
357
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
280
358
|
className: "dsh-proxy-section",
|
|
281
359
|
children: [
|
|
@@ -366,11 +444,98 @@ window.__ModuleLoader__.load({
|
|
|
366
444
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
367
445
|
className: "dsh-proxy-group",
|
|
368
446
|
children: [
|
|
369
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("
|
|
370
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("
|
|
447
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("egressTitle") }),
|
|
448
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("egressHint") }),
|
|
449
|
+
view === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
450
|
+
className: "dsh-proxy-egress",
|
|
451
|
+
children: [
|
|
452
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
453
|
+
className: `dsh-proxy-note ${view.egressStatus === void 0 ? "" : view.inProcessRouted ? "dsh-proxy-note-ok" : "dsh-proxy-note-warn"}`,
|
|
454
|
+
children: view.egressStatus === void 0 ? t("egressUnknown") : (view.inProcessRouted ? view.egressStatus === "launcher" ? t("egressRoutedLauncher") : t("egressRoutedSelf") : view.egressStatus === "launcher" ? t("egressLauncherOther") : t("egressUnavailable")).replace("{port}", String(view.port ?? "")).replace("{proxy}", String(view.launchProxy ?? "")).replace("{detail}", String(view.egressDetail ?? ""))
|
|
455
|
+
}),
|
|
456
|
+
!view.portHonored && view.portRequested > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
457
|
+
className: "dsh-proxy-note dsh-proxy-note-warn",
|
|
458
|
+
children: t("portFallback").replace("{requested}", String(view.portRequested)).replace("{bound}", String(view.port ?? "?"))
|
|
459
|
+
}),
|
|
460
|
+
portConflictsUpstream && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
461
|
+
className: "dsh-proxy-note dsh-proxy-note-warn",
|
|
462
|
+
children: t("portConflictsUpstream").replace(/\{port\}/g, String(view?.listenPort ?? ""))
|
|
463
|
+
}),
|
|
464
|
+
view.listenPort > 0 && view.listenPort !== view.portRequested && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
465
|
+
className: "dsh-proxy-note dsh-proxy-note-warn",
|
|
466
|
+
children: t("portRestartPending").replace("{listenPort}", String(view.listenPort)).replace("{port}", String(view.port ?? "?"))
|
|
467
|
+
}),
|
|
468
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
469
|
+
type: "button",
|
|
470
|
+
className: "dsh-proxy-disclosure",
|
|
471
|
+
"aria-expanded": diagnosticsOpen,
|
|
472
|
+
onClick: () => {
|
|
473
|
+
setAdvancedOpen(!diagnosticsOpen);
|
|
474
|
+
},
|
|
475
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {}), t("advancedToggle")]
|
|
476
|
+
}),
|
|
477
|
+
diagnosticsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
478
|
+
className: "dsh-proxy-diag",
|
|
479
|
+
children: [
|
|
480
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
481
|
+
className: "dsh-proxy-note",
|
|
482
|
+
children: (adoptedPort ? t("listenAddressAdopted") : t("listenAddress")).replace("{port}", String(view.port ?? "?"))
|
|
483
|
+
}),
|
|
484
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("diagHint") }),
|
|
485
|
+
view.egressStatus === "unavailable" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("egressSnippetLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
486
|
+
className: "dsh-proxy-snippet",
|
|
487
|
+
children: `Environment="HTTP_PROXY=http://127.0.0.1:${launchPort}" "HTTPS_PROXY=http://127.0.0.1:${launchPort}" "ALL_PROXY=http://127.0.0.1:${launchPort}" "NO_PROXY=127.0.0.1,localhost,::1" "NODE_USE_ENV_PROXY=1"`
|
|
488
|
+
})] }),
|
|
489
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
490
|
+
className: "dsh-proxy-row",
|
|
491
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
492
|
+
className: "dsh-proxy-proto",
|
|
493
|
+
children: t("listenPortLabel")
|
|
494
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
495
|
+
className: "dsh-proxy-port",
|
|
496
|
+
placeholder: t("listenPortPlaceholder"),
|
|
497
|
+
value: listenPort,
|
|
498
|
+
onChange: (e) => {
|
|
499
|
+
setListenPort(e.target.value);
|
|
500
|
+
setSaved(false);
|
|
501
|
+
},
|
|
502
|
+
autoComplete: "off",
|
|
503
|
+
spellCheck: false
|
|
504
|
+
})]
|
|
505
|
+
}),
|
|
506
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("listenPortHint") })
|
|
507
|
+
]
|
|
508
|
+
})
|
|
509
|
+
]
|
|
510
|
+
})
|
|
511
|
+
]
|
|
512
|
+
}),
|
|
513
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
514
|
+
className: "dsh-proxy-group",
|
|
515
|
+
children: [
|
|
516
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
517
|
+
className: "dsh-proxy-row",
|
|
518
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
519
|
+
className: "dsh-proxy-proto",
|
|
520
|
+
children: t("modeLabel")
|
|
521
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteModeSelector, {
|
|
522
|
+
value: routeMode,
|
|
523
|
+
onChange: (v) => {
|
|
524
|
+
setRouteMode(v);
|
|
525
|
+
setSaved(false);
|
|
526
|
+
},
|
|
527
|
+
t
|
|
528
|
+
})]
|
|
529
|
+
}),
|
|
530
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: routeMode === "except-listed" ? t("directHostsTitle") : t("proxiedHostsTitle") }),
|
|
531
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: routeMode === "except-listed" ? t("directHostsHint") : t("proxiedHostsHint") }),
|
|
532
|
+
selfLoop && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
533
|
+
className: "dsh-proxy-note dsh-proxy-note-warn",
|
|
534
|
+
children: t("selfLoopWarning")
|
|
535
|
+
}),
|
|
371
536
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
372
537
|
className: "dsh-proxy-search",
|
|
373
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
538
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGlobeOutlineRegular, {}),
|
|
374
539
|
placeholder: t("searchPlaceholder"),
|
|
375
540
|
value: query,
|
|
376
541
|
onChange: (e) => {
|
|
@@ -394,17 +559,29 @@ window.__ModuleLoader__.load({
|
|
|
394
559
|
}) : providerFiltered.map((e) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HostRow, {
|
|
395
560
|
host: e.host,
|
|
396
561
|
label: e.label,
|
|
397
|
-
checked:
|
|
398
|
-
|
|
399
|
-
onToggle: toggleHost
|
|
562
|
+
checked: routedHosts.has(normalizeHostForMatch(e.host)),
|
|
563
|
+
routed: isRouted(e.host),
|
|
564
|
+
onToggle: toggleHost,
|
|
565
|
+
t
|
|
400
566
|
}, e.host))
|
|
401
567
|
}),
|
|
402
568
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
403
569
|
className: "dsh-proxy-heading",
|
|
404
|
-
children: [
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
570
|
+
children: [
|
|
571
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("othersHeading") }),
|
|
572
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
573
|
+
className: "dsh-proxy-count",
|
|
574
|
+
children: otherHosts.length
|
|
575
|
+
}),
|
|
576
|
+
otherHosts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
577
|
+
type: "button",
|
|
578
|
+
className: "dsh-proxy-disclosure",
|
|
579
|
+
onClick: () => {
|
|
580
|
+
clearObserved();
|
|
581
|
+
},
|
|
582
|
+
children: t("othersClear")
|
|
583
|
+
})
|
|
584
|
+
]
|
|
408
585
|
}),
|
|
409
586
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
410
587
|
className: "dsh-proxy-hostlist",
|
|
@@ -414,9 +591,10 @@ window.__ModuleLoader__.load({
|
|
|
414
591
|
}) : otherFiltered.map((e) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HostRow, {
|
|
415
592
|
host: e.host,
|
|
416
593
|
label: e.label,
|
|
417
|
-
checked:
|
|
418
|
-
|
|
419
|
-
onToggle: toggleHost
|
|
594
|
+
checked: routedHosts.has(normalizeHostForMatch(e.host)),
|
|
595
|
+
routed: isRouted(e.host),
|
|
596
|
+
onToggle: toggleHost,
|
|
597
|
+
t
|
|
420
598
|
}, e.host))
|
|
421
599
|
})
|
|
422
600
|
]
|
|
@@ -427,7 +605,7 @@ window.__ModuleLoader__.load({
|
|
|
427
605
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
428
606
|
variant: "outline",
|
|
429
607
|
size: "md",
|
|
430
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
608
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLinkOutlineRegular, {}),
|
|
431
609
|
disabled: testing || !enabled,
|
|
432
610
|
onClick: () => {
|
|
433
611
|
test();
|
|
@@ -461,6 +639,10 @@ window.__ModuleLoader__.load({
|
|
|
461
639
|
" ",
|
|
462
640
|
error
|
|
463
641
|
]
|
|
642
|
+
}),
|
|
643
|
+
loadError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
644
|
+
className: "dsh-proxy-status dsh-proxy-status-err",
|
|
645
|
+
children: /unauthorized|forbidden|40[13]/.test(loadError) ? t("sessionExpired") : `${t("loadFailed")} ${loadError}`
|
|
464
646
|
})
|
|
465
647
|
]
|
|
466
648
|
})
|
|
@@ -506,6 +688,20 @@ window.__ModuleLoader__.load({
|
|
|
506
688
|
.dsh-proxy-status { font-size: 12px; color: var(--dsw-alias-label-tertiary); }
|
|
507
689
|
.dsh-proxy-status-ok { color: var(--dsw-alias-state-success-primary, var(--dsw-alias-label-primary)); }
|
|
508
690
|
.dsh-proxy-status-err { color: var(--dsw-alias-state-error-primary); }
|
|
691
|
+
.dsh-proxy-egress { display: flex; flex-direction: column; gap: 8px; }
|
|
692
|
+
.dsh-proxy-note { padding: 8px 10px; border-radius: 10px; font-size: 12px; line-height: 1.5; border: 1px solid var(--dsw-alias-border-l3); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary); }
|
|
693
|
+
.dsh-proxy-note-ok { border-color: var(--dsw-alias-state-success-primary, var(--dsw-alias-border-l3)); color: var(--dsw-alias-state-success-primary, var(--dsw-alias-label-primary)); }
|
|
694
|
+
.dsh-proxy-note-warn { border-color: var(--dsw-alias-state-warning-primary, var(--dsw-alias-border-l3)); color: var(--dsw-alias-label-primary); }
|
|
695
|
+
.dsh-proxy-snippet { padding: 8px 10px; border-radius: 10px; background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-primary); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; line-height: 1.6; word-break: break-all; user-select: all; }
|
|
696
|
+
.dsh-proxy-port { width: 180px; }
|
|
697
|
+
.dsh-proxy-segmented { display: inline-flex; gap: 2px; padding: 2px; border-radius: 999px; background: var(--dsw-alias-bg-module-platform); }
|
|
698
|
+
.dsh-proxy-segmented button { padding: 4px 12px; border: none; border-radius: 999px; background: transparent; color: var(--dsw-alias-label-secondary); font-size: 12px; cursor: pointer; }
|
|
699
|
+
.dsh-proxy-segmented button:hover { color: var(--dsw-alias-label-primary); }
|
|
700
|
+
.dsh-proxy-segmented button.is-active { background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-on-primary, #fff); }
|
|
701
|
+
.dsh-proxy-disclosure { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start; padding: 4px 8px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary); font-size: 12px; cursor: pointer; }
|
|
702
|
+
.dsh-proxy-disclosure:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
703
|
+
.dsh-proxy-disclosure[aria-expanded="true"] svg { transform: rotate(180deg); }
|
|
704
|
+
.dsh-proxy-diag { display: flex; flex-direction: column; gap: 8px; padding: 10px; border-radius: 12px; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l3); }
|
|
509
705
|
.dsh-proxy-heading { display: flex; align-items: center; gap: 6px; margin: 0 0 8px; }
|
|
510
706
|
.dsh-proxy-heading h4 { margin: 0; font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); }
|
|
511
707
|
.dsh-proxy-heading .dsh-proxy-count { font-size: 11px; color: var(--dsw-alias-label-tertiary); }
|
|
@@ -529,84 +725,127 @@ window.__ModuleLoader__.load({
|
|
|
529
725
|
//#region src/client/locales.ts
|
|
530
726
|
/**
|
|
531
727
|
* Copy dictionaries for the Smoothly Proxy settings section
|
|
532
|
-
* (Smoothly Proxy / 思磨力代理插件). English is the key source (
|
|
533
|
-
*
|
|
728
|
+
* (Smoothly Proxy / 思磨力代理插件). English is the key source (zh must carry
|
|
729
|
+
* every key), and the copy rule for both is: plain words, short sentences, no
|
|
730
|
+
* restating what the screen already shows.
|
|
534
731
|
*/
|
|
535
732
|
const en = {
|
|
536
733
|
nav: "Smoothly Proxy",
|
|
537
|
-
title: "Smoothly Proxy",
|
|
538
|
-
subtitle: "Model-provider traffic through an optional upstream proxy, per host.",
|
|
539
734
|
upstreamTitle: "Upstream proxy (optional)",
|
|
540
|
-
upstreamEnabled: "
|
|
735
|
+
upstreamEnabled: "Send traffic through the upstream proxy",
|
|
541
736
|
protocolLabel: "Protocol",
|
|
542
737
|
protocolHttp: "HTTP",
|
|
543
|
-
protocolHttps: "HTTPS
|
|
738
|
+
protocolHttps: "HTTPS",
|
|
544
739
|
protocolSocks5: "SOCKS5",
|
|
545
|
-
protoHint: "
|
|
740
|
+
protoHint: "Mixed ports (e.g. 7890) are usually HTTP. SOCKS5 carries HTTPS targets only.",
|
|
546
741
|
hostPlaceholder: "Proxy host, e.g. 127.0.0.1",
|
|
547
742
|
portPlaceholder: "Port, e.g. 7890",
|
|
548
743
|
usernamePlaceholder: "Username (optional)",
|
|
549
744
|
passwordPlaceholder: "Password (optional)",
|
|
745
|
+
modeLabel: "Routing rule",
|
|
746
|
+
modeOnlyListed: "Only checked hosts",
|
|
747
|
+
modeExceptListed: "Everything except checked",
|
|
550
748
|
proxiedHostsTitle: "Hosts going through the proxy",
|
|
551
|
-
|
|
749
|
+
directHostsTitle: "Hosts staying direct",
|
|
750
|
+
proxiedHostsHint: "Checked hosts go upstream; the rest connect directly.",
|
|
751
|
+
directHostsHint: "Checked hosts connect directly; everything else goes upstream.",
|
|
552
752
|
searchPlaceholder: "Search host / provider…",
|
|
553
753
|
providersHeading: "Model providers",
|
|
554
|
-
providersEmpty: "No
|
|
754
|
+
providersEmpty: "No provider endpoints yet (loopback endpoints are never proxyable).",
|
|
555
755
|
othersHeading: "Other observed hosts",
|
|
556
|
-
othersEmpty: "
|
|
756
|
+
othersEmpty: "Hosts dsh contacts show up here.",
|
|
757
|
+
othersClear: "Clear",
|
|
758
|
+
loadFailed: "Could not load the proxy settings.",
|
|
759
|
+
sessionExpired: "This page’s session has expired — refresh the page and try again.",
|
|
557
760
|
save: "Save",
|
|
558
|
-
saveStatus: "Saved
|
|
761
|
+
saveStatus: "Saved.",
|
|
559
762
|
saving: "Saving…",
|
|
560
763
|
saveFailed: "Save failed:",
|
|
561
764
|
test: "Test connection",
|
|
562
765
|
testing: "Testing…",
|
|
563
|
-
testFailed: "Test failed:",
|
|
564
766
|
testStatusOk: "Connected",
|
|
565
767
|
testStatusFail: "Connection failed",
|
|
566
|
-
immediateHint: "
|
|
768
|
+
immediateHint: "Applies immediately; install/update hosts take effect on the next install or update.",
|
|
567
769
|
direct: "direct",
|
|
568
770
|
viaUpstream: "via upstream",
|
|
569
|
-
selfLoopWarning: "The upstream points
|
|
570
|
-
|
|
571
|
-
|
|
771
|
+
selfLoopWarning: "The upstream points at this plugin itself — ignored.",
|
|
772
|
+
egressTitle: "dsh's own requests",
|
|
773
|
+
egressHint: "Model calls and web fetch are routed here automatically. Nothing to configure.",
|
|
774
|
+
egressRoutedSelf: "Routing through this plugin.",
|
|
775
|
+
egressRoutedLauncher: "Routing through this plugin (named by the launch environment).",
|
|
776
|
+
egressLauncherOther: "The launch environment declares a different proxy ({proxy}), so it keeps handling these requests. Remove it, or point it here, to use this plugin instead.",
|
|
777
|
+
egressUnavailable: "Could not take over automatically ({detail}). Set the line below in the environment dsh starts with, then restart.",
|
|
778
|
+
egressUnknown: "This host half does not report egress state (page newer than plugin). Restart dsh to match.",
|
|
779
|
+
egressSnippetLabel: "Launch environment (systemd: the [Service] section)",
|
|
780
|
+
advancedToggle: "Diagnostics & advanced",
|
|
781
|
+
listenAddress: "Listening on 127.0.0.1:{port} — port picked automatically.",
|
|
782
|
+
listenAddressAdopted: "Listening on 127.0.0.1:{port} — taken from the launch environment.",
|
|
783
|
+
diagHint: "Point other tools here if you want to verify traffic; dsh itself needs no configuration.",
|
|
784
|
+
listenPortLabel: "Fixed listen port (optional — NOT the upstream address)",
|
|
785
|
+
listenPortPlaceholder: "e.g. 45999 (0 = auto)",
|
|
786
|
+
listenPortHint: "Only when a launcher must know this address in advance. The upstream address belongs in the card above.",
|
|
787
|
+
portFallback: "Port {requested} was taken — using {bound} instead. A launcher pointing at {requested} will not reach this plugin.",
|
|
788
|
+
portRestartPending: "Port changed to {listenPort}; restart dsh to apply (currently on {port}).",
|
|
789
|
+
portConflictsUpstream: "{port} is your upstream proxy’s port. That address belongs in the card above: here the plugin cannot bind it, and a launcher pointing at it cannot reach this plugin either."
|
|
572
790
|
};
|
|
573
791
|
const zh = {
|
|
574
792
|
nav: "思磨力代理",
|
|
575
|
-
title: "思磨力代理插件",
|
|
576
|
-
subtitle: "按主机把模型提供方流量转发到可选的上游代理。",
|
|
577
793
|
upstreamTitle: "上游代理(可选)",
|
|
578
|
-
upstreamEnabled: "
|
|
794
|
+
upstreamEnabled: "把流量交给上游代理",
|
|
579
795
|
protocolLabel: "协议",
|
|
580
796
|
protocolHttp: "HTTP",
|
|
581
|
-
protocolHttps: "HTTPS
|
|
797
|
+
protocolHttps: "HTTPS",
|
|
582
798
|
protocolSocks5: "SOCKS5",
|
|
583
|
-
protoHint: "
|
|
799
|
+
protoHint: "混合端口(如 7890)一般选 HTTP;SOCKS5 只承载 HTTPS 目标。",
|
|
584
800
|
hostPlaceholder: "代理主机,如 127.0.0.1",
|
|
585
801
|
portPlaceholder: "端口,如 7890",
|
|
586
802
|
usernamePlaceholder: "用户名(可选)",
|
|
587
803
|
passwordPlaceholder: "密码(可选)",
|
|
804
|
+
modeLabel: "代理规则",
|
|
805
|
+
modeOnlyListed: "只代理勾选的主机",
|
|
806
|
+
modeExceptListed: "除勾选外全部走代理",
|
|
588
807
|
proxiedHostsTitle: "走代理的主机",
|
|
589
|
-
|
|
808
|
+
directHostsTitle: "直连的主机",
|
|
809
|
+
proxiedHostsHint: "勾选的主机走上游,其余直连。",
|
|
810
|
+
directHostsHint: "勾选的主机直连,其余全部走上游。",
|
|
590
811
|
searchPlaceholder: "搜索主机 / 提供方名称…",
|
|
591
812
|
providersHeading: "模型提供方",
|
|
592
|
-
providersEmpty: "
|
|
813
|
+
providersEmpty: "还没有可代理的模型端点(loopback 端点永远不可代理)。",
|
|
593
814
|
othersHeading: "其它已观测主机",
|
|
594
|
-
othersEmpty: "
|
|
815
|
+
othersEmpty: "dsh 访问过的其它主机会出现在这里。",
|
|
816
|
+
othersClear: "清空",
|
|
817
|
+
loadFailed: "读取代理设置失败。",
|
|
818
|
+
sessionExpired: "本页面的登录会话已过期——刷新页面后重试。",
|
|
595
819
|
save: "保存",
|
|
596
|
-
saveStatus: "
|
|
820
|
+
saveStatus: "已保存。",
|
|
597
821
|
saving: "保存中…",
|
|
598
822
|
saveFailed: "保存失败:",
|
|
599
823
|
test: "测试连接",
|
|
600
824
|
testing: "测试中…",
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
immediateHint: "保存后立即生效,无需重启;改动「安装/更新」相关主机的代理会在下次安装/更新时生效。",
|
|
825
|
+
testStatusOk: "连接成功",
|
|
826
|
+
testStatusFail: "连接失败",
|
|
827
|
+
immediateHint: "立即生效;安装/更新类主机在下次安装或更新时生效。",
|
|
605
828
|
direct: "直连",
|
|
606
|
-
viaUpstream: "
|
|
607
|
-
selfLoopWarning: "
|
|
608
|
-
|
|
609
|
-
|
|
829
|
+
viaUpstream: "走上游",
|
|
830
|
+
selfLoopWarning: "上游指向本插件自己,已忽略。",
|
|
831
|
+
egressTitle: "dsh 自己的请求",
|
|
832
|
+
egressHint: "模型调用与 web fetch 已自动走本插件,无需配置。",
|
|
833
|
+
egressRoutedSelf: "已由本插件接管。",
|
|
834
|
+
egressRoutedLauncher: "已由本插件接管(启动环境指名)。",
|
|
835
|
+
egressLauncherOther: "启动环境声明了别的代理({proxy}),这些请求继续归它管。想让它们走本插件,删掉那个声明或把它指向本插件。",
|
|
836
|
+
egressUnavailable: "未能自动接管({detail})。把下面这行加进 dsh 的启动环境后重启。",
|
|
837
|
+
egressUnknown: "宿主半区还没上报出网状态(页面比插件新)。重启 dsh 让版本一致。",
|
|
838
|
+
egressSnippetLabel: "启动环境(systemd:[Service] 段)",
|
|
839
|
+
advancedToggle: "诊断与高级设置",
|
|
840
|
+
listenAddress: "正在监听 127.0.0.1:{port},端口自动选择。",
|
|
841
|
+
listenAddressAdopted: "正在监听 127.0.0.1:{port},取自启动环境。",
|
|
842
|
+
diagHint: "可以把其它工具指到这个地址验证流量;dsh 本身无需配置。",
|
|
843
|
+
listenPortLabel: "本插件监听端口(可选,不是上游地址)",
|
|
844
|
+
listenPortPlaceholder: "如 45999(0 = 自动)",
|
|
845
|
+
listenPortHint: "只有启动器必须提前知道这个地址时才填;上游地址在上面的卡片里。",
|
|
846
|
+
portFallback: "端口 {requested} 被占用,已改用 {bound}。启动环境若指向 {requested},到不了本插件。",
|
|
847
|
+
portRestartPending: "端口已改为 {listenPort},重启 dsh 后生效(当前仍在 {port})。",
|
|
848
|
+
portConflictsUpstream: "{port} 是你上游代理的端口,那个地址属于上面的卡片:填在这里插件绑不上,启动环境指向它也到不了本插件。"
|
|
610
849
|
};
|
|
611
850
|
//#endregion
|
|
612
851
|
//#region src/client/index.ts
|