dsh-multi-router 0.5.9 → 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 (42) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +46 -6
  3. package/THIRD_PARTY_NOTICES.md +4 -0
  4. package/cordis.patch.yml +2 -2
  5. package/lib/accounts.js +4 -4
  6. package/lib/api-service.js +11 -0
  7. package/lib/api-service.js.map +7 -0
  8. package/lib/{chunk-U2MU2M5C.js → chunk-2LH6TD2U.js} +25 -115
  9. package/lib/chunk-2LH6TD2U.js.map +7 -0
  10. package/lib/chunk-4V52V5GO.js +105 -0
  11. package/lib/chunk-4V52V5GO.js.map +7 -0
  12. package/lib/chunk-625KMEYS.js +332 -0
  13. package/lib/chunk-625KMEYS.js.map +7 -0
  14. package/lib/{chunk-P43AWJ4Q.js → chunk-BSAI3SW5.js} +3 -3
  15. package/lib/{chunk-J3XO7RTF.js → chunk-DZ4ZUAYH.js} +50 -6
  16. package/lib/chunk-DZ4ZUAYH.js.map +7 -0
  17. package/lib/{chunk-MCHV2R25.js → chunk-NGVWVIBM.js} +22 -6
  18. package/lib/chunk-NGVWVIBM.js.map +7 -0
  19. package/lib/{chunk-FTM7RHQV.js → chunk-OK2T2UAZ.js} +4 -4
  20. package/lib/{chunk-S6ZQ45SS.js → chunk-VZPXRK7Q.js} +3 -3
  21. package/lib/{chunk-I4EW5TI7.js → chunk-ZQZSXCVR.js} +164 -48
  22. package/lib/chunk-ZQZSXCVR.js.map +7 -0
  23. package/lib/cli.js +7 -6
  24. package/lib/cli.js.map +1 -1
  25. package/lib/client.js +462 -44
  26. package/lib/client.js.map +2 -2
  27. package/lib/clients.js +9 -5
  28. package/lib/{host-v0.5.9.js → host-v0.6.1.js} +19 -13
  29. package/lib/host.js +19 -13
  30. package/lib/index.js +19 -13
  31. package/lib/index.js.map +2 -2
  32. package/lib/manager.js +11 -7
  33. package/lib/router.js +2 -2
  34. package/lib/shim.js +3 -3
  35. package/package.json +1 -1
  36. package/lib/chunk-I4EW5TI7.js.map +0 -7
  37. package/lib/chunk-J3XO7RTF.js.map +0 -7
  38. package/lib/chunk-MCHV2R25.js.map +0 -7
  39. package/lib/chunk-U2MU2M5C.js.map +0 -7
  40. /package/lib/{chunk-P43AWJ4Q.js.map → chunk-BSAI3SW5.js.map} +0 -0
  41. /package/lib/{chunk-FTM7RHQV.js.map → chunk-OK2T2UAZ.js.map} +0 -0
  42. /package/lib/{chunk-S6ZQ45SS.js.map → chunk-VZPXRK7Q.js.map} +0 -0
