@fonlan/dsh-model-router 0.3.5 → 0.3.6
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/lib/client.js +44 -68
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.tsx +40 -18
- package/src/client/locales.ts +3 -7
- package/src/client/{settings-card.css → settings-section.css} +19 -59
- package/src/client/{settings-card.tsx → settings-section.tsx} +159 -176
package/lib/client.js
CHANGED
|
@@ -11,9 +11,7 @@ window.__ModuleLoader__.load({
|
|
|
11
11
|
const LOCALE_NS = "model-router";
|
|
12
12
|
const zh = {
|
|
13
13
|
settingsTitle: "模型路由",
|
|
14
|
-
|
|
15
|
-
expand: "展开",
|
|
16
|
-
collapse: "收起",
|
|
14
|
+
sectionSub: "模型聚合、提供商切换与优先级顺序",
|
|
17
15
|
readOnly: "本部署的设置为只读。",
|
|
18
16
|
loading: "加载中…",
|
|
19
17
|
loadFailed: "加载失败:{message}",
|
|
@@ -64,9 +62,7 @@ window.__ModuleLoader__.load({
|
|
|
64
62
|
};
|
|
65
63
|
const en = {
|
|
66
64
|
settingsTitle: "Model Router",
|
|
67
|
-
|
|
68
|
-
expand: "Expand",
|
|
69
|
-
collapse: "Collapse",
|
|
65
|
+
sectionSub: "Model aggregation, provider switching and preference order",
|
|
70
66
|
readOnly: "This deployment stores settings read-only.",
|
|
71
67
|
loading: "Loading…",
|
|
72
68
|
loadFailed: "Load failed: {message}",
|
|
@@ -156,9 +152,9 @@ window.__ModuleLoader__.load({
|
|
|
156
152
|
setModelOrder: (order) => call("set-model-order", { order })
|
|
157
153
|
};
|
|
158
154
|
//#endregion
|
|
159
|
-
//#region \0dsh-css:/home/runner/work/dsh-model-router/dsh-model-router/src/client/settings-
|
|
160
|
-
const css$1 = "/* Model Router settings
|
|
161
|
-
const tagId$1 = "@fonlan/dsh-model-router/settings-
|
|
155
|
+
//#region \0dsh-css:/home/runner/work/dsh-model-router/dsh-model-router/src/client/settings-section.css.mjs
|
|
156
|
+
const css$1 = "/* Model Router settings page — the page owns one entry in the settings\n sidebar and renders its content in the panel's content column. Styles are\n theme-consistent via DSW alias variables; the page draws its own static\n header (title plus a one-line summary) over the always-visible controls. */\n\n/* page layout */\n\n.mr-page {\n display: flex;\n flex-direction: column;\n gap: 16px;\n max-width: 720px;\n}\n\n.mr-page-head {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.mr-page-title {\n margin: 0;\n color: var(--dsw-alias-label-primary, #e6e6e6);\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n}\n\n.mr-page-sub {\n margin: 0;\n color: var(--dsw-alias-label-tertiary, #8b93a7);\n font-size: 13px;\n line-height: 1.5;\n}\n\n/* body layout */\n\n.mr-body {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.mr-settings-readonly {\n color: var(--dsw-alias-label-tertiary, #8b93a7);\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n}\n\n/* ── page body: the router controls ─────────────────────────────────────── */\n\n.mr-root {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 0 0 4px;\n}\n\n.mr-quick-switch {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 8px 10px;\n border: 1px solid var(--dsw-alias-border-inverted);\n border-radius: 10px;\n background: var(--dsw-alias-bg-module-platform);\n cursor: pointer;\n}\n\n.mr-quick-switch input[type=\"checkbox\"] {\n margin-top: 2px;\n accent-color: var(--dsw-alias-accent);\n}\n\n.mr-quick-switch-copy {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.mr-quick-switch-label {\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n line-height: 20px;\n}\n\n.mr-quick-switch-desc {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.mr-toolbar {\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.mr-count {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.mr-button {\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n background: var(--dsw-alias-interactive-bg-hover);\n border: 1px solid var(--dsw-alias-border-l3);\n border-radius: 8px;\n font-size: 12px;\n line-height: 18px;\n padding: 3px 10px;\n}\n\n.mr-button:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n filter: brightness(1.08);\n}\n\n.mr-button:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.mr-error {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n color: var(--dsw-alias-state-error-primary);\n border-radius: 8px;\n padding: 7px 8px;\n font-size: 12px;\n line-height: 18px;\n}\n\n.mr-empty {\n color: var(--dsw-alias-label-tertiary);\n padding: 16px 0;\n font-size: 13px;\n line-height: 20px;\n}\n\n.mr-models {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.mr-card {\n border: 1px solid var(--dsw-alias-border-inverted);\n border-radius: 12px;\n padding: 10px 12px;\n background: var(--dsw-alias-bg-module-platform);\n transition: opacity 0.12s;\n}\n\n.mr-card-busy {\n opacity: 0.6;\n pointer-events: none;\n}\n\n.mr-card-header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.mr-model-name {\n color: var(--dsw-alias-label-primary);\n font-size: 14px;\n font-weight: 600;\n line-height: 20px;\n}\n\n.mr-model-id {\n color: var(--dsw-alias-label-tertiary);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12px;\n line-height: 18px;\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n min-width: 0;\n flex: 1;\n}\n\n.mr-provider-count {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n flex: none;\n}\n\n.mr-drag-hint {\n margin: 2px 0 6px;\n color: var(--dsw-alias-label-caption);\n font-size: 11px;\n line-height: 16px;\n}\n\n.mr-providers {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.mr-provider {\n display: flex;\n align-items: center;\n gap: 6px;\n border: 1px solid transparent;\n border-radius: 10px;\n padding: 2px 8px 2px 4px;\n}\n\n.mr-provider-active {\n background: var(--dsw-alias-interactive-bg-hover);\n border-color: var(--dsw-alias-border-l3);\n}\n\n.mr-provider-nocred {\n opacity: 0.55;\n}\n\n.mr-provider-drop {\n border-color: var(--dsw-alias-border-brand);\n}\n\n.mr-grip {\n color: var(--dsw-alias-label-caption);\n cursor: grab;\n font-size: 12px;\n line-height: 20px;\n flex: none;\n user-select: none;\n}\n\n.mr-grip:active {\n cursor: grabbing;\n}\n\n.mr-provider-button {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: baseline;\n gap: 6px;\n color: var(--dsw-alias-label-primary);\n background: none;\n border: none;\n text-align: left;\n cursor: pointer;\n font: inherit;\n padding: 4px 0;\n}\n\n.mr-provider-button:disabled {\n cursor: default;\n}\n\n.mr-provider-name {\n font-size: 13px;\n line-height: 20px;\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.mr-provider-nocred-label {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n flex: none;\n}\n\n.mr-active-badge {\n flex: none;\n color: var(--dsw-alias-label-inverted, #fff);\n background: var(--dsw-alias-border-brand, #4d6bfe);\n border-radius: 999px;\n font-size: 11px;\n line-height: 16px;\n padding: 0 8px;\n}\n\n/* ── model display order ────────────────────────────────────────────────── */\n\n.mr-sort {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 8px 10px;\n border: 1px solid var(--dsw-alias-border-inverted);\n border-radius: 10px;\n background: var(--dsw-alias-bg-module-platform);\n}\n\n.mr-sort-head {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.mr-sort-title {\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n line-height: 20px;\n}\n\n.mr-sort-desc {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.mr-sort-modes {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.mr-sort-mode {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n border: 1px solid transparent;\n border-radius: 8px;\n padding: 5px 6px;\n cursor: pointer;\n}\n\n.mr-sort-mode:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mr-sort-mode input[type=\"radio\"] {\n margin-top: 2px;\n accent-color: var(--dsw-alias-accent);\n}\n\n.mr-sort-mode-copy {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n}\n\n.mr-sort-mode-label {\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n line-height: 20px;\n}\n\n.mr-sort-mode-desc {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.mr-sort-custom {\n display: flex;\n flex-direction: column;\n gap: 6px;\n border-top: 1px solid var(--dsw-alias-border-l2, #2a3346);\n padding-top: 8px;\n}\n\n.mr-sort-hint {\n color: var(--dsw-alias-label-caption);\n margin: 0;\n font-size: 11px;\n line-height: 16px;\n}\n\n.mr-sort-list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n max-height: 280px;\n overflow-y: auto;\n}\n\n.mr-sort-item {\n display: flex;\n align-items: center;\n gap: 6px;\n border: 1px solid var(--dsw-alias-border-l2, #2a3346);\n border-radius: 8px;\n padding: 4px 8px 4px 4px;\n background: var(--dsw-alias-bg-layer-1, #0d1017);\n}\n\n.mr-sort-item-drop {\n border-color: var(--dsw-alias-border-brand);\n}\n\n.mr-sort-item-busy {\n opacity: 0.6;\n pointer-events: none;\n}\n\n.mr-sort-model-name {\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n line-height: 20px;\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n min-width: 0;\n}\n\n.mr-sort-model-id {\n color: var(--dsw-alias-label-tertiary);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n line-height: 16px;\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n flex: 1;\n text-align: right;\n}\n";
|
|
157
|
+
const tagId$1 = "@fonlan/dsh-model-router/settings-section.css";
|
|
162
158
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
163
159
|
const tag = document.createElement("style");
|
|
164
160
|
tag.dataset.plugin = "@fonlan/dsh-model-router";
|
|
@@ -167,23 +163,23 @@ window.__ModuleLoader__.load({
|
|
|
167
163
|
document.head.appendChild(tag);
|
|
168
164
|
}
|
|
169
165
|
//#endregion
|
|
170
|
-
//#region src/client/settings-
|
|
166
|
+
//#region src/client/settings-section.tsx
|
|
171
167
|
/**
|
|
172
|
-
* The Model Router
|
|
168
|
+
* The Model Router settings page (设置 → 侧栏「模型路由」).
|
|
173
169
|
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* the
|
|
170
|
+
* Registers into the settings.section slot, so the page owns one entry in the
|
|
171
|
+
* settings sidebar and renders its content in the panel's content column. The
|
|
172
|
+
* page draws its own static header (title plus a one-line summary) over the
|
|
173
|
+
* router controls: every routed model with its providers, their order and the
|
|
174
|
+
* provider the model currently routes to. All reads/mutations go through the
|
|
175
|
+
* plugin's fenced API; the server persists through the `model-router` settings
|
|
176
|
+
* namespace, so a switch here is live for the next request globally.
|
|
180
177
|
*
|
|
181
178
|
* The bound settings scope (the same `model-router` namespace the host half
|
|
182
|
-
* registers) supplies the
|
|
183
|
-
* loading the
|
|
184
|
-
* without the host half) nothing renders
|
|
185
|
-
*
|
|
186
|
-
* mutation controls.
|
|
179
|
+
* registers) supplies the page's dispatch state: while the namespace is merely
|
|
180
|
+
* loading the page stays mounted, and when it is unavailable (deployment
|
|
181
|
+
* without the host half) nothing renders. A read-only deployment shows the
|
|
182
|
+
* banner and disables the mutation controls.
|
|
187
183
|
*/
|
|
188
184
|
function arrayMove(list, from, to) {
|
|
189
185
|
const next = [...list];
|
|
@@ -208,7 +204,7 @@ window.__ModuleLoader__.load({
|
|
|
208
204
|
}, [ctx]);
|
|
209
205
|
return (0, react.useSyncExternalStore)(subscribe, getSnapshot);
|
|
210
206
|
}
|
|
211
|
-
function
|
|
207
|
+
function makeSettingsSection(ctx) {
|
|
212
208
|
const t = (() => {
|
|
213
209
|
try {
|
|
214
210
|
return ctx.locale.bind(LOCALE_NS);
|
|
@@ -216,11 +212,10 @@ window.__ModuleLoader__.load({
|
|
|
216
212
|
return (key) => key;
|
|
217
213
|
}
|
|
218
214
|
})();
|
|
219
|
-
return function
|
|
215
|
+
return function ModelRouterSettingsSection(props) {
|
|
220
216
|
const { scope } = props;
|
|
221
217
|
const snapshot = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot());
|
|
222
218
|
useLocaleRevision(ctx);
|
|
223
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
224
219
|
const [state, setState] = (0, react.useState)(null);
|
|
225
220
|
const [error, setError] = (0, react.useState)(null);
|
|
226
221
|
const [loading, setLoading] = (0, react.useState)(true);
|
|
@@ -370,44 +365,19 @@ window.__ModuleLoader__.load({
|
|
|
370
365
|
if (snapshot.status === "unavailable") return null;
|
|
371
366
|
const writable = snapshot.writable;
|
|
372
367
|
const models = (0, react.useMemo)(() => state?.models ?? [], [state]);
|
|
373
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
374
|
-
className: "mr-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
className: "mr-settings-head-text",
|
|
384
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
385
|
-
className: "mr-settings-title",
|
|
386
|
-
children: t("settingsTitle")
|
|
387
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
388
|
-
className: "mr-settings-sub",
|
|
389
|
-
children: t("settingsCardDescription")
|
|
390
|
-
})]
|
|
391
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
392
|
-
className: "mr-settings-chevron",
|
|
393
|
-
"data-open": open ? "" : void 0,
|
|
394
|
-
"aria-hidden": "true",
|
|
395
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
396
|
-
width: "14",
|
|
397
|
-
height: "14",
|
|
398
|
-
viewBox: "0 0 14 14",
|
|
399
|
-
fill: "none",
|
|
400
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
401
|
-
d: "M3.5 5.25L7 8.75L10.5 5.25",
|
|
402
|
-
stroke: "currentColor",
|
|
403
|
-
strokeWidth: "1.5",
|
|
404
|
-
strokeLinecap: "round",
|
|
405
|
-
strokeLinejoin: "round"
|
|
406
|
-
})
|
|
407
|
-
})
|
|
368
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
369
|
+
className: "mr-page",
|
|
370
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
371
|
+
className: "mr-page-head",
|
|
372
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
373
|
+
className: "mr-page-title",
|
|
374
|
+
children: t("settingsTitle")
|
|
375
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
376
|
+
className: "mr-page-sub",
|
|
377
|
+
children: t("sectionSub")
|
|
408
378
|
})]
|
|
409
|
-
}),
|
|
410
|
-
className: "mr-
|
|
379
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
380
|
+
className: "mr-body",
|
|
411
381
|
children: [!writable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
412
382
|
className: "mr-settings-readonly",
|
|
413
383
|
role: "status",
|
|
@@ -1309,8 +1279,10 @@ window.__ModuleLoader__.load({
|
|
|
1309
1279
|
}
|
|
1310
1280
|
//#endregion
|
|
1311
1281
|
//#region src/client/index.tsx
|
|
1312
|
-
/** The settings namespace this
|
|
1282
|
+
/** The settings namespace this page edits (must match the host half). */
|
|
1313
1283
|
const ROUTER_NS = "model-router";
|
|
1284
|
+
/** The settings sidebar entry this page owns (must stay stable). */
|
|
1285
|
+
const SECTION_ID = "model-router";
|
|
1314
1286
|
/** Services required before mounting (provided by the client runtime).
|
|
1315
1287
|
* `remote.session` is included like the host ui-model-selection declares it:
|
|
1316
1288
|
* the seat's `directoryFor` depends on the namespace, which mounts
|
|
@@ -1334,14 +1306,18 @@ window.__ModuleLoader__.load({
|
|
|
1334
1306
|
});
|
|
1335
1307
|
return () => off();
|
|
1336
1308
|
}, "model-router: dictionaries");
|
|
1337
|
-
const
|
|
1309
|
+
const t = ctx.locale.bind(LOCALE_NS);
|
|
1310
|
+
const SettingsSection = makeSettingsSection(ctx);
|
|
1338
1311
|
const services = ctx;
|
|
1339
1312
|
const scope = services.settingsScope.bind({ namespace: ROUTER_NS });
|
|
1340
|
-
services.slots.inject("settings.
|
|
1341
|
-
name: "settings.
|
|
1342
|
-
|
|
1313
|
+
services.slots.inject("settings.section", () => services.slots.register({
|
|
1314
|
+
name: "settings.section",
|
|
1315
|
+
id: SECTION_ID,
|
|
1316
|
+
order: 300,
|
|
1317
|
+
label: () => t("settingsTitle"),
|
|
1318
|
+
locale: LOCALE_NS,
|
|
1343
1319
|
inject: () => ({ scope })
|
|
1344
|
-
},
|
|
1320
|
+
}, SettingsSection));
|
|
1345
1321
|
registerModelRouterSeat(ctx);
|
|
1346
1322
|
}
|
|
1347
1323
|
//#endregion
|