dsh-multi-router 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +21 -6
  3. package/THIRD_PARTY_NOTICES.md +4 -0
  4. package/cordis.patch.yml +2 -2
  5. package/lib/accounts.js +2 -2
  6. package/lib/api-service.js +3 -3
  7. package/lib/{chunk-NSKCFVHP.js → chunk-2LH6TD2U.js} +15 -7
  8. package/lib/chunk-2LH6TD2U.js.map +7 -0
  9. package/lib/{chunk-SNEZMYNU.js → chunk-625KMEYS.js} +3 -3
  10. package/lib/{chunk-LLDJVBTQ.js → chunk-BSAI3SW5.js} +3 -3
  11. package/lib/{chunk-ROZINFTV.js → chunk-DZ4ZUAYH.js} +49 -6
  12. package/lib/chunk-DZ4ZUAYH.js.map +7 -0
  13. package/lib/{chunk-WVNY4LSL.js → chunk-NGVWVIBM.js} +20 -6
  14. package/lib/chunk-NGVWVIBM.js.map +7 -0
  15. package/lib/{chunk-JUOESKAK.js → chunk-VZPXRK7Q.js} +2 -2
  16. package/lib/{chunk-OCMOMSB4.js → chunk-ZQZSXCVR.js} +125 -41
  17. package/lib/chunk-ZQZSXCVR.js.map +7 -0
  18. package/lib/cli.js +4 -4
  19. package/lib/client.js +125 -40
  20. package/lib/client.js.map +2 -2
  21. package/lib/clients.js +6 -3
  22. package/lib/{host-v0.6.0.js → host-v0.6.1.js} +8 -7
  23. package/lib/host.js +8 -7
  24. package/lib/index.js +8 -7
  25. package/lib/index.js.map +2 -2
  26. package/lib/manager.js +6 -6
  27. package/lib/router.js +2 -2
  28. package/lib/shim.js +3 -3
  29. package/package.json +1 -1
  30. package/lib/chunk-NSKCFVHP.js.map +0 -7
  31. package/lib/chunk-OCMOMSB4.js.map +0 -7
  32. package/lib/chunk-ROZINFTV.js.map +0 -7
  33. package/lib/chunk-WVNY4LSL.js.map +0 -7
  34. /package/lib/{chunk-SNEZMYNU.js.map → chunk-625KMEYS.js.map} +0 -0
  35. /package/lib/{chunk-LLDJVBTQ.js.map → chunk-BSAI3SW5.js.map} +0 -0
  36. /package/lib/{chunk-JUOESKAK.js.map → chunk-VZPXRK7Q.js.map} +0 -0
