dsh-plugin-model-proxy 0.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/LICENSE +21 -0
- package/README.md +157 -0
- package/cordis.patch.yml +15 -0
- package/lib/client/ModelProxyCard.d.ts +14 -0
- package/lib/client/ModelProxyCard.d.ts.map +1 -0
- package/lib/client/catalog.d.ts +140 -0
- package/lib/client/catalog.d.ts.map +1 -0
- package/lib/client/controller.d.ts +107 -0
- package/lib/client/controller.d.ts.map +1 -0
- package/lib/client/index.d.ts +8 -0
- package/lib/client/index.d.ts.map +1 -0
- package/lib/client/locales.d.ts +106 -0
- package/lib/client/locales.d.ts.map +1 -0
- package/lib/client.js +1129 -0
- package/lib/client.js.map +7 -0
- package/lib/host/config.d.ts +47 -0
- package/lib/host/config.d.ts.map +1 -0
- package/lib/host/config.js +153 -0
- package/lib/host/config.js.map +1 -0
- package/lib/host/credentials.d.ts +24 -0
- package/lib/host/credentials.d.ts.map +1 -0
- package/lib/host/credentials.js +45 -0
- package/lib/host/credentials.js.map +1 -0
- package/lib/host/dispatcher.d.ts +30 -0
- package/lib/host/dispatcher.d.ts.map +1 -0
- package/lib/host/dispatcher.js +167 -0
- package/lib/host/dispatcher.js.map +1 -0
- package/lib/host/fetch-wrap.d.ts +45 -0
- package/lib/host/fetch-wrap.d.ts.map +1 -0
- package/lib/host/fetch-wrap.js +100 -0
- package/lib/host/fetch-wrap.js.map +1 -0
- package/lib/host/index.d.ts +18 -0
- package/lib/host/index.d.ts.map +1 -0
- package/lib/host/index.js +262 -0
- package/lib/host/index.js.map +1 -0
- package/lib/host/probe.d.ts +30 -0
- package/lib/host/probe.d.ts.map +1 -0
- package/lib/host/probe.js +43 -0
- package/lib/host/probe.js.map +1 -0
- package/lib/invariant.d.ts +5 -0
- package/lib/invariant.d.ts.map +1 -0
- package/lib/invariant.js +7 -0
- package/lib/invariant.js.map +1 -0
- package/package.json +95 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1129 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "dsh-plugin-model-proxy", factory: (require) => {
|
|
2
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
3
|
+
"use strict";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
// src/client/index.ts
|
|
23
|
+
var index_exports = {};
|
|
24
|
+
__export(index_exports, {
|
|
25
|
+
apply: () => apply,
|
|
26
|
+
inject: () => inject
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/client/catalog.ts
|
|
31
|
+
var EMPTY = { status: "idle", providers: [], modelsByProvider: {} };
|
|
32
|
+
function asRecord(value) {
|
|
33
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
34
|
+
}
|
|
35
|
+
function unwrapRpc(response) {
|
|
36
|
+
const envelope = asRecord(response);
|
|
37
|
+
const result = envelope === void 0 ? void 0 : asRecord(envelope.result);
|
|
38
|
+
if (result === void 0) throw new Error("malformed RPC envelope");
|
|
39
|
+
if (result.ok !== true) {
|
|
40
|
+
const error = asRecord(result.error);
|
|
41
|
+
throw new Error(typeof error?.message === "string" && error.message !== "" ? error.message : "RPC request failed");
|
|
42
|
+
}
|
|
43
|
+
return result.value;
|
|
44
|
+
}
|
|
45
|
+
function pickProviders(value) {
|
|
46
|
+
const root = asRecord(value);
|
|
47
|
+
const rows = Array.isArray(root?.providers) ? root.providers : [];
|
|
48
|
+
const out = [];
|
|
49
|
+
for (const row of rows) {
|
|
50
|
+
const r = asRecord(row);
|
|
51
|
+
if (r === void 0 || typeof r.provider !== "string" || r.provider === "") continue;
|
|
52
|
+
const p = {
|
|
53
|
+
provider: r.provider,
|
|
54
|
+
displayName: typeof r.displayName === "string" && r.displayName !== "" ? r.displayName : r.provider,
|
|
55
|
+
active: r.active === true
|
|
56
|
+
};
|
|
57
|
+
if (typeof r.settingsNs === "string" && r.settingsNs !== "") {
|
|
58
|
+
p.settingsNs = r.settingsNs;
|
|
59
|
+
if (Array.isArray(r.settingsPath)) {
|
|
60
|
+
p.settingsPath = r.settingsPath.filter((k) => typeof k === "string");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (r.declared === true || r.declared === false) p.declared = r.declared;
|
|
64
|
+
out.push(p);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
function getPath(value, path) {
|
|
69
|
+
let cur = value;
|
|
70
|
+
for (const key of path) {
|
|
71
|
+
const rec = asRecord(cur);
|
|
72
|
+
if (rec === void 0) return void 0;
|
|
73
|
+
cur = rec[key];
|
|
74
|
+
}
|
|
75
|
+
return cur;
|
|
76
|
+
}
|
|
77
|
+
function attachConfigured(providers, namespaces) {
|
|
78
|
+
const byNs = /* @__PURE__ */ new Map();
|
|
79
|
+
for (const n of namespaces) {
|
|
80
|
+
if (typeof n.ns === "string" && !byNs.has(n.ns)) byNs.set(n.ns, n.value);
|
|
81
|
+
}
|
|
82
|
+
return providers.map((p) => {
|
|
83
|
+
if (p.settingsNs === void 0 || !byNs.has(p.settingsNs)) return p;
|
|
84
|
+
const value = byNs.get(p.settingsNs);
|
|
85
|
+
const path = p.settingsPath ?? [];
|
|
86
|
+
const configured = path.length === 0 ? value !== void 0 : getPath(value, path) !== void 0;
|
|
87
|
+
return { ...p, configured };
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function pickModelsByProvider(value) {
|
|
91
|
+
const root = asRecord(value);
|
|
92
|
+
const groups = Array.isArray(root?.groups) ? root.groups : [];
|
|
93
|
+
const map = {};
|
|
94
|
+
for (const group of groups) {
|
|
95
|
+
const g = asRecord(group);
|
|
96
|
+
if (g === void 0 || typeof g.id !== "string" || g.id === "" || !Array.isArray(g.models)) continue;
|
|
97
|
+
const models = [];
|
|
98
|
+
for (const row of g.models) {
|
|
99
|
+
const m = asRecord(row);
|
|
100
|
+
if (m === void 0 || typeof m.id !== "string" || m.id === "") continue;
|
|
101
|
+
models.push({ id: m.id, ...typeof m.name === "string" && m.name !== "" ? { name: m.name } : {} });
|
|
102
|
+
}
|
|
103
|
+
map[g.id] = models;
|
|
104
|
+
}
|
|
105
|
+
return map;
|
|
106
|
+
}
|
|
107
|
+
var ProviderCatalogStore = class {
|
|
108
|
+
constructor(api, remote) {
|
|
109
|
+
this.api = api;
|
|
110
|
+
this.remote = remote;
|
|
111
|
+
}
|
|
112
|
+
api;
|
|
113
|
+
remote;
|
|
114
|
+
listeners = /* @__PURE__ */ new Set();
|
|
115
|
+
snapshot = EMPTY;
|
|
116
|
+
generation = 0;
|
|
117
|
+
unsubs = [];
|
|
118
|
+
bound = false;
|
|
119
|
+
/** Idempotent: start loading and subscribe to refresh events. */
|
|
120
|
+
bind() {
|
|
121
|
+
if (this.bound) return;
|
|
122
|
+
this.bound = true;
|
|
123
|
+
if (this.remote !== void 0) {
|
|
124
|
+
this.unsubs.push(this.remote.$on("llm/adapters-updated", () => void this.load()));
|
|
125
|
+
this.unsubs.push(this.remote.$on("settings/document-updated", () => void this.load()));
|
|
126
|
+
}
|
|
127
|
+
void this.load();
|
|
128
|
+
}
|
|
129
|
+
/** Stop listening; in-flight loads become no-ops via the generation guard. */
|
|
130
|
+
dispose() {
|
|
131
|
+
this.bound = false;
|
|
132
|
+
for (const off of this.unsubs.splice(0)) {
|
|
133
|
+
try {
|
|
134
|
+
off();
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
this.generation++;
|
|
139
|
+
this.listeners.clear();
|
|
140
|
+
}
|
|
141
|
+
subscribe(listener) {
|
|
142
|
+
this.listeners.add(listener);
|
|
143
|
+
return () => {
|
|
144
|
+
this.listeners.delete(listener);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
getSnapshot() {
|
|
148
|
+
return this.snapshot;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Fetch both catalogs. Each call settles independently (`allSettled`
|
|
152
|
+
* semantics via mapped thunks): a provider-catalog failure fails the whole
|
|
153
|
+
* load, while a model-catalog failure degrades to an empty model map —
|
|
154
|
+
* models are an enrichment, providers drive the primary dropdown.
|
|
155
|
+
*/
|
|
156
|
+
async load() {
|
|
157
|
+
const generation = ++this.generation;
|
|
158
|
+
if (this.snapshot.status === "idle") this.set({ ...EMPTY, status: "loading" });
|
|
159
|
+
const settle = (promise) => promise.then(
|
|
160
|
+
(value) => ({ ok: true, value }),
|
|
161
|
+
() => ({ ok: false })
|
|
162
|
+
);
|
|
163
|
+
const [providersOutcome, modelsOutcome, settingsOutcome] = await Promise.all([
|
|
164
|
+
settle(this.api.llm.providers({})),
|
|
165
|
+
settle(this.api.llm.models({})),
|
|
166
|
+
// Enrichment only: a describe failure must not degrade the dropdowns.
|
|
167
|
+
settle(this.api.settings?.describe({}) ?? Promise.reject(new Error("no settings face")))
|
|
168
|
+
]);
|
|
169
|
+
if (generation !== this.generation) return;
|
|
170
|
+
let providers;
|
|
171
|
+
if (!providersOutcome.ok) {
|
|
172
|
+
this.markUnavailable();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
providers = pickProviders(unwrapRpc(providersOutcome.value));
|
|
177
|
+
} catch {
|
|
178
|
+
this.markUnavailable();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
let modelsByProvider = {};
|
|
182
|
+
if (modelsOutcome.ok) {
|
|
183
|
+
try {
|
|
184
|
+
modelsByProvider = pickModelsByProvider(unwrapRpc(modelsOutcome.value));
|
|
185
|
+
} catch {
|
|
186
|
+
modelsByProvider = {};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (settingsOutcome.ok) {
|
|
190
|
+
try {
|
|
191
|
+
const described = unwrapRpc(settingsOutcome.value);
|
|
192
|
+
const namespaces = Array.isArray(described?.namespaces) ? described.namespaces : [];
|
|
193
|
+
providers = attachConfigured(
|
|
194
|
+
providers,
|
|
195
|
+
namespaces.map((n) => {
|
|
196
|
+
const r = asRecord(n);
|
|
197
|
+
return { ns: r?.ns, value: r?.value };
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
this.set({ status: "ready", providers, modelsByProvider });
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Keep last good rows across a failed refresh; the card falls back to
|
|
207
|
+
* free-text inputs only when the snapshot holds no providers at all.
|
|
208
|
+
*/
|
|
209
|
+
markUnavailable() {
|
|
210
|
+
this.set({ ...this.snapshot, status: "unavailable" });
|
|
211
|
+
}
|
|
212
|
+
set(snapshot) {
|
|
213
|
+
this.snapshot = snapshot;
|
|
214
|
+
for (const listener of [...this.listeners]) listener();
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// src/client/ModelProxyCard.tsx
|
|
219
|
+
var import_react = require("react");
|
|
220
|
+
|
|
221
|
+
// src/client/controller.ts
|
|
222
|
+
var KNOWN_RULE_FIELDS = ["id", "provider", "model", "proxyUrl", "enabled", "purpose", "credentialRef"];
|
|
223
|
+
function sanitizeRules(rules) {
|
|
224
|
+
return rules.map((rule) => {
|
|
225
|
+
const out = {};
|
|
226
|
+
const dropped = [];
|
|
227
|
+
for (const [key, value] of Object.entries(rule)) {
|
|
228
|
+
if (KNOWN_RULE_FIELDS.includes(key)) out[key] = value;
|
|
229
|
+
else dropped.push(key);
|
|
230
|
+
}
|
|
231
|
+
if (dropped.length > 0) {
|
|
232
|
+
console.warn(`[model-proxy] dropping unknown rule field(s): ${dropped.join(", ")}`);
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function makeRuleId() {
|
|
238
|
+
return Math.random().toString(36).slice(2, 9);
|
|
239
|
+
}
|
|
240
|
+
function buildCreatorRules(spec) {
|
|
241
|
+
const provider = spec.provider.trim();
|
|
242
|
+
if (provider === "") return [];
|
|
243
|
+
const models = [...new Set(spec.models.map((m) => m.trim()).filter((m) => m !== ""))];
|
|
244
|
+
if (models.length === 0) return [];
|
|
245
|
+
const purpose = (spec.purpose ?? "").trim();
|
|
246
|
+
const credentialRef = (spec.credentialRef ?? "").trim();
|
|
247
|
+
return models.map((model) => ({
|
|
248
|
+
id: makeRuleId(),
|
|
249
|
+
provider,
|
|
250
|
+
model,
|
|
251
|
+
proxyUrl: spec.proxyUrl.trim(),
|
|
252
|
+
enabled: spec.enabled ?? true,
|
|
253
|
+
...purpose !== "" ? { purpose } : {},
|
|
254
|
+
...credentialRef !== "" ? { credentialRef } : {}
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
function groupByProvider(rules) {
|
|
258
|
+
const groups = [];
|
|
259
|
+
const index = /* @__PURE__ */ new Map();
|
|
260
|
+
for (const rule of rules) {
|
|
261
|
+
let gi = index.get(rule.provider);
|
|
262
|
+
if (gi === void 0) {
|
|
263
|
+
gi = groups.length;
|
|
264
|
+
index.set(rule.provider, gi);
|
|
265
|
+
groups.push({ provider: rule.provider, rules: [] });
|
|
266
|
+
}
|
|
267
|
+
groups[gi].rules.push(rule);
|
|
268
|
+
}
|
|
269
|
+
return groups;
|
|
270
|
+
}
|
|
271
|
+
function ensureRuleIds(cfg) {
|
|
272
|
+
return { ...cfg, rules: cfg.rules.map((r) => r.id ? r : { ...r, id: makeRuleId() }) };
|
|
273
|
+
}
|
|
274
|
+
var ModelProxyController = class {
|
|
275
|
+
constructor(scope) {
|
|
276
|
+
this.scope = scope;
|
|
277
|
+
}
|
|
278
|
+
scope;
|
|
279
|
+
listeners = /* @__PURE__ */ new Set();
|
|
280
|
+
snapshot = { status: "loading", writable: false };
|
|
281
|
+
unsub;
|
|
282
|
+
bind() {
|
|
283
|
+
this.unsub = this.scope.subscribe(() => this.pull());
|
|
284
|
+
this.pull();
|
|
285
|
+
}
|
|
286
|
+
dispose() {
|
|
287
|
+
this.unsub?.();
|
|
288
|
+
this.listeners.clear();
|
|
289
|
+
}
|
|
290
|
+
subscribe(l) {
|
|
291
|
+
this.listeners.add(l);
|
|
292
|
+
return () => this.listeners.delete(l);
|
|
293
|
+
}
|
|
294
|
+
getSnapshot() {
|
|
295
|
+
return this.snapshot;
|
|
296
|
+
}
|
|
297
|
+
pull() {
|
|
298
|
+
const s = this.scope.getSnapshot();
|
|
299
|
+
this.snapshot = {
|
|
300
|
+
status: s.status ?? "loading",
|
|
301
|
+
value: s.value,
|
|
302
|
+
revision: s.revision,
|
|
303
|
+
writable: s.writable
|
|
304
|
+
};
|
|
305
|
+
this.emit();
|
|
306
|
+
}
|
|
307
|
+
emit() {
|
|
308
|
+
for (const l of [...this.listeners]) l();
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Persist the config as four fenced field writes.
|
|
312
|
+
*
|
|
313
|
+
* The scope contract exposes per-field writes only, so full atomicity would
|
|
314
|
+
* require collapsing the namespace to a single field (a breaking settings
|
|
315
|
+
* shape change). Until then we order the writes so the field most likely to
|
|
316
|
+
* be REJECTED by host validation (`rules`) goes FIRST: if it fails, nothing
|
|
317
|
+
* else has been committed and the failure aborts cleanly instead of leaving
|
|
318
|
+
* mixed old/new state. The remaining fields are booleans plus a URL that the
|
|
319
|
+
* card pre-validates with the same checks the host applies, making their
|
|
320
|
+
* rejection practically unreachable.
|
|
321
|
+
*/
|
|
322
|
+
async save(next) {
|
|
323
|
+
await this.scope.set("rules", sanitizeRules(next.rules));
|
|
324
|
+
await this.scope.set("defaultProxy", next.defaultProxy);
|
|
325
|
+
await this.scope.set("enabled", next.enabled);
|
|
326
|
+
await this.scope.set("debug", next.debug);
|
|
327
|
+
}
|
|
328
|
+
async setEnabled(v) {
|
|
329
|
+
await this.scope.set("enabled", v);
|
|
330
|
+
}
|
|
331
|
+
async setDebug(v) {
|
|
332
|
+
await this.scope.set("debug", v);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/client/locales.ts
|
|
337
|
+
var en = {
|
|
338
|
+
title: "Model Proxy",
|
|
339
|
+
desc: 'Route specific provider/model pairs through HTTP/HTTPS/SOCKS5 proxies. Specificity: exact model > prefix "vendor-*" > "*". Optional purpose filter targets compaction/session-title calls.',
|
|
340
|
+
enabled: "Enable proxy routing",
|
|
341
|
+
debug: "Debug logging (host log)",
|
|
342
|
+
rules: 'Rules (first match wins \u2014 exact before prefix before "*")',
|
|
343
|
+
addRule: "Add rule",
|
|
344
|
+
provider: "Provider",
|
|
345
|
+
providerPlaceholder: "e.g. openai",
|
|
346
|
+
model: "Model",
|
|
347
|
+
modelPlaceholder: "e.g. gpt-4o-mini, vendor-* or *",
|
|
348
|
+
purpose: "Purpose filter",
|
|
349
|
+
purposePlaceholder: "empty = all purposes (e.g. compaction, session-title)",
|
|
350
|
+
credRef: "Credential ref",
|
|
351
|
+
credRefPlaceholder: "credentials entry holding user:password (optional)",
|
|
352
|
+
proxyUrl: "Proxy URL",
|
|
353
|
+
proxyPlaceholder: "socks5://127.0.0.1:1080 or http://127.0.0.1:7890 (empty=direct)",
|
|
354
|
+
enabledShort: "On",
|
|
355
|
+
delete: "Delete",
|
|
356
|
+
defaultProxy: "Fallback proxy (when no rule matches)",
|
|
357
|
+
defaultPlaceholder: "optional, e.g. http://127.0.0.1:7890",
|
|
358
|
+
saving: "Saving\u2026",
|
|
359
|
+
save: "Save",
|
|
360
|
+
saved: "Saved",
|
|
361
|
+
unavailable: "Model proxy settings unavailable.",
|
|
362
|
+
empty: 'No rules yet \u2014 add one above. Tip: use model "*" for all models of a provider.',
|
|
363
|
+
invalid: "Please fix highlighted fields.",
|
|
364
|
+
duplicate: "Duplicate provider+model",
|
|
365
|
+
reading: "Loading\u2026",
|
|
366
|
+
readOnly: "Read-only (settings provider not writable).",
|
|
367
|
+
choose: "Select\u2026",
|
|
368
|
+
customOption: "Custom\u2026",
|
|
369
|
+
backToList: "Pick from the installed catalog",
|
|
370
|
+
dormant: "not configured",
|
|
371
|
+
groupConfigured: "Configured providers",
|
|
372
|
+
groupDirectory: "Other directory routes",
|
|
373
|
+
wildcardAll: "any model",
|
|
374
|
+
catalogUnavailable: "Provider catalog unavailable \u2014 type values manually.",
|
|
375
|
+
creatorTitle: "New rules",
|
|
376
|
+
creatorModels: "Models",
|
|
377
|
+
creatorPatternHint: "Or a pattern (vendor-* or *)",
|
|
378
|
+
createN: "Create {n} rule(s)",
|
|
379
|
+
cancel: "Cancel",
|
|
380
|
+
alreadyRuled: "rule exists",
|
|
381
|
+
noneCatalogued: "No catalog models \u2014 use * or a custom pattern.",
|
|
382
|
+
groupApplyProxy: "proxy for all\u2026",
|
|
383
|
+
groupApply: "Apply",
|
|
384
|
+
groupEnableAll: "Enable all",
|
|
385
|
+
groupDisableAll: "Disable all",
|
|
386
|
+
groupDelete: "Delete group",
|
|
387
|
+
groupConfirmDelete: "Sure?"
|
|
388
|
+
};
|
|
389
|
+
var zh = {
|
|
390
|
+
title: "\u6A21\u578B\u4EE3\u7406",
|
|
391
|
+
desc: '\u6309 provider/model \u7C92\u5EA6\u8D70\u4EE3\u7406\uFF08HTTP/HTTPS/SOCKS5\uFF09\u3002\u4F18\u5148\u7EA7\uFF1A\u7CBE\u786E model > \u524D\u7F00 "vendor-*" > "*"\u3002\u53EF\u9009\u7528\u9014\u8FC7\u6EE4\u53EF\u5355\u72EC\u63A7\u5236 compaction/session-title \u7B49\u8C03\u7528\u3002',
|
|
392
|
+
enabled: "\u542F\u7528\u4EE3\u7406\u8DEF\u7531",
|
|
393
|
+
debug: "\u8C03\u8BD5\u65E5\u5FD7\uFF08Host \u65E5\u5FD7\uFF09",
|
|
394
|
+
rules: '\u89C4\u5219\uFF08\u9996\u6761\u547D\u4E2D\u5373\u7528 \u2014 \u7CBE\u786E > \u524D\u7F00 > "*"\uFF09',
|
|
395
|
+
addRule: "\u6DFB\u52A0\u89C4\u5219",
|
|
396
|
+
provider: "Provider",
|
|
397
|
+
providerPlaceholder: "\u4F8B\u5982 openai",
|
|
398
|
+
model: "Model",
|
|
399
|
+
modelPlaceholder: "\u4F8B\u5982 gpt-4o-mini\u3001vendor-* \u6216 *",
|
|
400
|
+
purpose: "\u7528\u9014\u8FC7\u6EE4",
|
|
401
|
+
purposePlaceholder: "\u7559\u7A7A=\u6240\u6709\u7528\u9014\uFF08\u5982 compaction\u3001session-title\uFF09",
|
|
402
|
+
credRef: "\u51ED\u636E\u5F15\u7528",
|
|
403
|
+
credRefPlaceholder: "credentials \u670D\u52A1\u6761\u76EE\uFF08user:password\uFF09\uFF0C\u53EF\u9009",
|
|
404
|
+
proxyUrl: "\u4EE3\u7406\u5730\u5740",
|
|
405
|
+
proxyPlaceholder: "socks5://127.0.0.1:1080 \u6216 http://127.0.0.1:7890\uFF08\u7A7A=\u76F4\u8FDE\uFF09",
|
|
406
|
+
enabledShort: "\u542F\u7528",
|
|
407
|
+
delete: "\u5220\u9664",
|
|
408
|
+
defaultProxy: "\u515C\u5E95\u4EE3\u7406\uFF08\u65E0\u89C4\u5219\u547D\u4E2D\u65F6\uFF09",
|
|
409
|
+
defaultPlaceholder: "\u53EF\u9009\uFF0C\u4F8B\u5982 http://127.0.0.1:7890",
|
|
410
|
+
saving: "\u4FDD\u5B58\u4E2D\u2026",
|
|
411
|
+
save: "\u4FDD\u5B58",
|
|
412
|
+
saved: "\u5DF2\u4FDD\u5B58",
|
|
413
|
+
unavailable: "\u6A21\u578B\u4EE3\u7406\u8BBE\u7F6E\u4E0D\u53EF\u7528\u3002",
|
|
414
|
+
empty: '\u6682\u65E0\u89C4\u5219 \u2014 \u70B9\u51FB\u4E0A\u65B9\u6DFB\u52A0\u3002\u63D0\u793A\uFF1Amodel \u7528 "*" \u8868\u793A\u8BE5 provider \u7684\u6240\u6709\u6A21\u578B\u3002',
|
|
415
|
+
invalid: "\u8BF7\u4FEE\u6B63\u6807\u7EA2\u5B57\u6BB5\u3002",
|
|
416
|
+
duplicate: "provider+model \u91CD\u590D",
|
|
417
|
+
reading: "\u52A0\u8F7D\u4E2D\u2026",
|
|
418
|
+
readOnly: "\u53EA\u8BFB\uFF08settings provider \u4E0D\u53EF\u5199\uFF09\u3002",
|
|
419
|
+
choose: "\u8BF7\u9009\u62E9\u2026",
|
|
420
|
+
customOption: "\u81EA\u5B9A\u4E49\u2026",
|
|
421
|
+
backToList: "\u4ECE\u5DF2\u5B89\u88C5\u76EE\u5F55\u9009\u62E9",
|
|
422
|
+
dormant: "\u672A\u914D\u7F6E",
|
|
423
|
+
groupConfigured: "\u5DF2\u914D\u7F6E\u7684 Provider",
|
|
424
|
+
groupDirectory: "\u76EE\u5F55\u4E2D\u7684\u5176\u4ED6\u8DEF\u7531",
|
|
425
|
+
wildcardAll: "\u6240\u6709\u6A21\u578B",
|
|
426
|
+
catalogUnavailable: "\u63D0\u4F9B\u65B9\u76EE\u5F55\u4E0D\u53EF\u7528 \u2014 \u8BF7\u624B\u52A8\u8F93\u5165\u3002",
|
|
427
|
+
creatorTitle: "\u65B0\u5EFA\u89C4\u5219",
|
|
428
|
+
creatorModels: "\u52FE\u9009\u6A21\u578B",
|
|
429
|
+
creatorPatternHint: "\u6216\u81EA\u5B9A\u4E49\u6A21\u5F0F\uFF08vendor-*\u3001*\uFF09",
|
|
430
|
+
createN: "\u521B\u5EFA {n} \u6761\u89C4\u5219",
|
|
431
|
+
cancel: "\u53D6\u6D88",
|
|
432
|
+
alreadyRuled: "\u5DF2\u6709\u89C4\u5219",
|
|
433
|
+
noneCatalogued: "\u8BE5 provider \u65E0\u76EE\u5F55\u6A21\u578B\u2014\u2014\u53EF\u7528 * \u6216\u81EA\u5B9A\u4E49\u6A21\u5F0F\u3002",
|
|
434
|
+
groupApplyProxy: "\u6574\u7EC4\u4EE3\u7406\u5730\u5740\u2026",
|
|
435
|
+
groupApply: "\u5E94\u7528",
|
|
436
|
+
groupEnableAll: "\u5168\u90E8\u542F\u7528",
|
|
437
|
+
groupDisableAll: "\u5168\u90E8\u505C\u7528",
|
|
438
|
+
groupDelete: "\u5220\u9664\u672C\u7EC4",
|
|
439
|
+
groupConfirmDelete: "\u786E\u8BA4\uFF1F"
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// src/client/ModelProxyCard.tsx
|
|
443
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
444
|
+
var NO_CATALOG = { status: "idle", providers: [], modelsByProvider: {} };
|
|
445
|
+
var CUSTOM_SENTINEL = "__custom__";
|
|
446
|
+
function CatalogField(props) {
|
|
447
|
+
const { value, groups, onChange, disabled, chooseLabel, customLabel, listTitle, inputPlaceholder } = props;
|
|
448
|
+
const [customMode, setCustomMode] = (0, import_react.useState)(() => value !== "" && !groups.some((g) => g.options.some((o) => o.value === value)));
|
|
449
|
+
const flat = groups.flatMap((g) => g.options);
|
|
450
|
+
const showSelect = flat.length > 0 && !customMode;
|
|
451
|
+
if (flat.length === 0) {
|
|
452
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
453
|
+
"input",
|
|
454
|
+
{
|
|
455
|
+
value,
|
|
456
|
+
placeholder: inputPlaceholder,
|
|
457
|
+
onChange: (e) => onChange(e.target.value),
|
|
458
|
+
style: { padding: "6px 8px", borderRadius: 6, border: "1px solid #ccc", width: "100%", boxSizing: "border-box" },
|
|
459
|
+
disabled
|
|
460
|
+
}
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", gap: 4, alignItems: "center" }, children: showSelect ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
464
|
+
"select",
|
|
465
|
+
{
|
|
466
|
+
value: flat.some((o) => o.value === value) ? value : "",
|
|
467
|
+
onChange: (e) => {
|
|
468
|
+
if (e.target.value === CUSTOM_SENTINEL) {
|
|
469
|
+
setCustomMode(true);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
onChange(e.target.value);
|
|
473
|
+
},
|
|
474
|
+
style: { padding: "6px 8px", borderRadius: 6, border: "1px solid #ccc", flex: 1, minWidth: 0 },
|
|
475
|
+
disabled,
|
|
476
|
+
children: [
|
|
477
|
+
!flat.some((o) => o.value === value) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: chooseLabel }),
|
|
478
|
+
groups.map(
|
|
479
|
+
(g, gi) => g.label === void 0 ? g.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: o.value, children: o.label }, o.value)) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("optgroup", { label: g.label, children: g.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: o.value, children: o.label }, o.value)) }, `${gi}:${g.label}`)
|
|
480
|
+
),
|
|
481
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: CUSTOM_SENTINEL, children: customLabel })
|
|
482
|
+
]
|
|
483
|
+
}
|
|
484
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
485
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
486
|
+
"input",
|
|
487
|
+
{
|
|
488
|
+
value,
|
|
489
|
+
placeholder: inputPlaceholder,
|
|
490
|
+
onChange: (e) => onChange(e.target.value),
|
|
491
|
+
style: { padding: "6px 8px", borderRadius: 6, border: "1px solid #ccc", flex: 1, minWidth: 0 },
|
|
492
|
+
disabled
|
|
493
|
+
}
|
|
494
|
+
),
|
|
495
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
496
|
+
"button",
|
|
497
|
+
{
|
|
498
|
+
type: "button",
|
|
499
|
+
title: listTitle,
|
|
500
|
+
onClick: () => setCustomMode(false),
|
|
501
|
+
disabled,
|
|
502
|
+
style: {
|
|
503
|
+
padding: "4px 8px",
|
|
504
|
+
borderRadius: 6,
|
|
505
|
+
border: "1px solid #ddd",
|
|
506
|
+
background: "#fff",
|
|
507
|
+
cursor: "pointer",
|
|
508
|
+
lineHeight: 1
|
|
509
|
+
},
|
|
510
|
+
children: "\u25BE"
|
|
511
|
+
}
|
|
512
|
+
)
|
|
513
|
+
] }) });
|
|
514
|
+
}
|
|
515
|
+
var SUPPORTED_SCHEMES = ["http:", "https:", "socks5:", "socks5h:", "socks:"];
|
|
516
|
+
var EMPTY_CREATOR = {
|
|
517
|
+
provider: "",
|
|
518
|
+
checked: [],
|
|
519
|
+
customPattern: "",
|
|
520
|
+
proxyUrl: "",
|
|
521
|
+
purpose: "",
|
|
522
|
+
credentialRef: ""
|
|
523
|
+
};
|
|
524
|
+
function validateUrl(urlStr) {
|
|
525
|
+
try {
|
|
526
|
+
const u = new URL(urlStr);
|
|
527
|
+
if (!SUPPORTED_SCHEMES.includes(u.protocol)) return "unsupported scheme";
|
|
528
|
+
if (!u.hostname) return "missing host";
|
|
529
|
+
return void 0;
|
|
530
|
+
} catch {
|
|
531
|
+
return "invalid URL";
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function validateRule(r, all) {
|
|
535
|
+
if (r.provider !== r.provider.trim()) return "whitespace around provider";
|
|
536
|
+
if (r.model !== r.model.trim()) return "whitespace around model";
|
|
537
|
+
if (r.purpose !== void 0 && r.purpose !== "" && r.purpose !== r.purpose.trim()) return "whitespace around purpose";
|
|
538
|
+
if (r.credentialRef !== void 0 && r.credentialRef !== "" && r.credentialRef !== r.credentialRef.trim()) return "whitespace around credential ref";
|
|
539
|
+
if (!r.provider.trim()) return "provider required";
|
|
540
|
+
if (!r.model.trim()) return "model required";
|
|
541
|
+
const starCount = (r.model.match(/\*/g) ?? []).length;
|
|
542
|
+
const isBareWildcard = r.model === "*";
|
|
543
|
+
if (!isBareWildcard && starCount > 0 && !(starCount === 1 && r.model.endsWith("*"))) {
|
|
544
|
+
return 'at most one trailing * (e.g. "vendor-*")';
|
|
545
|
+
}
|
|
546
|
+
if (r.proxyUrl !== "") {
|
|
547
|
+
const err = validateUrl(r.proxyUrl);
|
|
548
|
+
if (err) return err;
|
|
549
|
+
}
|
|
550
|
+
const dup = all.filter((x) => x.provider === r.provider && x.model === r.model).length > 1;
|
|
551
|
+
if (dup) return "duplicate";
|
|
552
|
+
return void 0;
|
|
553
|
+
}
|
|
554
|
+
function validateDefaultProxy(defaultProxy) {
|
|
555
|
+
if (defaultProxy === "") return void 0;
|
|
556
|
+
return validateUrl(defaultProxy);
|
|
557
|
+
}
|
|
558
|
+
function GroupHeader(props) {
|
|
559
|
+
const { provider, displayName, count, allEnabled, disabled, t, onApplyProxy, onSetEnabled, onDelete } = props;
|
|
560
|
+
const [proxy, setProxy] = (0, import_react.useState)("");
|
|
561
|
+
const [confirming, setConfirming] = (0, import_react.useState)(false);
|
|
562
|
+
(0, import_react.useEffect)(() => {
|
|
563
|
+
if (!confirming) return;
|
|
564
|
+
const timer = setTimeout(() => setConfirming(false), 3e3);
|
|
565
|
+
return () => clearTimeout(timer);
|
|
566
|
+
}, [confirming]);
|
|
567
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center" }, children: [
|
|
568
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontWeight: 700 }, children: [
|
|
569
|
+
displayName === provider ? provider : `${displayName} \xB7 ${provider}`,
|
|
570
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: 400, opacity: 0.6, marginLeft: 6 }, children: count })
|
|
571
|
+
] }),
|
|
572
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
|
|
573
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
574
|
+
"input",
|
|
575
|
+
{
|
|
576
|
+
value: proxy,
|
|
577
|
+
placeholder: t("groupApplyProxy"),
|
|
578
|
+
onChange: (e) => setProxy(e.target.value),
|
|
579
|
+
onKeyDown: (e) => {
|
|
580
|
+
if (e.key === "Enter") onApplyProxy(proxy.trim());
|
|
581
|
+
},
|
|
582
|
+
style: { padding: "4px 8px", borderRadius: 6, border: "1px solid #ddd", width: 200 },
|
|
583
|
+
disabled
|
|
584
|
+
}
|
|
585
|
+
),
|
|
586
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
587
|
+
"button",
|
|
588
|
+
{
|
|
589
|
+
type: "button",
|
|
590
|
+
onClick: () => onApplyProxy(proxy.trim()),
|
|
591
|
+
disabled: disabled || proxy.trim() === "",
|
|
592
|
+
style: smallButtonStyle,
|
|
593
|
+
children: t("groupApply")
|
|
594
|
+
}
|
|
595
|
+
),
|
|
596
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => onSetEnabled(!allEnabled), disabled, style: smallButtonStyle, children: allEnabled ? t("groupDisableAll") : t("groupEnableAll") }),
|
|
597
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
598
|
+
"button",
|
|
599
|
+
{
|
|
600
|
+
type: "button",
|
|
601
|
+
onClick: () => {
|
|
602
|
+
if (confirming) {
|
|
603
|
+
setConfirming(false);
|
|
604
|
+
onDelete();
|
|
605
|
+
} else {
|
|
606
|
+
setConfirming(true);
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
disabled,
|
|
610
|
+
style: {
|
|
611
|
+
...smallButtonStyle,
|
|
612
|
+
borderColor: confirming ? "#d66" : "#ddd",
|
|
613
|
+
color: confirming ? "#d66" : void 0
|
|
614
|
+
},
|
|
615
|
+
children: confirming ? t("groupConfirmDelete") : t("groupDelete")
|
|
616
|
+
}
|
|
617
|
+
)
|
|
618
|
+
] });
|
|
619
|
+
}
|
|
620
|
+
var smallButtonStyle = {
|
|
621
|
+
padding: "4px 8px",
|
|
622
|
+
borderRadius: 6,
|
|
623
|
+
border: "1px solid #ddd",
|
|
624
|
+
background: "#fff",
|
|
625
|
+
cursor: "pointer",
|
|
626
|
+
lineHeight: 1
|
|
627
|
+
};
|
|
628
|
+
function ModelProxyCard({ controller, catalog, t: tProp }) {
|
|
629
|
+
const t = tProp ?? ((k) => en[k]);
|
|
630
|
+
const snap = (0, import_react.useSyncExternalStore)(
|
|
631
|
+
(cb) => controller.subscribe(cb),
|
|
632
|
+
() => controller.getSnapshot(),
|
|
633
|
+
() => controller.getSnapshot()
|
|
634
|
+
);
|
|
635
|
+
const cat = (0, import_react.useSyncExternalStore)(
|
|
636
|
+
(0, import_react.useCallback)((cb) => catalog ? catalog.subscribe(cb) : () => void 0, [catalog]),
|
|
637
|
+
(0, import_react.useCallback)(() => catalog?.getSnapshot() ?? NO_CATALOG, [catalog]),
|
|
638
|
+
(0, import_react.useCallback)(() => catalog?.getSnapshot() ?? NO_CATALOG, [catalog])
|
|
639
|
+
);
|
|
640
|
+
const cfg = (0, import_react.useMemo)(() => {
|
|
641
|
+
return ensureRuleIds(
|
|
642
|
+
snap.value ?? {
|
|
643
|
+
enabled: true,
|
|
644
|
+
rules: [],
|
|
645
|
+
defaultProxy: "",
|
|
646
|
+
debug: false
|
|
647
|
+
}
|
|
648
|
+
);
|
|
649
|
+
}, [snap.value]);
|
|
650
|
+
const [draft, setDraft] = (0, import_react.useState)(cfg);
|
|
651
|
+
const [saving, setSaving] = (0, import_react.useState)(false);
|
|
652
|
+
const [msg, setMsg] = (0, import_react.useState)();
|
|
653
|
+
const dirty = (0, import_react.useMemo)(() => JSON.stringify(draft) !== JSON.stringify(cfg), [draft, cfg]);
|
|
654
|
+
(0, import_react.useEffect)(() => {
|
|
655
|
+
if (!dirty) setDraft(cfg);
|
|
656
|
+
}, [cfg]);
|
|
657
|
+
(0, import_react.useEffect)(() => {
|
|
658
|
+
if (!msg || msg !== t("saved")) return;
|
|
659
|
+
const timer = setTimeout(() => setMsg(void 0), 1500);
|
|
660
|
+
return () => clearTimeout(timer);
|
|
661
|
+
}, [msg]);
|
|
662
|
+
const defaultProxyError = (0, import_react.useMemo)(() => validateDefaultProxy(draft.defaultProxy), [draft.defaultProxy]);
|
|
663
|
+
const providerGroups = (0, import_react.useMemo)(() => {
|
|
664
|
+
const opts = cat.providers.map((p) => ({
|
|
665
|
+
value: p.provider,
|
|
666
|
+
label: p.displayName === p.provider ? p.provider : `${p.displayName} (${p.provider})${p.active ? "" : ` \xB7 ${t("dormant")}`}`
|
|
667
|
+
}));
|
|
668
|
+
if (!cat.providers.some((p) => p.configured !== void 0)) return [{ options: opts }];
|
|
669
|
+
const configured = [];
|
|
670
|
+
const directory = [];
|
|
671
|
+
for (let i = 0; i < cat.providers.length; i++) {
|
|
672
|
+
const p = cat.providers[i];
|
|
673
|
+
(p.configured === true ? configured : directory).push(opts[i]);
|
|
674
|
+
}
|
|
675
|
+
return [
|
|
676
|
+
...configured.length > 0 ? [{ label: t("groupConfigured"), options: configured }] : [],
|
|
677
|
+
...directory.length > 0 ? [{ label: t("groupDirectory"), options: directory }] : []
|
|
678
|
+
];
|
|
679
|
+
}, [cat, t]);
|
|
680
|
+
const modelOptionsFor = (0, import_react.useCallback)(
|
|
681
|
+
(provider) => {
|
|
682
|
+
const models = cat.modelsByProvider[provider] ?? [];
|
|
683
|
+
if (models.length === 0) return [];
|
|
684
|
+
const wildcard = { value: "*", label: `* (${t("wildcardAll")})` };
|
|
685
|
+
return [
|
|
686
|
+
...models.some((m) => m.id === "*") ? [] : [wildcard],
|
|
687
|
+
...models.map((m) => ({ value: m.id, label: m.name && m.name !== m.id ? `${m.name} (${m.id})` : m.id }))
|
|
688
|
+
];
|
|
689
|
+
},
|
|
690
|
+
[cat, t]
|
|
691
|
+
);
|
|
692
|
+
const catalogMissing = catalog !== void 0 && cat.status === "unavailable" && cat.providers.length === 0;
|
|
693
|
+
const onSave = (0, import_react.useCallback)(async () => {
|
|
694
|
+
for (const r of draft.rules) {
|
|
695
|
+
const err = validateRule(r, draft.rules);
|
|
696
|
+
if (err) {
|
|
697
|
+
setMsg(t("invalid"));
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (defaultProxyError) {
|
|
702
|
+
setMsg(t("invalid"));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
setSaving(true);
|
|
706
|
+
setMsg(void 0);
|
|
707
|
+
try {
|
|
708
|
+
await controller.save(draft);
|
|
709
|
+
setMsg(t("saved"));
|
|
710
|
+
} catch (e) {
|
|
711
|
+
setMsg(String(e));
|
|
712
|
+
} finally {
|
|
713
|
+
setSaving(false);
|
|
714
|
+
}
|
|
715
|
+
}, [draft, controller, t, defaultProxyError]);
|
|
716
|
+
const setField = (0, import_react.useCallback)(
|
|
717
|
+
(patch) => setDraft((d) => ({ ...d, ...patch })),
|
|
718
|
+
[]
|
|
719
|
+
);
|
|
720
|
+
const [creatorOpen, setCreatorOpen] = (0, import_react.useState)(false);
|
|
721
|
+
const [creator, setCreator] = (0, import_react.useState)(EMPTY_CREATOR);
|
|
722
|
+
const [creatorError, setCreatorError] = (0, import_react.useState)();
|
|
723
|
+
const setCreatorField = (0, import_react.useCallback)(
|
|
724
|
+
(patch) => setCreator((c) => ({ ...c, ...patch })),
|
|
725
|
+
[]
|
|
726
|
+
);
|
|
727
|
+
const ruleExists = (0, import_react.useCallback)(
|
|
728
|
+
(provider, model) => draft.rules.some((r) => r.provider === provider && r.model === model),
|
|
729
|
+
[draft.rules]
|
|
730
|
+
);
|
|
731
|
+
const creatorModelCount = (0, import_react.useMemo)(() => {
|
|
732
|
+
if (creator.provider.trim() === "") return 0;
|
|
733
|
+
const custom = creator.customPattern.trim();
|
|
734
|
+
return creator.checked.length + (custom !== "" ? 1 : 0);
|
|
735
|
+
}, [creator]);
|
|
736
|
+
const createRules = (0, import_react.useCallback)(() => {
|
|
737
|
+
const models = [...creator.checked];
|
|
738
|
+
const custom = creator.customPattern.trim();
|
|
739
|
+
if (custom !== "") models.push(custom);
|
|
740
|
+
const newRules = buildCreatorRules({
|
|
741
|
+
provider: creator.provider,
|
|
742
|
+
models,
|
|
743
|
+
proxyUrl: creator.proxyUrl,
|
|
744
|
+
purpose: creator.purpose,
|
|
745
|
+
credentialRef: creator.credentialRef,
|
|
746
|
+
enabled: true
|
|
747
|
+
});
|
|
748
|
+
const combined = [...newRules, ...draft.rules];
|
|
749
|
+
for (const r of newRules) {
|
|
750
|
+
const err = validateRule(r, combined);
|
|
751
|
+
if (err) {
|
|
752
|
+
setCreatorError(err === "duplicate" ? t("duplicate") : err);
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
setDraft((d) => ({ ...d, rules: [...newRules, ...d.rules] }));
|
|
757
|
+
setCreator(EMPTY_CREATOR);
|
|
758
|
+
setCreatorError(void 0);
|
|
759
|
+
setCreatorOpen(false);
|
|
760
|
+
}, [creator, draft.rules, t]);
|
|
761
|
+
const applyGroupProxy = (0, import_react.useCallback)((provider, url) => {
|
|
762
|
+
setDraft((d) => ({
|
|
763
|
+
...d,
|
|
764
|
+
rules: d.rules.map((r) => r.provider === provider ? { ...r, proxyUrl: url } : r)
|
|
765
|
+
}));
|
|
766
|
+
}, []);
|
|
767
|
+
const setGroupEnabled = (0, import_react.useCallback)((provider, enabled) => {
|
|
768
|
+
setDraft((d) => ({
|
|
769
|
+
...d,
|
|
770
|
+
rules: d.rules.map((r) => r.provider === provider ? { ...r, enabled } : r)
|
|
771
|
+
}));
|
|
772
|
+
}, []);
|
|
773
|
+
const removeGroup = (0, import_react.useCallback)((provider) => {
|
|
774
|
+
setDraft((d) => ({ ...d, rules: d.rules.filter((r) => r.provider !== provider) }));
|
|
775
|
+
}, []);
|
|
776
|
+
const updateRule = (0, import_react.useCallback)((id, patch) => {
|
|
777
|
+
setDraft((d) => ({
|
|
778
|
+
...d,
|
|
779
|
+
rules: d.rules.map((r) => r.id === id ? { ...r, ...patch } : r)
|
|
780
|
+
}));
|
|
781
|
+
}, []);
|
|
782
|
+
const removeRule = (0, import_react.useCallback)((id) => {
|
|
783
|
+
setDraft((d) => ({ ...d, rules: d.rules.filter((r) => r.id !== id) }));
|
|
784
|
+
}, []);
|
|
785
|
+
if (snap.status === "loading") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: 12, opacity: 0.7 }, children: t("reading") });
|
|
786
|
+
if (snap.status === "unavailable") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: 12 }, children: t("unavailable") });
|
|
787
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: 12, display: "flex", flexDirection: "column", gap: 12 }, children: [
|
|
788
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
789
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 700, fontSize: 16 }, children: t("title") }),
|
|
790
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { opacity: 0.7, fontSize: 12, marginTop: 4 }, children: t("desc") }),
|
|
791
|
+
!snap.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { color: "#a66", fontSize: 12, marginTop: 6 }, children: t("readOnly") }),
|
|
792
|
+
catalogMissing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { opacity: 0.6, fontSize: 12, marginTop: 6 }, children: t("catalogUnavailable") })
|
|
793
|
+
] }),
|
|
794
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
795
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: draft.enabled, onChange: (e) => setField({ enabled: e.target.checked }), disabled: !snap.writable }),
|
|
796
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("enabled") })
|
|
797
|
+
] }),
|
|
798
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
799
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: draft.debug, onChange: (e) => setField({ debug: e.target.checked }), disabled: !snap.writable }),
|
|
800
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("debug") })
|
|
801
|
+
] }),
|
|
802
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
803
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 600 }, children: t("rules") }),
|
|
804
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
805
|
+
"button",
|
|
806
|
+
{
|
|
807
|
+
onClick: () => {
|
|
808
|
+
setCreatorOpen((o) => !o);
|
|
809
|
+
setCreatorError(void 0);
|
|
810
|
+
},
|
|
811
|
+
disabled: !snap.writable,
|
|
812
|
+
style: { padding: "6px 10px", borderRadius: 6, border: "1px solid #ccc", background: "#fff", cursor: "pointer" },
|
|
813
|
+
children: [
|
|
814
|
+
"\uFF0B ",
|
|
815
|
+
t("addRule")
|
|
816
|
+
]
|
|
817
|
+
}
|
|
818
|
+
)
|
|
819
|
+
] }),
|
|
820
|
+
draft.rules.length === 0 && !creatorOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { opacity: 0.6, fontSize: 12 }, children: t("empty") }),
|
|
821
|
+
creatorOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { border: "1px solid #bbb", borderRadius: 10, padding: 12, display: "flex", flexDirection: "column", gap: 8 }, children: [
|
|
822
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 600 }, children: t("creatorTitle") }),
|
|
823
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }, children: [
|
|
824
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
|
|
825
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, opacity: 0.7 }, children: t("provider") }),
|
|
826
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
827
|
+
CatalogField,
|
|
828
|
+
{
|
|
829
|
+
value: creator.provider,
|
|
830
|
+
groups: providerGroups,
|
|
831
|
+
onChange: (v) => setCreatorField({ provider: v }),
|
|
832
|
+
disabled: !snap.writable,
|
|
833
|
+
chooseLabel: t("choose"),
|
|
834
|
+
customLabel: t("customOption"),
|
|
835
|
+
listTitle: t("backToList"),
|
|
836
|
+
inputPlaceholder: t("providerPlaceholder")
|
|
837
|
+
}
|
|
838
|
+
)
|
|
839
|
+
] }),
|
|
840
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
|
|
841
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, opacity: 0.7 }, children: t("proxyUrl") }),
|
|
842
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
843
|
+
"input",
|
|
844
|
+
{
|
|
845
|
+
value: creator.proxyUrl,
|
|
846
|
+
placeholder: t("proxyPlaceholder"),
|
|
847
|
+
onChange: (e) => setCreatorField({ proxyUrl: e.target.value }),
|
|
848
|
+
style: { padding: "6px 8px", borderRadius: 6, border: "1px solid #ccc" },
|
|
849
|
+
disabled: !snap.writable
|
|
850
|
+
}
|
|
851
|
+
)
|
|
852
|
+
] })
|
|
853
|
+
] }),
|
|
854
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
|
|
855
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, opacity: 0.7 }, children: t("creatorModels") }),
|
|
856
|
+
modelOptionsFor(creator.provider).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { opacity: 0.6, fontSize: 12 }, children: t("noneCatalogued") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 4 }, children: modelOptionsFor(creator.provider).map((o) => {
|
|
857
|
+
const exists = ruleExists(creator.provider, o.value);
|
|
858
|
+
const checked = creator.checked.includes(o.value);
|
|
859
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", gap: 6, alignItems: "center", opacity: exists ? 0.45 : 1 }, children: [
|
|
860
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
861
|
+
"input",
|
|
862
|
+
{
|
|
863
|
+
type: "checkbox",
|
|
864
|
+
checked,
|
|
865
|
+
disabled: !snap.writable || exists,
|
|
866
|
+
onChange: () => setCreatorField({
|
|
867
|
+
checked: checked ? creator.checked.filter((m) => m !== o.value) : [...creator.checked, o.value]
|
|
868
|
+
})
|
|
869
|
+
}
|
|
870
|
+
),
|
|
871
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 12 }, children: [
|
|
872
|
+
o.label,
|
|
873
|
+
exists ? ` \xB7 ${t("alreadyRuled")}` : ""
|
|
874
|
+
] })
|
|
875
|
+
] }, o.value);
|
|
876
|
+
}) }),
|
|
877
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
878
|
+
"input",
|
|
879
|
+
{
|
|
880
|
+
value: creator.customPattern,
|
|
881
|
+
placeholder: t("creatorPatternHint"),
|
|
882
|
+
onChange: (e) => setCreatorField({ customPattern: e.target.value }),
|
|
883
|
+
style: { padding: "6px 8px", borderRadius: 6, border: "1px solid #ccc" },
|
|
884
|
+
disabled: !snap.writable
|
|
885
|
+
}
|
|
886
|
+
)
|
|
887
|
+
] }),
|
|
888
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }, children: [
|
|
889
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
|
|
890
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, opacity: 0.7 }, children: t("purpose") }),
|
|
891
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
892
|
+
"input",
|
|
893
|
+
{
|
|
894
|
+
value: creator.purpose,
|
|
895
|
+
placeholder: t("purposePlaceholder"),
|
|
896
|
+
onChange: (e) => setCreatorField({ purpose: e.target.value }),
|
|
897
|
+
style: { padding: "6px 8px", borderRadius: 6, border: "1px solid #ccc" },
|
|
898
|
+
disabled: !snap.writable
|
|
899
|
+
}
|
|
900
|
+
)
|
|
901
|
+
] }),
|
|
902
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
|
|
903
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, opacity: 0.7 }, children: t("credRef") }),
|
|
904
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
905
|
+
"input",
|
|
906
|
+
{
|
|
907
|
+
value: creator.credentialRef,
|
|
908
|
+
placeholder: t("credRefPlaceholder"),
|
|
909
|
+
onChange: (e) => setCreatorField({ credentialRef: e.target.value }),
|
|
910
|
+
style: { padding: "6px 8px", borderRadius: 6, border: "1px solid #ccc" },
|
|
911
|
+
disabled: !snap.writable
|
|
912
|
+
}
|
|
913
|
+
)
|
|
914
|
+
] })
|
|
915
|
+
] }),
|
|
916
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8, alignItems: "center" }, children: [
|
|
917
|
+
creatorError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#b33", fontSize: 11 }, children: creatorError }),
|
|
918
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
919
|
+
"button",
|
|
920
|
+
{
|
|
921
|
+
onClick: () => {
|
|
922
|
+
setCreatorOpen(false);
|
|
923
|
+
setCreator(EMPTY_CREATOR);
|
|
924
|
+
setCreatorError(void 0);
|
|
925
|
+
},
|
|
926
|
+
style: smallButtonStyle,
|
|
927
|
+
children: t("cancel")
|
|
928
|
+
}
|
|
929
|
+
),
|
|
930
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
931
|
+
"button",
|
|
932
|
+
{
|
|
933
|
+
onClick: createRules,
|
|
934
|
+
disabled: !snap.writable || creator.provider.trim() === "" || creatorModelCount === 0,
|
|
935
|
+
style: {
|
|
936
|
+
...smallButtonStyle,
|
|
937
|
+
borderColor: "#222",
|
|
938
|
+
background: creatorModelCount > 0 ? "#222" : "#999",
|
|
939
|
+
color: "#fff",
|
|
940
|
+
cursor: creatorModelCount > 0 ? "pointer" : "not-allowed"
|
|
941
|
+
},
|
|
942
|
+
children: t("createN").replace("{n}", String(creatorModelCount))
|
|
943
|
+
}
|
|
944
|
+
)
|
|
945
|
+
] })
|
|
946
|
+
] }),
|
|
947
|
+
groupByProvider(draft.rules).map((group) => {
|
|
948
|
+
const allEnabled = group.rules.every((r) => r.enabled);
|
|
949
|
+
const entry = cat.providers.find((p) => p.provider === group.provider);
|
|
950
|
+
const displayName = entry?.displayName !== void 0 && entry.displayName !== group.provider ? `${entry.displayName}` : group.provider;
|
|
951
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { border: "1px solid #e8e8e8", borderRadius: 10, padding: 10, display: "flex", flexDirection: "column", gap: 8 }, children: [
|
|
952
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
953
|
+
GroupHeader,
|
|
954
|
+
{
|
|
955
|
+
provider: group.provider,
|
|
956
|
+
displayName,
|
|
957
|
+
count: group.rules.length,
|
|
958
|
+
allEnabled,
|
|
959
|
+
disabled: !snap.writable,
|
|
960
|
+
t,
|
|
961
|
+
onApplyProxy: (url) => applyGroupProxy(group.provider, url),
|
|
962
|
+
onSetEnabled: (enabled) => setGroupEnabled(group.provider, enabled),
|
|
963
|
+
onDelete: () => removeGroup(group.provider)
|
|
964
|
+
}
|
|
965
|
+
),
|
|
966
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: group.rules.map((raw) => {
|
|
967
|
+
const r = raw;
|
|
968
|
+
const err = validateRule(r, draft.rules);
|
|
969
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
970
|
+
"div",
|
|
971
|
+
{
|
|
972
|
+
style: {
|
|
973
|
+
border: `1px solid ${err ? "#d66" : "#ddd"}`,
|
|
974
|
+
borderRadius: 8,
|
|
975
|
+
padding: 8,
|
|
976
|
+
display: "grid",
|
|
977
|
+
gridTemplateColumns: "1fr 1fr",
|
|
978
|
+
gap: 8,
|
|
979
|
+
background: r.enabled ? "#fff" : "#f7f7f7"
|
|
980
|
+
},
|
|
981
|
+
children: [
|
|
982
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
|
|
983
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, opacity: 0.7 }, children: t("model") }),
|
|
984
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
985
|
+
CatalogField,
|
|
986
|
+
{
|
|
987
|
+
value: r.model,
|
|
988
|
+
groups: [{ options: modelOptionsFor(r.provider) }],
|
|
989
|
+
onChange: (v) => updateRule(r.id, { model: v }),
|
|
990
|
+
disabled: !snap.writable,
|
|
991
|
+
chooseLabel: t("choose"),
|
|
992
|
+
customLabel: t("customOption"),
|
|
993
|
+
listTitle: t("backToList"),
|
|
994
|
+
inputPlaceholder: t("modelPlaceholder")
|
|
995
|
+
}
|
|
996
|
+
)
|
|
997
|
+
] }),
|
|
998
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
|
|
999
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, opacity: 0.7 }, children: t("proxyUrl") }),
|
|
1000
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1001
|
+
"input",
|
|
1002
|
+
{
|
|
1003
|
+
value: r.proxyUrl,
|
|
1004
|
+
placeholder: t("proxyPlaceholder"),
|
|
1005
|
+
onChange: (e) => updateRule(r.id, { proxyUrl: e.target.value }),
|
|
1006
|
+
style: { padding: "6px 8px", borderRadius: 6, border: "1px solid #ccc" },
|
|
1007
|
+
disabled: !snap.writable
|
|
1008
|
+
}
|
|
1009
|
+
),
|
|
1010
|
+
err && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#b33", fontSize: 11 }, children: err === "duplicate" ? t("duplicate") : err })
|
|
1011
|
+
] }),
|
|
1012
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
|
|
1013
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, opacity: 0.7 }, children: t("purpose") }),
|
|
1014
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1015
|
+
"input",
|
|
1016
|
+
{
|
|
1017
|
+
value: r.purpose ?? "",
|
|
1018
|
+
placeholder: t("purposePlaceholder"),
|
|
1019
|
+
onChange: (e) => updateRule(r.id, { purpose: e.target.value }),
|
|
1020
|
+
style: { padding: "6px 8px", borderRadius: 6, border: "1px solid #ccc" },
|
|
1021
|
+
disabled: !snap.writable
|
|
1022
|
+
}
|
|
1023
|
+
)
|
|
1024
|
+
] }),
|
|
1025
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
|
|
1026
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, opacity: 0.7 }, children: t("credRef") }),
|
|
1027
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1028
|
+
"input",
|
|
1029
|
+
{
|
|
1030
|
+
value: r.credentialRef ?? "",
|
|
1031
|
+
placeholder: t("credRefPlaceholder"),
|
|
1032
|
+
onChange: (e) => updateRule(r.id, { credentialRef: e.target.value }),
|
|
1033
|
+
style: { padding: "6px 8px", borderRadius: 6, border: "1px solid #ccc" },
|
|
1034
|
+
disabled: !snap.writable
|
|
1035
|
+
}
|
|
1036
|
+
)
|
|
1037
|
+
] }),
|
|
1038
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
1039
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: r.enabled, onChange: (e) => updateRule(r.id, { enabled: e.target.checked }), disabled: !snap.writable }),
|
|
1040
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12 }, children: t("enabledShort") })
|
|
1041
|
+
] }),
|
|
1042
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1043
|
+
"button",
|
|
1044
|
+
{
|
|
1045
|
+
onClick: () => removeRule(r.id),
|
|
1046
|
+
disabled: !snap.writable,
|
|
1047
|
+
style: { padding: "4px 8px", borderRadius: 6, border: "1px solid #ddd", background: "#fff", cursor: "pointer" },
|
|
1048
|
+
children: t("delete")
|
|
1049
|
+
}
|
|
1050
|
+
) })
|
|
1051
|
+
]
|
|
1052
|
+
},
|
|
1053
|
+
r.id
|
|
1054
|
+
);
|
|
1055
|
+
}) })
|
|
1056
|
+
] }, group.provider);
|
|
1057
|
+
}),
|
|
1058
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
|
|
1059
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, fontWeight: 600 }, children: t("defaultProxy") }),
|
|
1060
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1061
|
+
"input",
|
|
1062
|
+
{
|
|
1063
|
+
value: draft.defaultProxy,
|
|
1064
|
+
placeholder: t("defaultPlaceholder"),
|
|
1065
|
+
onChange: (e) => setField({ defaultProxy: e.target.value }),
|
|
1066
|
+
style: { padding: "6px 8px", borderRadius: 6, border: `1px solid ${defaultProxyError ? "#d66" : "#ccc"}` },
|
|
1067
|
+
disabled: !snap.writable
|
|
1068
|
+
}
|
|
1069
|
+
),
|
|
1070
|
+
defaultProxyError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#b33", fontSize: 11 }, children: defaultProxyError })
|
|
1071
|
+
] }),
|
|
1072
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
1073
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1074
|
+
"button",
|
|
1075
|
+
{
|
|
1076
|
+
onClick: onSave,
|
|
1077
|
+
disabled: !snap.writable || !dirty || saving,
|
|
1078
|
+
style: {
|
|
1079
|
+
padding: "8px 14px",
|
|
1080
|
+
borderRadius: 8,
|
|
1081
|
+
border: "1px solid #222",
|
|
1082
|
+
background: dirty ? "#222" : "#999",
|
|
1083
|
+
color: "#fff",
|
|
1084
|
+
cursor: dirty ? "pointer" : "not-allowed"
|
|
1085
|
+
},
|
|
1086
|
+
children: saving ? t("saving") : t("save")
|
|
1087
|
+
}
|
|
1088
|
+
),
|
|
1089
|
+
msg && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, opacity: 0.8 }, children: msg })
|
|
1090
|
+
] })
|
|
1091
|
+
] });
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// src/client/index.ts
|
|
1095
|
+
var inject = ["slots", "locale", "connection", "remote", "settingsScope"];
|
|
1096
|
+
function apply(ctx) {
|
|
1097
|
+
const t = ctx.locale.bind("settings.modelProxy");
|
|
1098
|
+
ctx.effect(() => ctx.locale.register("settings.modelProxy", { en, zh }), "model-proxy: locale");
|
|
1099
|
+
const scope = ctx.settingsScope.bind({ namespace: "model-proxy" });
|
|
1100
|
+
const ctrl = new ModelProxyController(scope);
|
|
1101
|
+
ctx.effect(() => {
|
|
1102
|
+
ctrl.bind();
|
|
1103
|
+
return () => ctrl.dispose();
|
|
1104
|
+
}, "model-proxy: scope bind");
|
|
1105
|
+
const cctx = ctx;
|
|
1106
|
+
const catalog = new ProviderCatalogStore(
|
|
1107
|
+
cctx.connection.api,
|
|
1108
|
+
cctx.remote
|
|
1109
|
+
);
|
|
1110
|
+
ctx.effect(() => {
|
|
1111
|
+
catalog.bind();
|
|
1112
|
+
return () => catalog.dispose();
|
|
1113
|
+
}, "model-proxy: catalog");
|
|
1114
|
+
ctx.slots.inject(
|
|
1115
|
+
"settings.plugin.item",
|
|
1116
|
+
() => ctx.slots.register(
|
|
1117
|
+
{
|
|
1118
|
+
name: "settings.plugin.item",
|
|
1119
|
+
key: "model-proxy",
|
|
1120
|
+
locale: "settings.modelProxy",
|
|
1121
|
+
inject: () => ({ controller: ctrl, catalog })
|
|
1122
|
+
},
|
|
1123
|
+
ModelProxyCard
|
|
1124
|
+
)
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
return module.exports; } });
|
|
1129
|
+
//# sourceMappingURL=client.js.map
|