package/lib/client.js CHANGED
@@ -21,13 +21,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var client_exports = {};
22
22
  __export(client_exports, {
23
23
  AccountPool: () => AccountPool,
24
+ ApiServicePanel: () => ApiServicePanel,
24
25
  CheckinPanel: () => CheckinPanel,
26
+ ContextSelect: () => ContextSelect,
25
27
  InitialSetup: () => InitialSetup,
28
+ ModelSettings: () => ModelSettings,
26
29
  ModelsPanel: () => ModelsPanel,
27
30
  PluginUpdatesPanel: () => PluginUpdatesPanel,
28
31
  RestartGuidance: () => RestartGuidance,
29
32
  RouterPage: () => RouterPage,
30
33
  RoutesTable: () => RoutesTable,
34
+ SidebarUsage: () => SidebarUsage,
35
+ SidebarUsageCard: () => SidebarUsageCard,
31
36
  UpdateReminder: () => UpdateReminder,
32
37
  UsageDetails: () => UsageDetails,
33
38
  apply: () => apply,
@@ -44,7 +49,7 @@ module.exports = __toCommonJS(client_exports);
44
49
  var import_react = require("react");
45
50
 
46
51
  // package.json
47
- var version = "0.5.9";
52
+ var version = "0.6.1";
48
53
 
49
54
  // src/client.jsx
50
55
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -127,6 +132,101 @@ function apply(ctx) {
127
132
  reloadFromHost = () => requestPluginReload(ctx.get?.("remote"));
128
133
  ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({ name: "plugins.bundle.config", key: "dsh-multi-router" }, RouterPage));
129
134
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({ name: "shell.overlay", id: "dsh-multi-router-updates" }, UpdateReminder));
135
+ ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
136
+ name: "sidebar.footer.action",
137
+ id: "dsh-multi-router-usage",
138
+ order: 10,
139
+ inject: () => ({ open: () => {
140
+ const navigation = ctx.get?.("pluginNavigation");
141
+ if (!navigation?.openBundle) throw Error("\u8BF7\u5728\u63D2\u4EF6\u9875\u6253\u5F00 dsh-multi-router\uFF1B\u5F53\u524D DSH \u672A\u63D0\u4F9B\u8BE6\u60C5\u5BFC\u822A\u3002");
142
+ navigation.openBundle("dsh-multi-router");
143
+ } })
144
+ }, SidebarUsage));
145
+ }
146
+ var SUMMARY_EVENT = "dsh-multi-router-summary";
147
+ var compactTokens = (n) => n >= 1e9 ? `${(n / 1e9).toFixed(1)}B` : n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : fmt(n);
148
+ function SidebarUsageCard({ data, wide = true, open, error }) {
149
+ const title = `${data.today}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09
150
+ \u4ECA\u65E5 ${fmt(data.tokens)} Token \xB7 ${fmt(data.requests)} \u6B21\u8BF7\u6C42
151
+ \u7D2F\u8BA1 ${fmt(data.totalTokens)} Token${data.unmetered ? `
152
+ ${fmt(data.unmetered)} \u6B21\u8C03\u7528\u672A\u8FD4\u56DE\u7528\u91CF` : ""}${error ? `
153
+ ${error}` : ""}
154
+ \u70B9\u51FB\u6253\u5F00\u8D26\u53F7\u4E0E\u8DEF\u7531`;
155
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
156
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
157
+ [class*="_footArea"] [class*="_footerActions"]:has(.mr-sidebar-usage){flex-direction:column}
158
+ .mr-sidebar-usage{box-sizing:border-box;display:flex;align-items:center;gap:10px;width:100%;min-width:0;min-height:48px;padding:7px 10px;border:1px solid var(--dsw-alias-border-l2,#444);border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary,#eee);font:inherit;text-align:left;cursor:pointer}
159
+ .mr-sidebar-usage:hover{background:var(--dsw-alias-bg-module-platform,#303036)}.mr-sidebar-usage:focus-visible{outline:2px solid var(--dsw-alias-state-active-primary,#3a71db);outline-offset:2px}
160
+ .mr-sidebar-usage svg{flex-shrink:0}.mr-sidebar-usage span{flex:1;min-width:0}.mr-sidebar-usage small{display:block;color:var(--dsw-alias-label-secondary,#aaa);font-size:10px;line-height:15px;white-space:nowrap}.mr-sidebar-usage strong{display:block;font-size:13px;font-weight:600;line-height:18px;font-variant-numeric:tabular-nums;white-space:nowrap}.mr-sidebar-usage[data-wide=false]{width:36px;min-height:36px;padding:8px;justify-content:center}
161
+ ` }),
162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { className: "mr-sidebar-usage", "data-wide": wide, title, "aria-label": `Token \u7528\u91CF\u6982\u89C8\uFF0C\u4ECA\u65E5 ${fmt(data.tokens)}\uFF0C\u7D2F\u8BA1 ${fmt(data.totalTokens)}\uFF0C\u6253\u5F00\u63D2\u4EF6\u8BE6\u60C5`, onClick: open, children: [
163
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 4v16h17M8 16v-5m5 5V7m5 9v-7" }) }),
164
+ wide && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { children: [
167
+ "\u4ECA\u65E5 Token",
168
+ error ? " \xB7 \u672A\u5237\u65B0" : ""
169
+ ] }),
170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: compactTokens(data.tokens) })
171
+ ] }),
172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: "\u7D2F\u8BA1 Token" }),
174
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: compactTokens(data.totalTokens) })
175
+ ] })
176
+ ] })
177
+ ] })
178
+ ] });
179
+ }
180
+ function SidebarUsage({ wide = true, open }) {
181
+ const [data, setData] = (0, import_react.useState)(null), [error, setError] = (0, import_react.useState)("");
182
+ (0, import_react.useEffect)(() => {
183
+ let stopped = false, pending = false, again = false;
184
+ const abort = new AbortController();
185
+ async function poll(event) {
186
+ if (document.hidden) return;
187
+ if (pending) {
188
+ if (event?.type === SUMMARY_EVENT) again = true;
189
+ return;
190
+ }
191
+ pending = true;
192
+ try {
193
+ const next = await readManagementResponse(await fetch(`${ENDPOINT}/summary`, { cache: "no-store", signal: AbortSignal.any([abort.signal, AbortSignal.timeout(8e3)]) }));
194
+ if (!stopped) {
195
+ setData(next);
196
+ setError("");
197
+ }
198
+ } catch {
199
+ if (!stopped) setError("\u6682\u65F6\u65E0\u6CD5\u5237\u65B0\u672C\u673A\u7EDF\u8BA1");
200
+ } finally {
201
+ pending = false;
202
+ if (again && !stopped) {
203
+ again = false;
204
+ void poll();
205
+ }
206
+ }
207
+ }
208
+ void poll();
209
+ const timer = setInterval(() => {
210
+ void poll();
211
+ }, 6e4);
212
+ document.addEventListener("visibilitychange", poll);
213
+ window.addEventListener(SUMMARY_EVENT, poll);
214
+ return () => {
215
+ stopped = true;
216
+ abort.abort();
217
+ clearInterval(timer);
218
+ document.removeEventListener("visibilitychange", poll);
219
+ window.removeEventListener(SUMMARY_EVENT, poll);
220
+ };
221
+ }, []);
222
+ if (!data || data.enabled === false) return null;
223
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarUsageCard, { data, wide, error, open: () => {
224
+ try {
225
+ open();
226
+ } catch (e) {
227
+ setError(e.message);
228
+ }
229
+ } });
130
230
  }
131
231
  function Modal({ title, close, children, busy }) {
132
232
  const ref = (0, import_react.useRef)(null);
@@ -318,9 +418,10 @@ function RouterPage({ view = "page" }) {
318
418
  try {
319
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 }) });
320
420
  const next = await readManagementResponse(res);
321
- const { exported, workbuddyLogin, loginResult, ...state } = next;
421
+ const { exported, workbuddyLogin, loginResult, apiKey, modelTest, ...state } = next;
322
422
  setData(state);
323
- setNotice(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
+ if (action.sidebarUsage !== void 0 || action.action === "config.import") window.dispatchEvent(new Event(SUMMARY_EVENT));
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");
324
425
  return next;
325
426
  } catch (e) {
326
427
  setError(e.message);
@@ -368,6 +469,12 @@ function RouterPage({ view = "page" }) {
368
469
  "v",
369
470
  data?.pluginVersion ?? version
370
471
  ] }),
472
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "check refresh-control", children: [
473
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", disabled: busy || !data || backendNeedsRestart(data), checked: data?.options?.sidebarUsage !== false, onChange: (e) => {
474
+ void act({ action: "options.save", sidebarUsage: e.target.checked });
475
+ } }),
476
+ "\u4FA7\u680F\u7528\u91CF"
477
+ ] }),
371
478
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "status", children: [
372
479
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", { className: busy || !data ? "warn" : "good", "aria-hidden": "true" }),
373
480
  busy ? "\u6B63\u5728\u5904\u7406\u2026" : !data ? "\u6B63\u5728\u8FDE\u63A5\u2026" : "\u672C\u673A\u8FD0\u884C"
@@ -381,7 +488,7 @@ function RouterPage({ view = "page" }) {
381
488
  }, children: "\u5237\u65B0\u9762\u677F" })
382
489
  ] })
383
490
  ] }),
384
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { "aria-label": "\u8DEF\u7531\u63D2\u4EF6\u914D\u7F6E\u9875\u7B7E", children: [["overview", "\u603B\u89C8\u4E0E\u7528\u91CF"], ["usage", "Token \u660E\u7EC6"], ["accounts", "\u8D26\u53F7\u4E0E\u767B\u5F55"], ["checkins", "\u79EF\u5206\u7B7E\u5230"], ["models", "\u6A21\u578B\u7BA1\u7406"], ["routes", "\u8DEF\u7531\u89C4\u5219"], ["plugins", "\u63D2\u4EF6\u66F4\u65B0"], ["migration", "\u914D\u7F6E\u8FC1\u79FB"]].map(([key, label]) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { "aria-pressed": tab === key, onClick: () => {
491
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { "aria-label": "\u8DEF\u7531\u63D2\u4EF6\u914D\u7F6E\u9875\u7B7E", children: [["overview", "\u603B\u89C8\u4E0E\u7528\u91CF"], ["usage", "Token \u660E\u7EC6"], ["accounts", "\u8D26\u53F7\u4E0E\u767B\u5F55"], ["checkins", "\u79EF\u5206\u7B7E\u5230"], ["models", "\u6A21\u578B\u7BA1\u7406"], ["routes", "\u8DEF\u7531\u89C4\u5219"], ["api", "API \u670D\u52A1"], ["plugins", "\u63D2\u4EF6\u66F4\u65B0"], ["migration", "\u914D\u7F6E\u8FC1\u79FB"]].map(([key, label]) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { "aria-pressed": tab === key, onClick: () => {
385
492
  setTab(key);
386
493
  setError("");
387
494
  setNotice("");
@@ -413,11 +520,12 @@ function RouterPage({ view = "page" }) {
413
520
  ] }),
414
521
  ["overview", "accounts"].includes(tab) && data.setup?.dismissed !== true && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InitialSetup, { data, busy, act, go: (key) => setTab(key) }),
415
522
  tab === "overview" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Overview, { data, busy, act, go: (key) => setTab(key) }),
523
+ tab === "api" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApiServicePanel, { data: data.apiService, busy, act }),
416
524
  tab === "plugins" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PluginUpdatesPanel, { data: data.pluginUpdates, busy, act }),
417
525
  tab === "checkins" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckinPanel, { data, busy, act }),
418
526
  tab === "usage" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UsageDetails, { data, busy, act }),
419
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),
420
- 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 }),
421
529
  tab === "routes" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "stack", children: [
422
530
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bar", children: [
423
531
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
@@ -436,7 +544,7 @@ function RouterPage({ view = "page" }) {
436
544
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary", disabled: busy, onClick: () => {
437
545
  setError("");
438
546
  setNotice("");
439
- 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()] });
440
548
  }, children: "\u65B0\u5EFA\u8DEF\u7531" })
441
549
  ] })
442
550
  ] }),
@@ -472,10 +580,10 @@ function RouterPage({ view = "page" }) {
472
580
  presetModal && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Modal, { title: "\u6A21\u578B\u8DEF\u7531\u9884\u914D\u7F6E", busy, close: () => setPresetModal(false), children: [
473
581
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-body stack", children: [
474
582
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Feedback, { error, notice }),
475
- /* @__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" }),
476
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: [
477
585
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: preset.name }) }),
478
- /* @__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" : ""}` }),
479
587
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy || preset.added, onClick: () => {
480
588
  void act({ action: "preset.add", id: preset.id });
481
589
  }, children: preset.added ? "\u5DF2\u6DFB\u52A0" : "\u6DFB\u52A0\u6B64\u8DEF\u7531" })
@@ -494,8 +602,221 @@ function RouterPage({ view = "page" }) {
494
602
  }, error, notice }) })