package/lib/client.js CHANGED
@@ -23,7 +23,9 @@ __export(client_exports, {
23
23
  AccountPool: () => AccountPool,
24
24
  ApiServicePanel: () => ApiServicePanel,
25
25
  CheckinPanel: () => CheckinPanel,
26
+ ContextSelect: () => ContextSelect,
26
27
  InitialSetup: () => InitialSetup,
28
+ ModelSettings: () => ModelSettings,
27
29
  ModelsPanel: () => ModelsPanel,
28
30
  PluginUpdatesPanel: () => PluginUpdatesPanel,
29
31
  RestartGuidance: () => RestartGuidance,
@@ -47,7 +49,7 @@ module.exports = __toCommonJS(client_exports);
47
49
  var import_react = require("react");
48
50
 
49
51
  // package.json
50
- var version = "0.6.0";
52
+ var version = "0.6.1";
51
53
 
52
54
  // src/client.jsx
53
55
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -416,10 +418,10 @@ function RouterPage({ view = "page" }) {
416
418
  try {
417
419
  const res = await fetch(ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json", "X-Multi-Router-Key": data.key }, body: JSON.stringify({ revision: data.revision, ...action }) });
418
420
  const next = await readManagementResponse(res);
419
- const { exported, workbuddyLogin, loginResult, apiKey, ...state } = next;
421
+ const { exported, workbuddyLogin, loginResult, apiKey, modelTest, ...state } = next;
420
422
  setData(state);
421
423
  if (action.sidebarUsage !== void 0 || action.action === "config.import") window.dispatchEvent(new Event(SUMMARY_EVENT));
422
- setNotice(action.action.startsWith("api.") ? next.apiService?.phase === "error" ? "\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u4F46\u670D\u52A1\u5C1A\u672A\u542F\u52A8\uFF0C\u8BF7\u67E5\u770B\u4E0B\u65B9\u63D0\u793A\u3002" : action.action === "api.key.reveal" ? "" : "API \u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002" : action.action === "workbuddy.sms" ? "" : action.action === "workbuddy.login" ? loginResult?.warning || "\u767B\u5F55\u51ED\u8BC1\u5DF2\u4FDD\u5B58\uFF0C\u81EA\u52A8\u7B7E\u5230\u5F00\u5173\u4FDD\u6301\u539F\u8BBE\u7F6E\u3002" : action.action === "checkins.verify" ? "\u767B\u5F55\u9A8C\u8BC1\u901A\u8FC7\uFF0C\u65E7\u7684\u4EE4\u724C\u5931\u6548\u63D0\u793A\u5DF2\u89E3\u9664\uFF1B\u672A\u63D0\u4EA4\u7B7E\u5230\u3002" : action.action === "checkins.settings" ? "\u7B7E\u5230\u8BA1\u5212\u5DF2\u4FDD\u5B58\uFF1B\u4ECA\u65E5\u5DF2\u5904\u7406\u8BB0\u5F55\u4FDD\u6301\u4E0D\u53D8\u3002" : action.action.startsWith("checkins.") || action.action.startsWith("plugins.") || action.action === "setup.dismiss" ? "" : action.action === "accounts.discover" || action.action === "monitor.refresh" ? "\u5DF2\u91CD\u65B0\u68C0\u6D4B\u672C\u673A\u767B\u5F55\u5E76\u5237\u65B0\u7528\u91CF\uFF1B\u68C0\u6D4B\u7ED3\u679C\u89C1\u4E0B\u65B9\u3002" : action.action.includes("refresh") ? "\u5DF2\u5237\u65B0\u3002\u65E0\u6CD5\u83B7\u53D6\u7684\u8D26\u53F7\u4F1A\u4FDD\u7559\u4E0A\u6B21\u6570\u636E\u5E76\u6807\u8BB0\u3002" : action.action === "config.import" ? "\u914D\u7F6E\u5DF2\u5BFC\u5165\u5E76\u751F\u6548\uFF0C\u539F\u914D\u7F6E\u5DF2\u5907\u4EFD\uFF1B\u6A21\u578B\u548C\u7528\u91CF\u6B63\u5728\u540E\u53F0\u66F4\u65B0\u3002" : action.action.startsWith("config.") || action.action === "account.export" ? "" : "\u5DF2\u4FDD\u5B58\u5E76\u751F\u6548\u3002");
424
+ setNotice(action.action === "model.test" ? "" : action.action.startsWith("api.") ? next.apiService?.phase === "error" ? "\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u4F46\u670D\u52A1\u5C1A\u672A\u542F\u52A8\uFF0C\u8BF7\u67E5\u770B\u4E0B\u65B9\u63D0\u793A\u3002" : action.action === "api.key.reveal" ? "" : "API \u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002" : action.action === "workbuddy.sms" ? "" : action.action === "workbuddy.login" ? loginResult?.warning || "\u767B\u5F55\u51ED\u8BC1\u5DF2\u4FDD\u5B58\uFF0C\u81EA\u52A8\u7B7E\u5230\u5F00\u5173\u4FDD\u6301\u539F\u8BBE\u7F6E\u3002" : action.action === "checkins.verify" ? "\u767B\u5F55\u9A8C\u8BC1\u901A\u8FC7\uFF0C\u65E7\u7684\u4EE4\u724C\u5931\u6548\u63D0\u793A\u5DF2\u89E3\u9664\uFF1B\u672A\u63D0\u4EA4\u7B7E\u5230\u3002" : action.action === "checkins.settings" ? "\u7B7E\u5230\u8BA1\u5212\u5DF2\u4FDD\u5B58\uFF1B\u4ECA\u65E5\u5DF2\u5904\u7406\u8BB0\u5F55\u4FDD\u6301\u4E0D\u53D8\u3002" : action.action.startsWith("checkins.") || action.action.startsWith("plugins.") || action.action === "setup.dismiss" ? "" : action.action === "accounts.discover" || action.action === "monitor.refresh" ? "\u5DF2\u91CD\u65B0\u68C0\u6D4B\u672C\u673A\u767B\u5F55\u5E76\u5237\u65B0\u7528\u91CF\uFF1B\u68C0\u6D4B\u7ED3\u679C\u89C1\u4E0B\u65B9\u3002" : action.action.includes("refresh") ? "\u5DF2\u5237\u65B0\u3002\u65E0\u6CD5\u83B7\u53D6\u7684\u8D26\u53F7\u4F1A\u4FDD\u7559\u4E0A\u6B21\u6570\u636E\u5E76\u6807\u8BB0\u3002" : action.action === "config.import" ? "\u914D\u7F6E\u5DF2\u5BFC\u5165\u5E76\u751F\u6548\uFF0C\u539F\u914D\u7F6E\u5DF2\u5907\u4EFD\uFF1B\u6A21\u578B\u548C\u7528\u91CF\u6B63\u5728\u540E\u53F0\u66F4\u65B0\u3002" : action.action.startsWith("config.") || action.action === "account.export" ? "" : "\u5DF2\u4FDD\u5B58\u5E76\u751F\u6548\u3002");
423
425
  return next;
424
426
  } catch (e) {
425
427
  setError(e.message);
@@ -523,7 +525,7 @@ function RouterPage({ view = "page" }) {
523
525
  tab === "checkins" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckinPanel, { data, busy, act }),
524
526
  tab === "usage" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UsageDetails, { data, busy, act }),
525
527
  tab === "accounts" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccountPool, { data, provider: accountProvider, providerSelect: accountProviderSelect, busy, act, manage: (a) => openAccount("edit", a), add: () => openAccount("add") }, accountProvider),
526
- tab === "models" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModelsPanel, { data, provider, providerSelect, search, setSearch, busy, act }),
528
+ tab === "models" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModelsPanel, { error, notice, data, provider, providerSelect, search, setSearch, busy, act }),
527
529
  tab === "routes" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "stack", children: [
528
530
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bar", children: [
529
531
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
@@ -542,7 +544,7 @@ function RouterPage({ view = "page" }) {
542
544
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary", disabled: busy, onClick: () => {
543
545
  setError("");
544
546
  setNotice("");
545
- setDraft({ id: "", name: "", revision: data.revision, contextWindow: 2e5, maxTokens: 32e3, strategy: "failover", preferExpiringCredits: true, expiringWithinDays: 7, visible: true, targets: [newTarget()] });
547
+ setDraft({ id: "", name: "", revision: data.revision, contextMode: "maximum", contextWindow: 2e5, maxTokens: 32e3, strategy: "failover", preferExpiringCredits: true, expiringWithinDays: 7, visible: true, targets: [newTarget()] });
546
548
  }, children: "\u65B0\u5EFA\u8DEF\u7531" })
547
549
  ] })
548
550
  ] }),
@@ -578,10 +580,10 @@ function RouterPage({ view = "page" }) {
578
580
  presetModal && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Modal, { title: "\u6A21\u578B\u8DEF\u7531\u9884\u914D\u7F6E", busy, close: () => setPresetModal(false), children: [
579
581
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-body stack", children: [
580
582
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Feedback, { error, notice }),
581
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "steps", children: "Free \u81EA\u52A8\u53D1\u73B0\u5404\u8D26\u53F7\u7684\u514D\u8D39\u6A21\u578B\uFF1B\u5176\u4ED6\u9884\u914D\u7F6E\u7ED1\u5B9A\u4E09\u5BB6\u5171\u6709\u6A21\u578B\u3002\u5DF2\u6709\u8DEF\u7531\u4E0D\u4F1A\u88AB\u8986\u76D6\u3002" }),
583
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "steps", children: "Free \u81EA\u52A8\u53D1\u73B0\u5404\u8D26\u53F7\u7684\u514D\u8D39\u6A21\u578B\uFF1B\u5176\u4ED6\u9884\u914D\u7F6E\u7ED1\u5B9A\u5DF2\u6838\u5BF9\u7684\u6E20\u9053\u6A21\u578B\u3002\u5DF2\u6709\u8DEF\u7531\u4E0D\u4F1A\u88AB\u8986\u76D6\u3002" }),
582
584
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid", children: (data.presets || []).map((preset) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "box stack", children: [
583
585
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: preset.name }) }),
584
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: preset.freeOnly ? "\u6BCF\u5929\u81EA\u52A8\u68C0\u67E5\u514D\u8D39\u6A21\u578B\uFF0C\u8C03\u7528\u524D\u590D\u6838\u96F6\u500D\u7387\uFF1B\u4EC5\u5728\u514D\u8D39\u6A21\u578B\u95F4\u5207\u6362\u3002" : `\u5DF2\u53D1\u73B0\u53EF\u7528\u6E20\u9053\uFF1A${preset.availableProviders.length} / 3${preset.availableProviders.length < 3 ? " \xB7 \u53EF\u6DFB\u52A0\u8D26\u53F7\u6216\u5237\u65B0\u6A21\u578B\u76EE\u5F55\u540E\u4F7F\u7528" : ""}` }),
586
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: preset.freeOnly ? "\u6BCF\u5929\u81EA\u52A8\u68C0\u67E5\u514D\u8D39\u6A21\u578B\uFF0C\u8C03\u7528\u524D\u590D\u6838\u96F6\u500D\u7387\uFF1B\u4EC5\u5728\u514D\u8D39\u6A21\u578B\u95F4\u5207\u6362\u3002" : `\u5DF2\u53D1\u73B0\u53EF\u7528\u6E20\u9053\uFF1A${preset.availableProviders.length} / ${preset.providerCount ?? preset.targets.length}${preset.availableProviders.length < (preset.providerCount ?? preset.targets.length) ? " \xB7 \u53EF\u6DFB\u52A0\u8D26\u53F7\u6216\u5237\u65B0\u6A21\u578B\u76EE\u5F55\u540E\u4F7F\u7528" : ""}` }),
585
587
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy || preset.added, onClick: () => {
586
588
  void act({ action: "preset.add", id: preset.id });
587
589
  }, children: preset.added ? "\u5DF2\u6DFB\u52A0" : "\u6DFB\u52A0\u6B64\u8DEF\u7531" })