495
603
  ] });
496
604
  }
497
- function ModelsPanel({ data, provider, providerSelect, search, setSearch, busy, act }) {
498
- const [editing, setEditing] = (0, import_react.useState)(null);
605
+ function ApiServicePanel({ data: a, busy, act }) {
606
+ const [key, setKey] = (0, import_react.useState)(""), [hint, setHint] = (0, import_react.useState)(""), [confirmKey, setConfirmKey] = (0, import_react.useState)(null), [models, setModels] = (0, import_react.useState)(false), [example, setExample] = (0, import_react.useState)("");
607
+ const copyField = (0, import_react.useRef)(null);
608
+ (0, import_react.useEffect)(() => {
609
+ setKey((current) => a?.keyHint && current.endsWith(a.keyHint.slice(-4)) ? current : "");
610
+ }, [a?.keyHint]);
611
+ (0, import_react.useEffect)(() => {
612
+ if (!key) return;
613
+ const timer = setTimeout(() => setKey(""), 6e4);
614
+ return () => clearTimeout(timer);
615
+ }, [key]);
616
+ if (!a) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "steps", children: "\u5F53\u524D\u540E\u53F0\u672A\u63D0\u4F9B API \u670D\u52A1\uFF0C\u8BF7\u66F4\u65B0\u63D2\u4EF6\u3002" });
617
+ const save = (fields) => act({ action: "api.settings", apiRevision: a.revision, ...fields });
618
+ async function keyAction(action) {
619
+ const result = await act({ action, apiRevision: a.revision });
620
+ if (result) {
621
+ setKey(result.apiKey || "");
622
+ setConfirmKey(null);
623
+ }
624
+ }
625
+ async function copy(value, fallback = false) {
626
+ try {
627
+ await navigator.clipboard.writeText(value);
628
+ setHint("\u5DF2\u590D\u5236\u3002");
629
+ } catch {
630
+ setHint("\u5F53\u524D\u73AF\u5883\u65E0\u6CD5\u81EA\u52A8\u590D\u5236\uFF0C\u8BF7\u5728\u6587\u672C\u6846\u4E2D\u5168\u9009\u590D\u5236\u3002");
631
+ if (fallback) copyField.current?.select();
632
+ else setExample(value);
633
+ }
634
+ }
635
+ const model = a.models[0]?.id || "\u4F60\u7684\u8DEF\u7531\u6A21\u578B ID";
636
+ const command = `curl '${a.baseUrl}/chat/completions' \\
637
+ -H 'Authorization: Bearer YOUR_API_KEY' \\
638
+ -H 'Content-Type: application/json' \\
639
+ -d '${JSON.stringify({ model, messages: [{ role: "user", content: "\u4F60\u597D" }], stream: true })}'`;
640
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "stack", children: [
641
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "box stack", children: [
642
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bar", children: [
643
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
644
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "API \u670D\u52A1" }),
645
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u5C06\u8D26\u53F7\u6C60\u4E0E\u7EDF\u4E00\u8DEF\u7531\u63D0\u4F9B\u7ED9\u5176\u4ED6\u5DE5\u5177\u3002\u968F DSH \u8FD0\u884C\uFF0C\u9ED8\u8BA4\u5173\u95ED\u3002" })
646
+ ] }),
647
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "status", role: "status", children: [
648
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", { className: a.phase === "running" ? "good" : a.phase === "error" ? "warn" : "", "aria-hidden": "true" }),
649
+ { running: "\u8FD0\u884C\u4E2D", stopped: "\u5DF2\u5173\u95ED", starting: "\u542F\u52A8\u4E2D", error: "\u542F\u52A8\u5931\u8D25" }[a.phase] || a.phase
650
+ ] })
651
+ ] }),
652
+ a.error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "error", role: "alert", children: a.error }),
653
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bar", children: [
654
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: a.phase === "running" ? `${a.active} \u4E2A\u5904\u7406\u4E2D\u8BF7\u6C42 \xB7 ${a.allowLan ? "\u672C\u673A\u4E0E\u7F51\u7EDC\u8BBF\u95EE" : "\u4EC5\u672C\u673A\u8BBF\u95EE"}` : "\u5F00\u542F\u540E\u5373\u53EF\u590D\u5236\u63A5\u5165\u5730\u5740\uFF1B\u5173\u95ED\u4F1A\u7EC8\u6B62\u6B63\u5728\u5904\u7406\u7684 API \u8BF7\u6C42\u3002" }),
655
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "check", children: [
656
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: a.enabled, disabled: busy || !a.hasKey, onChange: (e) => {
657
+ void save({ enabled: e.target.checked });
658
+ } }),
659
+ "\u542F\u7528 API \u670D\u52A1"
660
+ ] })
661
+ ] }),
662
+ !a.hasKey && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "steps", children: "\u5148\u751F\u6210 API Key\uFF0C\u518D\u542F\u7528\u670D\u52A1\u3002\u8D26\u53F7\u767B\u5F55\u51ED\u8BC1\u4E0D\u4F1A\u901A\u8FC7\u6B64\u670D\u52A1\u63D0\u4F9B\u3002" })
663
+ ] }),
664
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "metrics", children: [
665
+ metric("\u4ECA\u65E5 API \u8C03\u7528", fmt(a.today.calls), `${a.today.attempts} \u6B21\u4E0A\u6E38\u5C1D\u8BD5\uFF0C\u542B\u6545\u969C\u5207\u6362`),
666
+ metric("\u4ECA\u65E5 API Token", fmt(a.today.tokens), "\u5E73\u53F0\u5B9E\u9645\u8FD4\u56DE\u7528\u91CF"),
667
+ metric("\u5DF2\u5F00\u653E\u6A21\u578B", fmt(a.models.length), a.modelIds === null ? "\u8DDF\u968F\u8DEF\u7531\u5C55\u793A\u8BBE\u7F6E" : "\u81EA\u9009\u6A21\u578B"),
668
+ metric("\u5904\u7406\u4E2D", fmt(a.active), "\u6700\u591A 8 \u4E2A\u5E76\u53D1 API \u8BF7\u6C42")
669
+ ] }),
670
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "box stack", children: [
671
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bar", children: [
672
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
673
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u63A5\u5165\u4FE1\u606F" }),
674
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u5BA2\u6237\u7AEF\u9009\u62E9 OpenAI \u517C\u5BB9\u65B9\u5F0F\uFF0C\u586B\u5199\u4E0B\u65B9 Base URL \u4E0E API Key\u3002" })
675
+ ] }),
676
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => setExample(command), children: "\u8C03\u7528\u793A\u4F8B" })
677
+ ] }),
678
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
679
+ "Base URL",
680
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
681
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "mono", style: { flex: 1, minWidth: 180 }, readOnly: true, value: a.baseUrl, onFocus: (e) => e.currentTarget.select() }),
682
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => {
683
+ void copy(a.baseUrl);
684
+ }, children: "\u590D\u5236\u5730\u5740" })
685
+ ] })
686
+ ] }),
687
+ a.allowLan && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
688
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u5176\u4ED6\u8BBE\u5907\u53EF\u4F7F\u7528\u4EE5\u4E0B\u672C\u673A\u7F51\u5361\u5730\u5740\uFF08\u9700\u8981\u7F51\u7EDC\u53EF\u8FBE\u53CA\u9632\u706B\u5899\u653E\u884C\uFF09\uFF1A" }),
689
+ a.lanUrls.length ? a.lanUrls.map((url) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mono", style: { overflowWrap: "anywhere" }, children: url }, url)) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u6682\u65E0\u53EF\u7528 IPv4 \u7F51\u5361\u5730\u5740\u3002" })
690
+ ] }),
691
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
692
+ "API Key",
693
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
694
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { ref: copyField, className: "mono", style: { flex: 1, minWidth: 180 }, readOnly: true, autoComplete: "off", spellCheck: "false", value: key || a.keyHint || "\u5C1A\u672A\u751F\u6210", onFocus: (e) => e.currentTarget.select() }),
695
+ a.hasKey && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
696
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => {
697
+ if (key) setKey("");
698
+ else void keyAction("api.key.reveal");
699
+ }, children: key ? "\u9690\u85CF" : "\u663E\u793A" }),
700
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: async () => {
701
+ const result = await act({ action: "api.key.reveal", apiRevision: a.revision });
702
+ if (result?.apiKey) {
703
+ setKey(result.apiKey);
704
+ await copy(result.apiKey, true);
705
+ }
706
+ }, children: "\u590D\u5236 Key" })
707
+ ] })
708
+ ] })
709
+ ] }),
710
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
711
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => {
712
+ if (a.hasKey) setConfirmKey("create");
713
+ else void keyAction("api.key.create");
714
+ }, children: a.hasKey ? "\u91CD\u7F6E API Key" : "\u751F\u6210 API Key" }),
715
+ a.hasKey && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => setConfirmKey("revoke"), children: "\u64A4\u9500 Key \u5E76\u5173\u95ED\u670D\u52A1" }),
716
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "Key \u4EC5\u4FDD\u5B58\u5728\u672C\u673A\u79C1\u6709\u6587\u4EF6\uFF1B\u663E\u793A\u540E 60 \u79D2\u81EA\u52A8\u9690\u85CF\u3002" })
717
+ ] }),
718
+ hint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", role: "status", children: hint })
719
+ ] }),
720
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "box stack", children: [
721
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u670D\u52A1\u8BBE\u7F6E" }),
722
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: "stack", onSubmit: (e) => {
723
+ e.preventDefault();
724
+ const f = new FormData(e.currentTarget);
725
+ void save({ port: Number(f.get("port")), allowLan: f.has("lan") });
726
+ }, children: [
727
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
728
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "filter", children: [
729
+ "\u76D1\u542C\u7AEF\u53E3",
730
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "number", name: "port", min: "1024", max: "65535", required: true, defaultValue: a.port, disabled: busy })
731
+ ] }),
732
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "check", children: [
733
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", name: "lan", defaultChecked: a.allowLan, disabled: busy }),
734
+ "\u5141\u8BB8\u5C40\u57DF\u7F51\u8BBF\u95EE"
735
+ ] }),
736
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, children: "\u4FDD\u5B58\u8BBE\u7F6E" })
737
+ ] }),
738
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u9ED8\u8BA4\u76D1\u542C 127.0.0.1\u3002\u5F00\u542F\u7F51\u7EDC\u8BBF\u95EE\u540E\u76D1\u542C\u6240\u6709 IPv4 \u7F51\u5361\uFF0C\u4F7F\u7528 HTTP\uFF0C\u8BF7\u4EC5\u7528\u4E8E\u53EF\u4FE1\u7F51\u7EDC\uFF1B\u5982\u9700\u5916\u7F51\u8BBF\u95EE\uFF0C\u8BF7\u81EA\u884C\u914D\u7F6E HTTPS \u4E0E\u8BBF\u95EE\u9650\u5236\u3002\u4FEE\u6539\u7AEF\u53E3\u6216\u8BBF\u95EE\u8303\u56F4\u4F1A\u4E2D\u65AD\u5F53\u524D API \u8BF7\u6C42\u3002" })
739
+ ] }, a.revision)
740
+ ] }),
741
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "box table-shell", children: [
742
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bar table-footer", children: [
743
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
744
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u5F00\u653E\u6A21\u578B" }),
745
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: a.modelIds === null ? "\u81EA\u52A8\u8DDF\u968F\u5DF2\u5C55\u793A\u8DEF\u7531\uFF0C\u5305\u542B\u52A8\u6001\u514D\u8D39\u6A21\u578B\u3002" : "\u4EC5\u5F00\u653E\u624B\u52A8\u9009\u62E9\u7684\u8DEF\u7531\u3002" })
746
+ ] }),
747
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => setModels(true), children: "\u9009\u62E9\u5F00\u653E\u6A21\u578B" })
748
+ ] }),
749
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scroll", role: "region", "aria-label": "API \u5F00\u653E\u6A21\u578B", tabIndex: "0", children: [
750
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { className: "table", children: [
751
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
752
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u6A21\u578B\u540D\u79F0" }),
753
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8C03\u7528\u6A21\u578B ID" })
754
+ ] }) }),
755
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: a.models.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
756
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: m.name }),
757
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "mono", style: { overflowWrap: "anywhere" }, children: m.id })
758
+ ] }, m.id)) })
759
+ ] }),
760
+ !a.models.length && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "empty", children: "\u6682\u672A\u5F00\u653E\u6A21\u578B\uFF0C\u8BF7\u5C55\u793A\u8DEF\u7531\u6216\u624B\u52A8\u9009\u62E9\u3002" })
761
+ ] })
762
+ ] }),
763
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { children: [
764
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u63A5\u53E3\u4E0E\u7EDF\u8BA1\u8BF4\u660E" }),
765
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u652F\u6301 GET /v1/models\u3001POST /v1/chat/completions\uFF08\u6D41\u5F0F\u4E0E\u666E\u901A JSON\uFF09\uFF0C\u6CBF\u7528\u8DEF\u7531\u7684\u63A8\u7406\u9ED8\u8BA4\u503C\uFF0C\u53EF\u7528 reasoning_effort \u8986\u76D6\u3002\u5DE5\u5177\u8C03\u7528\u4E0E\u56FE\u7247\u80FD\u529B\u53D6\u51B3\u4E8E\u76EE\u6807\u6A21\u578B\uFF1B\u5916\u90E8\u5DE5\u5177\u81EA\u884C\u6267\u884C\u5DE5\u5177\u5E76\u56DE\u4F20\u7ED3\u679C\u3002\u7528\u91CF\u8BB0\u5F55\u5230 Token \u660E\u7EC6\u7684\u300CAPI \u670D\u52A1\u300D\u6765\u6E90\uFF0C\u4E0D\u4FDD\u5B58\u8BF7\u6C42\u5185\u5BB9\u6216 Key\uFF1B\u4E0A\u4E0B\u6587\u7531\u8C03\u7528\u5BA2\u6237\u7AEF\u4F20\u5165\u3002\u6B64\u7248\u672C\u4E0D\u63D0\u4F9B Responses\u3001Embeddings \u6216\u8DE8\u7AD9\u6D4F\u89C8\u5668 CORS \u63A5\u53E3\u3002\u542F\u7528\u72B6\u6001\u4F1A\u4FDD\u5B58\uFF0CDSH \u4E0B\u6B21\u542F\u52A8\u81EA\u52A8\u6062\u590D\uFF1B\u9000\u51FA DSH \u540E\u505C\u6B62\u3002" })
766
+ ] }),
767
+ confirmKey && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Modal, { title: confirmKey === "create" ? "\u91CD\u7F6E API Key" : "\u64A4\u9500 API Key", busy, close: () => setConfirmKey(null), children: [
768
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "modal-body stack", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: confirmKey === "create" ? "\u65E7 Key \u5C06\u7ACB\u5373\u5931\u6548\uFF0C\u5176\u4ED6\u5BA2\u6237\u7AEF\u9700\u8981\u586B\u5199\u65B0 Key\u3002" : "Key \u5C06\u7ACB\u5373\u5931\u6548\uFF0CAPI \u670D\u52A1\u4F1A\u5173\u95ED\uFF0C\u6B63\u5728\u5904\u7406\u7684 API \u8BF7\u6C42\u5C06\u4E2D\u65AD\u3002" }) }),
769
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-foot row", children: [
770
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => setConfirmKey(null), children: "\u53D6\u6D88" }),
771
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { className: "primary", disabled: busy, onClick: () => {
772
+ void keyAction(`api.key.${confirmKey}`);
773
+ }, children: [
774
+ "\u786E\u8BA4",
775
+ confirmKey === "create" ? "\u91CD\u7F6E" : "\u64A4\u9500"
776
+ ] })
777
+ ] })
778
+ ] }),
779
+ models && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Modal, { title: "\u9009\u62E9 API \u5F00\u653E\u6A21\u578B", busy, close: () => setModels(false), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApiModels, { data: a, busy, save: async (modelIds) => {
780
+ if (await save({ modelIds })) setModels(false);
781
+ } }) }),
782
+ example && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Modal, { title: "\u63A5\u5165\u793A\u4F8B / \u624B\u52A8\u590D\u5236", busy: false, close: () => setExample(""), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-body stack", children: [
783
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u5C06 YOUR_API_KEY \u66FF\u6362\u4E3A\u81EA\u5DF1\u7684\u670D\u52A1 Key\u3002Base URL \u5DF2\u5305\u542B /v1\uFF1B\u6D4B\u8BD5\u8C03\u7528\u4F1A\u6B63\u5E38\u6D88\u8017\u76EE\u6807\u8D26\u53F7\u7684\u7528\u91CF\u3002" }),
784
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
785
+ "\u53EF\u590D\u5236\u5185\u5BB9",
786
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { className: "mono", readOnly: true, rows: 8, value: example, onFocus: (e) => e.currentTarget.select() })
787
+ ] })
788
+ ] }) })
789
+ ] });
790
+ }
791
+ function ApiModels({ data, busy, save }) {
792
+ const [follow, setFollow] = (0, import_react.useState)(data.modelIds === null), [selected, setSelected] = (0, import_react.useState)(data.modelIds ?? data.models.map((m) => m.id));
793
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: (e) => {
794
+ e.preventDefault();
795
+ void save(follow ? null : selected);
796
+ }, children: [
797
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-body stack", children: [
798
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "check", children: [
799
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: follow, disabled: busy, onChange: (e) => setFollow(e.target.checked) }),
800
+ "\u8DDF\u968F\u8DEF\u7531\u5C55\u793A\uFF08\u65B0\u53D1\u73B0\u7684\u514D\u8D39\u6A21\u578B\u4E5F\u81EA\u52A8\u5F00\u653E\uFF09"
801
+ ] }),
802
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
803
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy || follow, onClick: () => setSelected(data.choices.map((m) => m.id)), children: "\u5168\u9009" }),
804
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy || follow, onClick: () => setSelected([]), children: "\u5168\u4E0D\u9009" })
805
+ ] }),
806
+ data.choices.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "check", children: [
807
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", disabled: busy || follow, checked: follow ? m.visible : selected.includes(m.id), onChange: (e) => setSelected(e.target.checked ? [...selected, m.id] : selected.filter((id) => id !== m.id)) }),
808
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
809
+ m.name,
810
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "mono muted", style: { overflowWrap: "anywhere" }, children: m.id })
811
+ ] })
812
+ ] }, m.id)),
813
+ !data.choices.length && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u8BF7\u5148\u6DFB\u52A0\u8DEF\u7531\u3002" })
814
+ ] }),
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" }) })
816
+ ] });
817
+ }
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);
499
820
  const models = data.providers.find((p) => p.id === provider)?.models ?? [];