@@ -813,8 +815,8 @@ function ApiModels({ data, busy, save }) {
813
815
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "modal-foot row", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary", disabled: busy, children: "\u4FDD\u5B58\u5F00\u653E\u8303\u56F4" }) })
814
816
  ] });
815
817
  }
816
- function ModelsPanel({ data, provider, providerSelect, search, setSearch, busy, act }) {
817
- const [editing, setEditing] = (0, import_react.useState)(null);
818
+ function ModelsPanel({ error = "", notice = "", data, provider, providerSelect, search, setSearch, busy, act }) {
819
+ const [editing, setEditing] = (0, import_react.useState)(null), [testing, setTesting] = (0, import_react.useState)(null), [testResult, setTestResult] = (0, import_react.useState)(null);
818
820
  const models = data.providers.find((p) => p.id === provider)?.models ?? [];
819
821
  const all = data.providers.flatMap((p) => p.models), shown = models.filter((m) => `${m.id} ${m.name}`.toLowerCase().includes(search.toLowerCase()));
820
822
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "stack", children: [
@@ -865,8 +867,14 @@ function ModelsPanel({ data, provider, providerSelect, search, setSearch, busy,
865
867
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "mono muted", children: m.id })
866
868
  ] }),
867
869
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
868
- fmt(m.contextWindow / 1e3),
869
- "K",
870
+ capacity(m.contextWindow),
871
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { className: "muted", children: [
872
+ "\u6700\u5927 ",
873
+ capacity(m.maxContextWindow ?? m.contextWindow),
874
+ " \xB7 \u8F93\u51FA ",
875
+ capacity(m.maxTokens),
876
+ m.supportsImages ? " \xB7 \u56FE\u7247" : ""
877
+ ] }),
870
878
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "muted", children: m.reasoning?.supportedEfforts?.length ? m.reasoning.supportedEfforts.map((e) => effortNames[e] || e).join(" / ") : "\u5E73\u53F0\u9ED8\u8BA4" })
871
879
  ] }),
872
880
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "check", children: [
@@ -875,29 +883,94 @@ function ModelsPanel({ data, provider, providerSelect, search, setSearch, busy,
875
883
  } }),
876
884
  m.visible ? "\u5DF2\u5C55\u793A" : "\u5DF2\u9690\u85CF"
877
885
  ] }) }),
878
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "cell-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => setEditing({ id: m.id, name: m.name, provider }), children: "\u4FEE\u6539\u540D\u79F0" }) })
886
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { className: "cell-actions", children: [
887
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => setEditing({ id: m.id, name: m.name, provider, contextWindow: m.configuredContextWindow ?? null, supportsImages: m.supportsImages, reasoningEffort: m.configuredEffort ?? null }), children: "\u8BBE\u7F6E" }),
888
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => {
889
+ setTesting(m);
890
+ setTestResult(null);
891
+ }, children: "\u6D4B\u8BD5" })
892
+ ] })
879
893
  ] }, m.id)) })
880
894
  ] }),
881
895
  !shown.length && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "empty", children: models.length ? "\u6CA1\u6709\u5339\u914D\u7684\u6A21\u578B\u3002" : "\u6DFB\u52A0\u5E76\u542F\u7528\u8D26\u53F7\u540E\uFF0C\u83B7\u53D6\u6A21\u578B\u76EE\u5F55\u3002" })
882
896
  ] }),
883
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "table-footer", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u9690\u85CF\u4E0D\u5F71\u54CD\u8DEF\u7531\u6216\u5DF2\u6709\u4F1A\u8BDD\u3002\u6279\u91CF\u8BBE\u7F6E\u4F5C\u7528\u4E8E\u5168\u90E8\u4F9B\u5E94\u5546\u53CA\u540E\u7EED\u53D1\u73B0\u7684\u6A21\u578B\u3002" }) })
897
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "table-footer", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u4E0A\u4E0B\u6587\u9ED8\u8BA4\u6700\u5927\uFF0C\u53EF\u5728\u8BBE\u7F6E\u4E2D\u8C03\u4F4E\uFF1B\u539F\u59CB\u6A21\u578B\u4E0E\u8DEF\u7531\u9884\u7B97\u72EC\u7ACB\u3002\u9690\u85CF\u4E0D\u5F71\u54CD\u8DEF\u7531\u6216\u5DF2\u6709\u4F1A\u8BDD\u3002" }) })
884
898
  ] }),
885
- editing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Modal, { title: "\u4FEE\u6539\u6A21\u578B\u540D\u79F0", busy, close: () => setEditing(null), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: async (e) => {
886
- e.preventDefault();
899
+ editing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Modal, { title: "\u6A21\u578B\u8BBE\u7F6E", busy, close: () => setEditing(null), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModelSettings, { error, notice, model: models.find((m) => m.id === editing.id), draft: editing, setDraft: setEditing, busy, save: async () => {
887
900
  if (await act({ action: "model.update", ...editing })) setEditing(null);
901
+ } }) }),
902
+ testing && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Modal, { title: `\u6D4B\u8BD5 ${testing.name}`, busy, close: () => {
903
+ setTesting(null);
904
+ setTestResult(null);
888
905
  }, children: [
889
906
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-body stack", children: [
890
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
891
- "\u6A21\u578B\u540D\u79F0",
892
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { autoFocus: true, required: true, maxLength: 200, disabled: busy, value: editing.name, onChange: (e) => setEditing({ ...editing, name: e.target.value }) })
893
- ] }),
894
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mono muted", children: editing.id })
907
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Feedback, { error, notice }),
908
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "steps", children: "\u53D1\u9001\u4E00\u6761\u7B80\u77ED\u8BF7\u6C42\uFF0C\u6B63\u5E38\u6D88\u8017\u76EE\u6807\u8D26\u53F7\u7684\u7528\u91CF\u3002\u6B64\u6D4B\u8BD5\u4EC5\u68C0\u67E5\u8FDE\u901A\u6027\uFF0C\u4E0D\u4EE3\u8868\u5DF2\u9A8C\u8BC1\u6EE1\u4E0A\u4E0B\u6587\u6216\u56FE\u7247\u8BC6\u522B\u80FD\u529B\u3002" }),
909
+ testResult && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", className: testResult.ok ? "good" : "warn", children: testResult.ok ? `\u53EF\u7528 \xB7 ${testResult.elapsedMs} ms` : `\u6D4B\u8BD5\u5931\u8D25\uFF08HTTP ${testResult.status}\uFF09\xB7 ${testResult.hint}` })
895
910
  ] }),