500
821
  const all = data.providers.flatMap((p) => p.models), shown = models.filter((m) => `${m.id} ${m.name}`.toLowerCase().includes(search.toLowerCase()));
501
822
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "stack", children: [
@@ -546,8 +867,14 @@ function ModelsPanel({ data, provider, providerSelect, search, setSearch, busy,
546
867
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "mono muted", children: m.id })
547
868
  ] }),
548
869
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
549
- fmt(m.contextWindow / 1e3),
550
- "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
+ ] }),
551
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" })
552
879
  ] }),
553
880
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "check", children: [
@@ -556,29 +883,94 @@ function ModelsPanel({ data, provider, providerSelect, search, setSearch, busy,
556
883
  } }),
557
884
  m.visible ? "\u5DF2\u5C55\u793A" : "\u5DF2\u9690\u85CF"
558
885
  ] }) }),
559
- /* @__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
+ ] })
560
893
  ] }, m.id)) })
561
894
  ] }),
562
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" })
563
896
  ] }),
564
- /* @__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" }) })
565
898
  ] }),
566
- 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) => {
567
- 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 () => {
568
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);
569
905
  }, children: [
570
906
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-body stack", children: [
571
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
572
- "\u6A21\u578B\u540D\u79F0",
573
- /* @__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 }) })
574
- ] }),
575
- /* @__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}` })
576
910
  ] }),
577
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-foot row", children: [
578
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy, onClick: () => setEditing(null), children: "\u53D6\u6D88" }),
579
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary", disabled: busy, children: "\u4FDD\u5B58\u540D\u79F0" })
580
- ] })
581
- ] }) })
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" }) })
582
974
  ] });
583
975
  }
584
976
  function RoutesTable({ data, busy, act, edit }) {
@@ -592,12 +984,13 @@ function RoutesTable({ data, busy, act, edit }) {
592
984
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", className: "cell-actions", children: "\u7BA1\u7406" })
593
985
  ] }) }),
594
986
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: data.routes.map((r) => {
595
- 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)));
596
988
  const providers = [...new Set(r.targets.map((t) => data.providers.find((p) => p.id === t.provider)?.name || t.provider))];
597
989
  const cooling = r.expiringStatus?.cooldownUntil > Date.now(), modes = r.reasoning?.supportedEfforts ?? [];
598
990
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { className: r.visible !== false ? "selected" : "", children: [
599
991
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
600
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"}` }),
601
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" })
602
995
  ] }),
603
996
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { className: "route-targets", children: [
@@ -611,7 +1004,7 @@ function RoutesTable({ data, busy, act, edit }) {
611
1004
  ] }),
612
1005
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "check", children: [
613
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) => {
614
- const { id, reasoning, supportsImages, expiringStatus, freeStatus, ...route } = r;
1007
+ const { id, reasoning, supportsImages, expiringStatus, freeStatus, maxContextWindow, supportedContextWindows, configuredContextWindow, contextKnown, eligibleAccountIds, enforceContext, ...route } = r;
615
1008
  void act({ action: "route.save", id, previousId: id, route: { ...route, visible: e.target.checked } });
616
1009
  } }),
617
1010
  r.visible !== false ? "\u5F00\u542F" : "\u5173\u95ED"
@@ -1054,11 +1447,11 @@ function PerformanceMetrics({ events }) {
1054
1447
  ] });
1055
1448
  }
1056
1449
  function UsageDetails({ data, busy, act }) {
1057
- const [account, setAccount] = (0, import_react.useState)(""), [route, setRoute] = (0, import_react.useState)(""), [outcome, setOutcome] = (0, import_react.useState)(""), [page, setPage] = (0, import_react.useState)(0);
1450
+ const [account, setAccount] = (0, import_react.useState)(""), [route, setRoute] = (0, import_react.useState)(""), [outcome, setOutcome] = (0, import_react.useState)(""), [source, setSource] = (0, import_react.useState)(""), [page, setPage] = (0, import_react.useState)(0);
1058
1451
  const [period, setPeriod] = (0, import_react.useState)("30"), [start, setStart] = (0, import_react.useState)(() => new Date(Date.now() - 29 * 864e5).toLocaleDateString("en-CA", { timeZone: "Asia/Shanghai" })), [end, setEnd] = (0, import_react.useState)(() => (/* @__PURE__ */ new Date()).toLocaleDateString("en-CA", { timeZone: "Asia/Shanghai" }));