896
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-foot row", children: [
897
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy, onClick: () => setEditing(null), children: "\u53D6\u6D88" }),
898
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary", disabled: busy, children: "\u4FDD\u5B58\u540D\u79F0" })
899
- ] })
900
- ] }) })
911
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "modal-foot row", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: async () => {
912
+ setTestResult(null);
913
+ const result = await act({ action: "model.test", provider, id: testing.id });
914
+ if (result) setTestResult(result.modelTest);
915
+ }, children: busy ? "\u6B63\u5728\u6D4B\u8BD5\u2026" : "\u5F00\u59CB\u6D4B\u8BD5" }) })
916
+ ] })
917
+ ] });
918
+ }
919
+ var capacity = (n) => n >= 1e6 && n % 1e6 === 0 ? `${n / 1e6}M` : `${fmt(n / 1e3)}K`;
920
+ function ContextSelect({ model, value, onChange, disabled, label = "\u4E0A\u4E0B\u6587\u9884\u7B97" }) {
921
+ const max = model.maxContextWindow ?? model.contextWindow;
922
+ const choices = [...new Set([model.defaultContextWindow, ...model.supportedContextWindows ?? [], 2e5, 3e5, 5e5, 6e5, value].filter((n) => Number.isSafeInteger(n) && n > 0 && n <= max))].sort((a, b) => a - b);
923
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
924
+ label,
925
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { "aria-label": label, disabled, value: value ?? "", onChange: (e) => onChange(e.target.value ? Number(e.target.value) : null), children: [
926
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", { value: "", children: [
927
+ "\u6700\u5927\u5BB9\u91CF \xB7 ",
928
+ capacity(max),
929
+ "\uFF08\u9ED8\u8BA4\uFF09"
930
+ ] }),
931
+ choices.map((n) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", { value: n, children: [
932
+ capacity(n),
933
+ n === model.defaultContextWindow ? " \xB7 \u6E20\u9053\u9ED8\u8BA4" : " \xB7 \u672C\u5730\u9884\u7B97"
934
+ ] }, n))
935
+ ] })
936
+ ] });
937
+ }
938
+ function ModelSettings({ error = "", notice = "", model, draft, setDraft, busy, save }) {
939
+ const set = (key, value) => setDraft((d) => ({ ...d, [key]: value }));
940
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: (e) => {
941
+ e.preventDefault();
942
+ void save();
943
+ }, children: [
944
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-body stack", children: [
945
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Feedback, { error, notice }),
946
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
947
+ "\u6A21\u578B\u540D\u79F0",
948
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { autoFocus: true, required: true, maxLength: 200, disabled: busy, value: draft.name, onChange: (e) => set("name", e.target.value) })
949
+ ] }),
950
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mono muted", children: model.id }),
951
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContextSelect, { model, value: draft.contextWindow, disabled: busy, onChange: (n) => set("contextWindow", n) }),
952
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "notice", children: [
953
+ "\u6700\u5927\u4E0A\u4E0B\u6587 ",
954
+ capacity(model.maxContextWindow),
955
+ " \xB7 \u6700\u5927\u8F93\u51FA ",
956
+ capacity(model.maxTokens),
957
+ "\u3002\u624B\u52A8\u9884\u7B97\u53EA\u8C03\u4F4E DSH \u7684\u4F1A\u8BDD\u538B\u7F29\u9608\u503C\uFF1B\u8BF7\u6C42\u4F7F\u7528\u80FD\u591F\u5BB9\u7EB3\u8BE5\u9884\u7B97\u7684\u6E20\u9053\u6863\u4F4D\u3002"
958
+ ] }),
959
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "check", children: [
960
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", disabled: busy || !model.imageCapable, checked: draft.supportsImages, onChange: (e) => set("supportsImages", e.target.checked) }),
961
+ "\u56FE\u7247\u8F93\u5165",
962
+ !model.imageCapable && "\uFF08\u6E20\u9053\u672A\u58F0\u660E\u652F\u6301\uFF09"
963
+ ] }),
964
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
965
+ "\u9ED8\u8BA4\u63A8\u7406\u7B49\u7EA7",
966
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { disabled: busy, value: draft.reasoningEffort ?? "", onChange: (e) => set("reasoningEffort", e.target.value || null), children: [
967
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5E73\u53F0\u9ED8\u8BA4" }),
968
+ model.reasoning?.supportedEfforts?.map((e) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: e, children: effortNames[e] || e }, e))
969
+ ] })
970
+ ] }),
971
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u5173\u95ED\u601D\u8003\u4EC5\u5728\u6E20\u9053\u58F0\u660E\u652F\u6301\u65F6\u63D0\u4F9B\u3002\u4F1A\u8BDD\u4E2D\u7684\u624B\u52A8\u9009\u62E9\u4F18\u5148\u4E8E\u6B64\u9ED8\u8BA4\u503C\u3002" })
972
+ ] }),
973
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "modal-foot row", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary", disabled: busy, children: "\u4FDD\u5B58\u5E76\u751F\u6548" }) })
901
974
  ] });
902
975
  }
903
976
  function RoutesTable({ data, busy, act, edit }) {
@@ -911,12 +984,13 @@ function RoutesTable({ data, busy, act, edit }) {
911
984
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", className: "cell-actions", children: "\u7BA1\u7406" })
912
985
  ] }) }),
913
986
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: data.routes.map((r) => {
914
- const accounts = data.accounts.filter((a) => a.enabled !== false && r.targets.some((t) => t.provider === a.provider && (!t.account || t.account === a.id)));
987
+ const accounts = data.accounts.filter((a) => a.enabled !== false && (!r.eligibleAccountIds || r.eligibleAccountIds.includes(a.id)) && r.targets.some((t) => t.provider === a.provider && (!t.account || t.account === a.id)));
915
988
  const providers = [...new Set(r.targets.map((t) => data.providers.find((p) => p.id === t.provider)?.name || t.provider))];
916
989
  const cooling = r.expiringStatus?.cooldownUntil > Date.now(), modes = r.reasoning?.supportedEfforts ?? [];
917
990
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { className: r.visible !== false ? "selected" : "", children: [
918
991
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
919
992
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: r.name }),
993
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "muted", children: r.contextKnown === false ? "\u7B49\u5F85\u6A21\u578B\u76EE\u5F55" : `${capacity(r.contextWindow)} \u4E0A\u4E0B\u6587 \xB7 ${r.contextMode === "custom" ? "\u81EA\u5B9A\u4E49\u9884\u7B97" : "\u9ED8\u8BA4\u6700\u5927"}` }),
920
994
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "muted", children: r.freeOnly ? `${r.freeStatus?.modelRoutes?.filter((m) => m.selected).length || 0} \u4E2A\u514D\u8D39\u6A21\u578B\u5DF2\u5C55\u793A` : modes.length ? `\u63A8\u7406\uFF1A${modes.map((e) => effortNames[e] || e).join(" / ")}` : "\u63A8\u7406\uFF1A\u5E73\u53F0\u9ED8\u8BA4" })
921
995
  ] }),
922
996
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { className: "route-targets", children: [
@@ -930,7 +1004,7 @@ function RoutesTable({ data, busy, act, edit }) {
930
1004
  ] }),
931
1005
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "check", children: [
932
1006
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", "aria-label": `\u5C55\u793A\u8DEF\u7531 ${r.name}`, disabled: busy, checked: r.visible !== false, onChange: (e) => {
933
- const { id, reasoning, supportsImages, expiringStatus, freeStatus, ...route } = r;
1007
+ const { id, reasoning, supportsImages, expiringStatus, freeStatus, maxContextWindow, supportedContextWindows, configuredContextWindow, contextKnown, eligibleAccountIds, enforceContext, ...route } = r;
934
1008
  void act({ action: "route.save", id, previousId: id, route: { ...route, visible: e.target.checked } });
935
1009
  } }),
936
1010
  r.visible !== false ? "\u5F00\u542F" : "\u5173\u95ED"
@@ -2163,7 +2237,7 @@ function FreeStatus({ route, busy, act, onUpdate }) {
2163
2237
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
2164
2238
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", children: "\u6A21\u578B / \u5C55\u793A" }),
2165
2239
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", children: "\u53EF\u7528\u8D26\u53F7" }),
2166
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", children: "\u63A8\u7406\u7B49\u7EA7" })
2240
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", children: "\u4E0A\u4E0B\u6587 / \u63A8\u7406\u7B49\u7EA7" })
2167
2241
  ] }) }),
2168
2242
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: models.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { className: m.selected ? "selected" : "", children: [
2169
2243
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
@@ -2182,6 +2256,9 @@ function FreeStatus({ route, busy, act, onUpdate }) {
2182
2256
  ] }),