1059
1452
  const all = data.telemetry?.events || [], retention = data.options.usageRetentionDays;
1060
1453
  const [from, until] = usageDateBounds(period, start, end), invalidRange = !(from < until);
1061
- const events = all.filter((e) => e.at >= from && e.at < until && (!account || e.account === account) && (!route || e.alias === route) && (!outcome || e.outcome === outcome));
1454
+ const events = all.filter((e) => e.at >= from && e.at < until && (!account || e.account === account) && (!route || e.alias === route) && (!outcome || e.outcome === outcome) && (!source || (e.source || "dsh") === source));
1062
1455
  const pages = Math.max(1, Math.ceil(events.length / 50)), current = Math.min(page, pages - 1);
1063
1456
  const accountName = (id) => data.accounts.find((a) => a.id === id)?.name || id;
1064
1457
  const routeName = (id) => data.routes.find((r) => r.id === id)?.name || id;
@@ -1145,6 +1538,17 @@ function UsageDetails({ data, busy, act }) {
1145
1538
  [...new Set(all.map((e) => e.alias))].map((id) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: id, children: routeName(id) }, id))
1146
1539
  ] })
1147
1540
  ] }),
1541
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "filter", children: [
1542
+ "\u6765\u6E90",
1543
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { value: source, onChange: (e) => {
1544
+ setSource(e.target.value);
1545
+ setPage(0);
1546
+ }, children: [
1547
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5168\u90E8\u6765\u6E90" }),
1548
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "dsh", children: "DSH \u4F1A\u8BDD" }),
1549
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "api", children: "API \u670D\u52A1" })
1550
+ ] })
1551
+ ] }),
1148
1552
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "filter", children: [
1149
1553
  "\u7ED3\u679C",
1150
1554
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { value: outcome, onChange: (e) => {
@@ -1184,7 +1588,10 @@ function UsageDetails({ data, busy, act }) {
1184
1588
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7ED3\u679C" })
1185
1589
  ] }) }),