2183
2257
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: m.accountNames.join("\u3001") || "\u6682\u65E0\u8D26\u53F7" }),
2184
2258
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
2259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContextSelect, { model: m, value: m.configuredContextWindow, disabled: busy, label: `${m.modelName} \u4E0A\u4E0B\u6587`, onChange: (contextWindow) => {
2260
+ void update(m, { contextWindow });
2261
+ } }),
2185
2262
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
2186
2263
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "sr-only", children: [
2187
2264
  m.modelName,
@@ -2219,7 +2296,7 @@ function FreeStatus({ route, busy, act, onUpdate }) {
2219
2296
  function FreeRouteForm({ draft, setDraft, data, act, busy, error, notice }) {
2220
2297
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: async (e) => {
2221
2298
  e.preventDefault();
2222
- const { id, previousId, revision, reasoning, supportsImages, expiringStatus, freeStatus, ...route } = draft;
2299
+ const { id, previousId, revision, reasoning, supportsImages, expiringStatus, freeStatus, maxContextWindow, supportedContextWindows, configuredContextWindow, contextKnown, eligibleAccountIds, enforceContext, ...route } = draft;
2223
2300
  route.targets = route.targets.map((t) => ({ provider: t.provider, ...t.account ? { account: t.account } : {}, ...t.model ? { model: t.model } : {}, ...t.models ? { models: t.models } : {} }));
2224
2301
  if (await act({ action: "route.save", id, previousId, revision, route })) setDraft(null);
2225
2302
  }, children: [
@@ -2260,7 +2337,7 @@ function RouteForm({ draft, setDraft, data, act, busy, error, notice }) {
2260
2337
  e.currentTarget.querySelector("details")?.setAttribute("open", "");
2261
2338
  return;
2262
2339
  }
2263
- const { id, previousId, revision, reasoning, supportsImages, expiringStatus, ...route } = draft;
2340
+ const { id, previousId, revision, reasoning, supportsImages, expiringStatus, maxContextWindow, supportedContextWindows, configuredContextWindow, contextKnown, eligibleAccountIds, enforceContext, ...route } = draft;
2264
2341
  route.targets = route.targets.map((t) => {
2265
2342
  const models = t.modelText.split(",").map((x) => x.trim()).filter(Boolean);
2266
2343
  return { provider: t.provider, ...t.account ? { account: t.account } : {}, ...models.length ? { models } : {}, ...route.strategy === "weighted" ? { weight: Number(t.weight) } : {}, ...route.strategy === "tokens" ? { tokenBudget: Number(t.tokenBudget) } : {}, ...route.strategy === "credits" ? { creditBudget: Number(t.creditBudget) } : {} };
@@ -2361,18 +2438,26 @@ function RouteForm({ draft, setDraft, data, act, busy, error, notice }) {
2361
2438
  ] }, t.key);
2362
2439
  }),
2363
2440
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy, onClick: () => set("targets", [...draft.targets, { provider: "workbuddy", account: "", key: crypto.randomUUID(), weight: 1, tokenBudget: 1e5, creditBudget: 100, modelText: "" }]), children: "\u6DFB\u52A0\u5207\u6362\u76EE\u6807" }),
2364
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { children: [
2365
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u9AD8\u7EA7\uFF1A\u4E0A\u4E0B\u6587\u4E0E\u8F93\u51FA\u957F\u5EA6" }),
2366
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "grid", style: { marginTop: 12 }, children: [
2367
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
2368
- "\u4E0A\u4E0B\u6587\u957F\u5EA6",
2369
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "number", disabled: busy, min: "1", required: true, value: draft.contextWindow, onChange: (e) => set("contextWindow", Number(e.target.value)) })
2370
- ] }),
2371
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
2372
- "\u6700\u5927\u8F93\u51FA\u957F\u5EA6",
2373
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "number", disabled: busy, min: "1", max: draft.contextWindow, required: true, value: draft.maxTokens, onChange: (e) => set("maxTokens", Number(e.target.value)) })
2441
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "box stack", children: [
2442
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
2443
+ "\u4E0A\u4E0B\u6587\u5BB9\u91CF",
2444
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { disabled: busy, value: draft.contextMode ?? "maximum", onChange: (e) => set("contextMode", e.target.value), children: [
2445
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "maximum", children: "\u4F7F\u7528\u76EE\u6807\u6E20\u9053\u6A21\u578B\u7684\u6700\u5927\u5BB9\u91CF\uFF08\u9ED8\u8BA4\uFF09" }),
2446
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "custom", children: "\u624B\u52A8\u8BBE\u7F6E\u672C\u5730\u9884\u7B97" })
2374
2447
  ] })
2448
+ ] }),
2449
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u9ED8\u8BA4\u968F\u6A21\u578B\u76EE\u5F55\u66F4\u65B0\u3002\u53EA\u5411\u652F\u6301\u5F53\u524D\u5BB9\u91CF\u7684\u6E20\u9053\u5207\u6362\uFF1B\u8C03\u4F4E\u9884\u7B97\u53EF\u8BA9\u66F4\u591A\u6E20\u9053\u53C2\u4E0E\u3002\u4E0D\u4F1A\u5220\u9664\u4F1A\u8BDD\u5386\u53F2\u3002" }),
2450
+ draft.contextMode === "custom" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
2451
+ "\u4E0A\u4E0B\u6587\u9884\u7B97\uFF08Token\uFF09",
2452
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "number", disabled: busy, min: "1", required: true, value: draft.contextWindow, onChange: (e) => set("contextWindow", Number(e.target.value)) })
2375
2453
  ] })
2454
+ ] }),
2455
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { children: [
2456
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u9AD8\u7EA7\uFF1A\u8F93\u51FA\u957F\u5EA6" }),
2457
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid", style: { marginTop: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
2458
+ "\u6700\u5927\u8F93\u51FA\u957F\u5EA6",
2459
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "number", disabled: busy, min: "1", max: draft.contextWindow, required: true, value: draft.maxTokens, onChange: (e) => set("maxTokens", Number(e.target.value)) })
2460
+ ] }) })
2376
2461
  ] })
2377
2462
  ] }),
2378
2463
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-foot bar", children: [