1186
1590
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: events.slice(current * 50, (current + 1) * 50).map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
1187
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: when(e.at) }),
1591
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
1592
+ when(e.at),
1593
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "muted", children: e.source === "api" ? "API \u670D\u52A1" : "DSH \u4F1A\u8BDD" })
1594
+ ] }),
1188
1595
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
1189
1596
  data.providers.find((p) => p.id === e.provider)?.name || e.provider,
1190
1597
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
@@ -1830,7 +2237,7 @@ function FreeStatus({ route, busy, act, onUpdate }) {
1830
2237
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
1831
2238
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", children: "\u6A21\u578B / \u5C55\u793A" }),
1832
2239
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", children: "\u53EF\u7528\u8D26\u53F7" }),
1833
- /* @__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" })
1834
2241
  ] }) }),
1835
2242
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: models.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { className: m.selected ? "selected" : "", children: [
1836
2243
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
@@ -1849,6 +2256,9 @@ function FreeStatus({ route, busy, act, onUpdate }) {
1849
2256
  ] }),
1850
2257
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: m.accountNames.join("\u3001") || "\u6682\u65E0\u8D26\u53F7" }),
1851
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
+ } }),
1852
2262
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
1853
2263
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "sr-only", children: [
1854
2264
  m.modelName,
@@ -1886,7 +2296,7 @@ function FreeStatus({ route, busy, act, onUpdate }) {
1886
2296
  function FreeRouteForm({ draft, setDraft, data, act, busy, error, notice }) {
1887
2297
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: async (e) => {
1888
2298
  e.preventDefault();
1889
- 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;
1890
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 } : {} }));
1891
2301
  if (await act({ action: "route.save", id, previousId, revision, route })) setDraft(null);
1892
2302
  }, children: [
@@ -1927,7 +2337,7 @@ function RouteForm({ draft, setDraft, data, act, busy, error, notice }) {
1927
2337
  e.currentTarget.querySelector("details")?.setAttribute("open", "");
1928
2338
  return;
1929
2339
  }
1930
- 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;
1931
2341
  route.targets = route.targets.map((t) => {
1932
2342
  const models = t.modelText.split(",").map((x) => x.trim()).filter(Boolean);
1933
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) } : {} };
@@ -2028,18 +2438,26 @@ function RouteForm({ draft, setDraft, data, act, busy, error, notice }) {
2028
2438
  ] }, t.key);
2029
2439
  }),
2030
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" }),
2031
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { children: [
2032
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u9AD8\u7EA7\uFF1A\u4E0A\u4E0B\u6587\u4E0E\u8F93\u51FA\u957F\u5EA6" }),
2033
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "grid", style: { marginTop: 12 }, children: [
2034
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
2035
- "\u4E0A\u4E0B\u6587\u957F\u5EA6",
2036
- /* @__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)) })
2037
- ] }),
2038
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
2039
- "\u6700\u5927\u8F93\u51FA\u957F\u5EA6",
2040
- /* @__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" })
2041
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)) })
2042
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
+ ] }) })
2043
2461
  ] })
2044
2462
  ] }),
2045
2463
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-foot bar", children: [