dsh-multi-router 0.5.4 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createAccountClient
4
- } from "./chunk-LADXRFL7.js";
4
+ } from "./chunk-EGLF4CWD.js";
5
5
  import {
6
6
  importAccount
7
7
  } from "./chunk-OFFVP6YN.js";
@@ -9,7 +9,7 @@ import "./chunk-FTM7RHQV.js";
9
9
  import "./chunk-S5KMFAQM.js";
10
10
  import {
11
11
  Router
12
- } from "./chunk-6ZXEVC3R.js";
12
+ } from "./chunk-J3XO7RTF.js";
13
13
  import {
14
14
  DEFAULT_ROUTES,
15
15
  PROVIDERS,
package/lib/client.js CHANGED
@@ -26,11 +26,14 @@ __export(client_exports, {
26
26
  PluginUpdatesPanel: () => PluginUpdatesPanel,
27
27
  RestartGuidance: () => RestartGuidance,
28
28
  RouterPage: () => RouterPage,
29
+ UpdateReminder: () => UpdateReminder,
29
30
  UsageDetails: () => UsageDetails,
30
31
  apply: () => apply,
31
32
  backendNeedsRestart: () => backendNeedsRestart,
33
+ duration: () => duration,
32
34
  inject: () => inject,
33
35
  name: () => name,
36
+ performanceSummary: () => performanceSummary,
34
37
  readManagementResponse: () => readManagementResponse,
35
38
  usageDateBounds: () => usageDateBounds
36
39
  });
@@ -38,7 +41,7 @@ module.exports = __toCommonJS(client_exports);
38
41
  var import_react = require("react");
39
42
 
40
43
  // package.json
41
- var version = "0.5.4";
44
+ var version = "0.5.5";
42
45
 
43
46
  // src/client.jsx
44
47
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -100,13 +103,14 @@ var styles = `
100
103
  .mr nav{gap:4px;padding:16px 0 0;margin-bottom:24px}.mr nav button{border:0;border-radius:0;background:transparent;padding:12px 14px;border-bottom:2px solid transparent;color:var(--mr-muted)}.mr nav button[aria-pressed=true]{background:transparent;color:var(--mr-text);border-bottom-color:var(--mr-text);font-weight:600}.mr nav button:hover{background:var(--mr-soft)}
101
104
  .mr button.primary{background:var(--mr-text);color:var(--mr-card);border-color:var(--mr-text)}.mr button.primary:hover{opacity:.85}.mr button:active:not(:disabled){filter:brightness(.9)}.mr button.quiet{color:var(--mr-muted)}.mr .badge{border-radius:100px;background:var(--mr-soft);padding:4px 10px}.mr .metric{background:var(--mr-soft)}.mr .metric strong{font-size:28px;font-variant-numeric:tabular-nums;letter-spacing:-.5px;margin:10px 0 4px}.mr .metrics{gap:16px}
102
105
  .mr .status{display:inline-flex;align-items:center;gap:7px;font-size:13px;white-space:nowrap}.mr .status i{width:8px;height:8px;border-radius:50%;background:var(--mr-muted);display:inline-block;flex-shrink:0}.mr .status i.good{background:var(--dsw-alias-state-success-primary,#38a16a)}.mr .status i.warn{background:var(--dsw-alias-state-warning-primary,#c48b28)}.mr .warn{color:var(--dsw-alias-state-warning-primary,#c48b28)}
103
- .mr .table-shell{padding:0;overflow:hidden}.mr .scroll>.table{min-width:760px}.mr .table{font-size:14px}.mr .table th{background:var(--mr-soft);font-size:12px;letter-spacing:.02em;font-weight:500;white-space:nowrap}.mr .table th,.mr .table td{padding:16px 20px;vertical-align:middle}.mr .table tbody tr:last-child td{border-bottom:0}.mr .table td small{display:block;font-size:12px;margin-top:3px}.mr .table td>strong{font-weight:600}.mr .table tr.selected{background:var(--mr-selected)}.mr .table tbody tr:hover{background:var(--mr-soft)}.mr .table .account-name{max-width:230px;overflow-wrap:anywhere}.mr .table .amount{font-size:20px;line-height:1.35;font-variant-numeric:tabular-nums;font-weight:600;white-space:nowrap}.mr .table .cell-actions{white-space:nowrap;text-align:right}.mr .table .account-check{min-width:44px;min-height:44px;justify-content:center;flex-shrink:0}.mr .table .account-cell{display:flex;gap:10px;align-items:center}.mr .table .account-cell .check{margin-left:-12px}.mr .table .account-cell input{accent-color:var(--mr-text)}.mr .table .row{flex-wrap:nowrap}.mr .section-title{margin-bottom:4px}.mr .toolbar{align-items:flex-end;gap:16px}.mr .search{max-width:280px;flex:1;min-width:160px}.mr .table-footer{padding:14px 20px;background:var(--mr-soft)}.mr .hint{font-size:13px;color:var(--mr-muted)}.mr details.help{border:0;padding:0}.mr details.help>div{padding:12px 0;max-width:76ch}.mr .plan-form{align-items:flex-end;padding:16px 0 0;border-top:1px solid var(--mr-border)}.mr .plan-form .filter{min-width:180px}.mr .steps{border:1px solid var(--mr-border);border-radius:10px;background:var(--mr-soft);padding:14px 16px}.mr .route-path span{background:var(--mr-soft);border-color:transparent;font-size:12px}.mr .route-card details{padding-top:10px}.mr .feedback{margin-bottom:16px}.mr .box .feedback{margin-bottom:0}.mr .scroll{max-width:100%}.mr .scroll:focus-visible{outline:2px solid var(--mr-accent);outline-offset:-3px}
106
+ .mr .table-shell{padding:0;overflow:hidden}.mr .scroll>.table{min-width:760px}.mr .table{font-size:14px}.mr .table th{background:var(--mr-soft);font-size:12px;letter-spacing:.02em;font-weight:500;white-space:nowrap}.mr .table th,.mr .table td{padding:16px 20px;vertical-align:middle}.mr .table td:last-child{white-space:nowrap}.mr .table tbody tr:last-child td{border-bottom:0}.mr .table td small{display:block;font-size:12px;margin-top:3px}.mr .table td>strong{font-weight:600}.mr .table tr.selected{background:var(--mr-selected)}.mr .table tbody tr:hover{background:var(--mr-soft)}.mr .table .account-name{max-width:230px;overflow-wrap:anywhere}.mr .table .amount{font-size:20px;line-height:1.35;font-variant-numeric:tabular-nums;font-weight:600;white-space:nowrap}.mr .table .cell-actions{white-space:nowrap;text-align:right}.mr .table .account-check{min-width:44px;min-height:44px;justify-content:center;flex-shrink:0}.mr .table .account-cell{display:flex;gap:10px;align-items:center}.mr .table .account-cell .check{margin-left:-12px}.mr .table .account-cell input{accent-color:var(--mr-text)}.mr .table .row{flex-wrap:nowrap}.mr .section-title{margin-bottom:4px}.mr .toolbar{align-items:flex-end;gap:16px}.mr .search{max-width:280px;flex:1;min-width:160px}.mr .table-footer{padding:14px 20px;background:var(--mr-soft)}.mr .hint{font-size:13px;color:var(--mr-muted)}.mr details.help{border:0;padding:0}.mr details.help>div{padding:12px 0;max-width:76ch}.mr .plan-form{align-items:flex-end;padding:16px 0 0;border-top:1px solid var(--mr-border)}.mr .plan-form .filter{min-width:180px}.mr .steps{border:1px solid var(--mr-border);border-radius:10px;background:var(--mr-soft);padding:14px 16px}.mr .route-path span{background:var(--mr-soft);border-color:transparent;font-size:12px}.mr .route-card details{padding-top:10px}.mr .feedback{margin-bottom:16px}.mr .box .feedback{margin-bottom:0}.mr .scroll{max-width:100%}.mr .scroll:focus-visible{outline:2px solid var(--mr-accent);outline-offset:-3px}
104
107
  @media(max-width:850px){.mr .metrics{gap:10px}.mr nav{gap:0}.mr nav button{padding:12px 10px}}@media(max-width:600px){.mr h2{font-size:22px}.mr nav{display:grid;grid-template-columns:repeat(4,minmax(0,1fr))}.mr nav button{padding:10px 4px;font-size:12px;white-space:nowrap}.mr .toolbar>.row{width:100%}.mr .toolbar .search{max-width:none}.mr .metric strong{font-size:24px}.mr .table th,.mr .table td{padding:14px 16px}.mr .filter{max-width:none}.mr .plan-form label{flex:1}.mr .box.table-shell{padding:0}}
105
108
 
106
109
  @media(prefers-reduced-motion:reduce){.mr *{transition:none!important}}
107
110
  `;
108
111
  function apply(ctx) {
109
112
  ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({ name: "plugins.bundle.config", key: "dsh-multi-router" }, RouterPage));
113
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({ name: "shell.overlay", key: "dsh-multi-router-updates" }, UpdateReminder));
110
114
  }
111
115
  function Modal({ title, close, children, busy }) {
112
116
  const ref = (0, import_react.useRef)(null);
@@ -126,6 +130,98 @@ function Modal({ title, close, children, busy }) {
126
130
  children
127
131
  ] });
128
132
  }
133
+ function UpdateReminder() {
134
+ const [data, setData] = (0, import_react.useState)(null), [opened, setOpened] = (0, import_react.useState)(null), [manage, setManage] = (0, import_react.useState)(false), [busy, setBusy] = (0, import_react.useState)(false), [error, setError] = (0, import_react.useState)("");
135
+ (0, import_react.useEffect)(() => {
136
+ let live = true, pending = false;
137
+ async function poll() {
138
+ if (document.hidden || pending) return;
139
+ pending = true;
140
+ try {
141
+ const next = await readManagementResponse(await fetch(ENDPOINT, { cache: "no-store" }));
142
+ if (live && !backendNeedsRestart(next)) {
143
+ setData(next);
144
+ setOpened((current) => current ?? (next.pluginUpdates?.reminders?.length && !document.querySelector("dialog[open]") ? next.pluginUpdates.reminders : null));
145
+ }
146
+ } catch {
147
+ } finally {
148
+ pending = false;
149
+ }
150
+ }
151
+ void poll();
152
+ const timer = setInterval(() => {
153
+ void poll();
154
+ }, 6e4);
155
+ return () => {
156
+ live = false;
157
+ clearInterval(timer);
158
+ };
159
+ }, []);
160
+ (0, import_react.useEffect)(() => {
161
+ if (data?.pluginUpdates?.job?.state !== "running") return;
162
+ let live = true;
163
+ const timer = setInterval(() => {
164
+ void fetch(ENDPOINT, { cache: "no-store" }).then(readManagementResponse).then((next) => {
165
+ if (live) setData(next);
166
+ }).catch(() => {
167
+ });
168
+ }, 2e3);
169
+ return () => {
170
+ live = false;
171
+ clearInterval(timer);
172
+ };
173
+ }, [data?.pluginUpdates?.job?.state]);
174
+ async function act(action) {
175
+ if (busy) return false;
176
+ setBusy(true);
177
+ setError("");
178
+ try {
179
+ const next = await readManagementResponse(await fetch(ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json", "X-Multi-Router-Key": data.key }, body: JSON.stringify({ revision: data.revision, ...action }) }));
180
+ setData(next);
181
+ return next;
182
+ } catch (e) {
183
+ setError(e.message);
184
+ return false;
185
+ } finally {
186
+ setBusy(false);
187
+ }
188
+ }
189
+ async function dismiss() {
190
+ if (await act({ action: "plugins.dismiss", keys: opened.map((r) => r.key) })) {
191
+ setOpened(null);
192
+ setManage(false);
193
+ }
194
+ }
195
+ if (!opened || !data) return null;
196
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mr", children: [
197
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: styles }),
198
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Modal, { title: "\u53D1\u73B0\u63D2\u4EF6\u66F4\u65B0", busy: busy || data.pluginUpdates?.job?.state === "running", close: () => {
199
+ void dismiss();
200
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-body stack", children: [
201
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Feedback, { error }),
202
+ manage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PluginUpdatesPanel, { data: data.pluginUpdates, busy, act }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
203
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
204
+ opened.length,
205
+ " \u4E2A\u7528\u6237\u5B89\u88C5\u7684\u63D2\u4EF6\u6709\u66F4\u65B0\u3002\u8BF7\u9009\u62E9\u67E5\u770B\u66F4\u65B0\uFF0C\u6216\u7A0D\u540E\u5904\u7406\u3002"
206
+ ] }),
207
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: opened.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
208
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: r.name }),
209
+ " \xB7 ",
210
+ r.version || "\u672A\u77E5\u7248\u672C",
211
+ " \u2192 ",
212
+ r.latestVersion || `Git ${r.latestCommit?.slice(0, 8)}`
213
+ ] }, r.key)) }),
214
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u6BCF 24 \u5C0F\u65F6\u81EA\u52A8\u68C0\u67E5 GitHub / npm \u5B89\u88C5\u6765\u6E90\uFF1B\u540C\u4E00\u7248\u672C\u53EA\u63D0\u9192\u4E00\u6B21\u3002DSH \u5185\u7F6E\u63D2\u4EF6\u4E0D\u53C2\u4E0E\u68C0\u67E5\uFF0C\u4E0D\u4F1A\u81EA\u52A8\u5B89\u88C5\u66F4\u65B0\u3002" })
215
+ ] }),
216
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
217
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy || data.pluginUpdates?.job?.state === "running", onClick: () => {
218
+ void dismiss();
219
+ }, children: "\u7A0D\u540E\u5904\u7406" }),
220
+ !manage && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary", disabled: busy, onClick: () => setManage(true), children: "\u67E5\u770B\u5E76\u66F4\u65B0" })
221
+ ] })
222
+ ] }) })
223
+ ] });
224
+ }
129
225
  function Feedback({ error, notice }) {
130
226
  return error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "feedback error", role: "alert", children: error }) : notice ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "feedback", role: "status", children: notice }) : null;
131
227
  }
@@ -136,7 +232,7 @@ var metric = (label, value, detail) => /* @__PURE__ */ (0, import_jsx_runtime.js
136
232
  ] });
137
233
  function RouterPage({ view = "page" }) {
138
234
  const [data, setData] = (0, import_react.useState)(null), [tab, setTab] = (0, import_react.useState)("overview");
139
- const [provider, setProvider] = (0, import_react.useState)("workbuddy"), [busy, setBusy] = (0, import_react.useState)(false), [live, setLive] = (0, import_react.useState)(true);
235
+ const [accountProvider, setAccountProvider] = (0, import_react.useState)(""), [provider, setProvider] = (0, import_react.useState)("workbuddy"), [busy, setBusy] = (0, import_react.useState)(false), [live, setLive] = (0, import_react.useState)(true);
140
236
  const [error, setError] = (0, import_react.useState)(""), [notice, setNotice] = (0, import_react.useState)(""), [draft, setDraft] = (0, import_react.useState)(null), [accountModal, setAccountModal] = (0, import_react.useState)(null), [presetModal, setPresetModal] = (0, import_react.useState)(false), [search, setSearch] = (0, import_react.useState)("");
141
237
  const polling = (0, import_react.useRef)(false);
142
238
  async function load() {
@@ -145,6 +241,13 @@ function RouterPage({ view = "page" }) {
145
241
  setError("");
146
242
  }
147
243
  (0, import_react.useEffect)(() => {
244
+ const accountProviderSelect2 = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "filter", children: [
245
+ "\u4F9B\u5E94\u5546 / \u533A\u57DF",
246
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { value: accountProvider, onChange: (e) => setAccountProvider(e.target.value), children: [
247
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5168\u90E8\u4F9B\u5E94\u5546" }),
248
+ data?.providers.map((p2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: p2.id, children: p2.name }, p2.id))
249
+ ] })
250
+ ] });
148
251
  if (view === "summary") return;
149
252
  void load().catch((e) => setError(e.message));
150
253
  if (!live) return;
@@ -206,6 +309,13 @@ function RouterPage({ view = "page" }) {
206
309
  setSearch("");
207
310
  }, children: data?.providers.map((p2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: p2.id, children: p2.name }, p2.id)) })
208
311
  ] });
312
+ const accountProviderSelect = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "filter", children: [
313
+ "\u4F9B\u5E94\u5546 / \u533A\u57DF",
314
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { value: accountProvider, onChange: (e) => setAccountProvider(e.target.value), children: [
315
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5168\u90E8\u4F9B\u5E94\u5546" }),
316
+ data?.providers.map((p2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: p2.id, children: p2.name }, p2.id))
317
+ ] })
318
+ ] });
209
319
  if (view === "summary") return "\u4E09\u5BB6\u8D26\u53F7\u4E0E\u767B\u5F55\u4FE1\u606F\u3001\u7528\u91CF\u76D1\u63A7\u3001\u6A21\u578B\u7BA1\u7406\uFF0C\u4EE5\u53CA\u6743\u91CD\u548C\u989D\u5EA6\u8DEF\u7531\u3002";
210
320
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mr", children: [
211
321
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: styles }),
@@ -239,7 +349,7 @@ function RouterPage({ view = "page" }) {
239
349
  tab === "plugins" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PluginUpdatesPanel, { data: data.pluginUpdates, busy, act }),
240
350
  tab === "checkins" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckinPanel, { data, busy, act }),
241
351
  tab === "usage" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UsageDetails, { data, busy, act }),
242
- tab === "accounts" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccountPool, { data, provider, providerSelect, busy, act, manage: (a) => openAccount("edit", a), add: () => openAccount("add") }, provider),
352
+ 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),
243
353
  tab === "models" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "stack", children: [
244
354
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "box stack", children: [
245
355
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bar", children: [
@@ -410,8 +520,8 @@ function RouterPage({ view = "page" }) {
410
520
  }, children: "\u8865\u9F50\u5168\u90E8\u9884\u914D\u7F6E" })
411
521
  ] })
412
522
  ] }),
413
- accountModal && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Modal, { title: accountModal.mode === "edit" ? "\u8D26\u53F7\u767B\u5F55\u8BE6\u60C5" : accountModal.mode === "credentials" ? "\u8865\u5145 / \u66F4\u65B0\u8D26\u53F7\u51ED\u8BC1" : "\u6DFB\u52A0\u8D26\u53F7\u4E0E\u83B7\u53D6\u767B\u5F55", busy, close: () => setAccountModal(null), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccountForm, { modal: accountModal, provider, data, busy, act, replace: () => openAccount("credentials", accountModal.account), close: (selected) => {
414
- if (typeof selected === "string") setProvider(selected);
523
+ accountModal && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Modal, { title: accountModal.mode === "edit" ? "\u8D26\u53F7\u767B\u5F55\u8BE6\u60C5" : accountModal.mode === "credentials" ? "\u8865\u5145 / \u66F4\u65B0\u8D26\u53F7\u51ED\u8BC1" : "\u6DFB\u52A0\u8D26\u53F7\u4E0E\u83B7\u53D6\u767B\u5F55", busy, close: () => setAccountModal(null), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccountForm, { modal: accountModal, provider: accountProvider || "workbuddy", data, busy, act, replace: () => openAccount("credentials", accountModal.account), close: (selected) => {
524
+ if (typeof selected === "string" && accountProvider) setAccountProvider(selected);
415
525
  setAccountModal(null);
416
526
  }, error, notice }) })
417
527
  ] });
@@ -439,11 +549,11 @@ function CheckinResult({ row, compact = false }) {
439
549
  }
440
550
  function AccountPool({ data, provider, providerSelect, busy, act, manage, add }) {
441
551
  const [search, setSearch] = (0, import_react.useState)("");
442
- const accounts = data.accounts.filter((a) => a.provider === provider), chosen = accounts.filter((a) => a.enabled !== false).length;
552
+ const accounts = data.accounts.filter((a) => !provider || a.provider === provider), chosen = accounts.filter((a) => a.enabled !== false).length;
443
553
  const visible = accounts.filter((a) => `${a.name} ${a.id}`.toLowerCase().includes(search.toLowerCase()));
444
554
  const rows = data.checkins?.accounts || [], eligible = rows.filter((r) => accounts.some((a) => a.id === r.id && a.enabled !== false) && r.ready);
445
555
  const setAll = (enabled) => {
446
- void act({ action: "accounts.enabled", provider, enabled });
556
+ void act({ action: "accounts.enabled", ...provider ? { provider } : {}, enabled });
447
557
  };
448
558
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "stack", children: [
449
559
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bar", children: [
@@ -467,11 +577,11 @@ function AccountPool({ data, provider, providerSelect, busy, act, manage, add })
467
577
  ] })
468
578
  ] }),
469
579
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
470
- !provider.startsWith("qoder") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => {
471
- void act({ action: "accounts.discover", provider });
580
+ !provider?.startsWith("qoder") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => {
581
+ void act({ action: "accounts.discover", ...provider ? { provider } : {} });
472
582
  }, children: "\u83B7\u53D6\u672C\u673A\u767B\u5F55" }),
473
583
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => {
474
- void act({ action: "monitor.refresh", provider });
584
+ void act({ action: "monitor.refresh", ...provider ? { provider } : {} });
475
585
  }, children: "\u5237\u65B0\u72B6\u6001\u4E0E\u7528\u91CF" })
476
586
  ] })
477
587
  ] }),
@@ -513,6 +623,7 @@ function AccountPool({ data, provider, providerSelect, busy, act, manage, add })
513
623
  } }) }),
514
624
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "account-name", children: [
515
625
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: displayName(a) }),
626
+ !provider && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "muted", children: data.providers?.find((p) => p.id === a.provider)?.name || a.provider }),
516
627
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "status", children: [
517
628
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", { className: !enabled ? "" : cooldown.length ? "warn" : "good", "aria-hidden": "true" }),
518
629
  !enabled ? "\u672A\u5165\u6C60" : active ? "\u6B63\u5728\u8C03\u7528" : cooldown.length ? "\u90E8\u5206\u8DEF\u7531\u51B7\u5374\u4E2D" : "\u5DF2\u5728\u6C60\u4E2D"
@@ -549,7 +660,9 @@ function AccountPool({ data, provider, providerSelect, busy, act, manage, add })
549
660
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u672C\u673A\u8D26\u53F7\u83B7\u53D6\u4E0E\u51ED\u8BC1\u8BF4\u660E" }),
550
661
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "stack", children: [
551
662
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "WorkBuddy \u4E0E Trae \u542F\u52A8\u65F6\u81EA\u52A8\u8BFB\u53D6\u672C\u673A\u5DF2\u767B\u5F55\u8D26\u53F7\uFF0C\u6BCF\u5206\u949F\u91CD\u65B0\u68C0\u6D4B\uFF1BTrae \u81EA\u52A8\u8BC6\u522B CN\u3001Global \u4E0E SOLO\u3002\u4E5F\u53EF\u6DFB\u52A0\u624B\u52A8\u51ED\u8BC1\u6216\u5BFC\u5165\u5176\u5B83\u8BBE\u5907\u8D26\u53F7\uFF0C\u5404\u8D26\u53F7\u72EC\u7ACB\u4FDD\u5B58\u3002Qoder \u9700\u6DFB\u52A0 PAT\u3002" }),
552
- data.localDiscovery?.filter((s) => s.provider === provider).map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "notice", children: [
663
+ data.localDiscovery?.filter((s) => !provider || s.provider === provider).map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "notice", children: [
664
+ data.providers?.find((p) => p.id === s.provider)?.name || s.provider,
665
+ " \xB7 ",
553
666
  s.state === "found" ? "\u5DF2\u53D1\u73B0\u672C\u673A\u5BA2\u6237\u7AEF\u767B\u5F55\u3002" : s.state === "unreadable" ? "\u65E0\u6CD5\u8BFB\u53D6\u672C\u673A\u767B\u5F55\u6587\u4EF6\uFF0C\u8BF7\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u91CD\u65B0\u767B\u5F55\uFF0C\u6216\u624B\u52A8\u586B\u5199\u51ED\u8BC1\u3002" : "\u672A\u627E\u5230\u672C\u673A\u767B\u5F55\uFF0C\u8BF7\u5148\u767B\u5F55\u5BF9\u5E94\u533A\u57DF\u7684\u5B98\u65B9\u5BA2\u6237\u7AEF\u3002",
554
667
  " \xB7 ",
555
668
  when(s.checkedAt)
@@ -699,6 +812,7 @@ function Overview({ data, live, setLive, busy, act, go }) {
699
812
  metric("\u8D26\u53F7\u6C60", `${data.accounts.filter((a) => a.enabled).length} / ${data.accounts.length}`, "\u542F\u7528 / \u5168\u90E8\u8D26\u53F7"),
700
813
  metric("\u7D2F\u8BA1 Token", fmt(totals.tokens), `${fmt(totals.requests)} \u6B21\u8BF7\u6C42`)
701
814
  ] }),
815
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PerformanceMetrics, { events: (t.events || []).filter((e) => e.at >= usageDateBounds("1")[0]) }),
702
816
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bar", children: [
703
817
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u5E73\u53F0\u989D\u5EA6" }),
704
818
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
@@ -785,6 +899,7 @@ function Overview({ data, live, setLive, busy, act, go }) {
785
899
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8D26\u53F7" }),
786
900
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8DEF\u7531 / \u6A21\u578B" }),
787
901
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "Token" }),
902
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u6027\u80FD" }),
788
903
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7ED3\u679C" })
789
904
  ] }) }),
790
905
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: (t.events || []).slice(0, 15).map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
@@ -792,6 +907,7 @@ function Overview({ data, live, setLive, busy, act, go }) {
792
907
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: data.accounts.find((a) => a.id === e.account)?.name || e.account }),
793
908
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "mono", children: e.alias }),
794
909
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmt(e.tokens) }),
910
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RequestPerformance, { event: e }) }),
795
911
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: e.outcome === "completed" ? "\u6210\u529F" : e.outcome === "cancelled" ? "\u5DF2\u53D6\u6D88" : `\u5931\u8D25 ${e.status}` })
796
912
  ] }, `${e.at}:${i}`)) })
797
913
  ] }),
@@ -807,6 +923,46 @@ function usageDateBounds(period, start, end, now = Date.now()) {
807
923
  if (period === "all") return [-Infinity, Infinity];
808
924
  return [midnight(today) - (Number(period) - 1) * 864e5, midnight(today) + 864e5];
809
925
  }
926
+ var duration = (ms) => typeof ms !== "number" || !Number.isFinite(ms) ? "\u2014" : ms < 6e4 ? `${fmt(ms / 1e3)} \u79D2` : `${Math.floor(ms / 6e4)} \u5206 ${fmt(ms % 6e4 / 1e3)} \u79D2`;
927
+ function performanceSummary(events) {
928
+ const rows = events.filter((e) => e.outcome === "completed" && e.timing);
929
+ const sum = (key, source = rows) => source.some((e) => e.timing[key] != null) ? source.reduce((n, e) => n + (e.timing[key] ?? 0), 0) : null;
930
+ const first = rows.filter((e) => e.timing.ttftMs != null), decode = rows.filter((e) => e.timing.decodeMs > 0 && e.timing.decodeTokens != null), tools = rows.filter((e) => e.timingSource === "dsh-step");
931
+ return {
932
+ modelMs: sum("modelMs"),
933
+ toolMs: sum("toolMs", tools),
934
+ ttftMs: first.length ? sum("ttftMs", first) / first.length : null,
935
+ tps: decode.length ? sum("decodeTokens", decode) / (sum("decodeMs", decode) / 1e3) : null,
936
+ samples: rows.length,
937
+ firstSamples: first.length,
938
+ decodeSamples: decode.length,
939
+ toolSamples: tools.length,
940
+ pendingTools: tools.reduce((n, e) => n + (e.timing.toolCount ?? 0) - (e.timing.toolsCompleted ?? 0), 0)
941
+ };
942
+ }
943
+ function PerformanceMetrics({ events }) {
944
+ const stats = performanceSummary(events);
945
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "stack", "aria-label": "\u8C03\u7528\u6027\u80FD\u7EDF\u8BA1", children: [
946
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bar", children: [
947
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u8C03\u7528\u6027\u80FD" }),
948
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "notice", children: [
949
+ "\u6210\u529F\u8C03\u7528 \xB7 ",
950
+ fmt(stats.samples),
951
+ " \u6761\u6027\u80FD\u8BB0\u5F55"
952
+ ] })
953
+ ] }),
954
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "metrics", children: [
955
+ metric("\u6A21\u578B\u7528\u65F6", duration(stats.modelMs), "\u6210\u529F\u8C03\u7528\u7528\u65F6\u5408\u8BA1"),
956
+ metric("\u5DE5\u5177\u8C03\u7528\u7528\u65F6", duration(stats.toolMs), `${stats.toolSamples} \u6761 DSH \u6B65\u9AA4${stats.pendingTools ? ` \xB7 ${stats.pendingTools} \u4E2A\u5DE5\u5177\u672A\u5B8C\u6210` : ""}`),
957
+ metric("\u9996 Token \u5E73\u5747\uFF08TTFT\uFF09", duration(stats.ttftMs), `${stats.firstSamples} \u6761\u9996 Token \u8BB0\u5F55`),
958
+ metric("\u8F93\u51FA\u901F\u5EA6\uFF08TPS\uFF09", stats.tps === null ? "\u2014" : `${fmt(stats.tps)} tok/s`, `${stats.decodeSamples} \u6761\u89E3\u7801\u8BB0\u5F55 \xB7 \u52A0\u6743\u901F\u5EA6`)
959
+ ] }),
960
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "help", children: [
961
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u7EDF\u8BA1\u53E3\u5F84\u4E0E\u7F3A\u5931\u6570\u636E" }),
962
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u968F\u5F53\u524D\u65F6\u95F4\u3001\u8D26\u53F7\u3001\u8DEF\u7531\u548C\u7ED3\u679C\u7B5B\u9009\u6C47\u603B\uFF0C\u4ECE\u672C\u7248\u5F00\u59CB\u8BB0\u5F55\uFF1B\u65E7\u6570\u636E\u4E0D\u8865\u7B97\u3002DSH \u6B65\u9AA4\u53E3\u5F84\u4E0E\u4F1A\u8BDD\u7EDF\u8BA1\u4E00\u81F4\uFF1A\u6A21\u578B\u7528\u65F6\u4ECE\u6B65\u9AA4\u5F00\u59CB\u5230\u56DE\u590D\u5B8C\u6210\uFF0C\u9996 Token \u5305\u62EC\u975E\u7A7A\u6587\u672C\u3001\u63A8\u7406\u6216\u5DE5\u5177\u8C03\u7528\uFF0C\u5DE5\u5177\u7528\u65F6\u6309\u8C03\u7528\u4E0E\u7ED3\u679C\u914D\u5BF9\u76F8\u52A0\uFF08\u5E76\u884C\u5DE5\u5177\u53EF\u80FD\u91CD\u53E0\uFF09\u3002TPS = \u6709\u6548\u8F93\u51FA Token \u5408\u8BA1 \xF7 \u9996 Token \u540E\u7684\u89E3\u7801\u79D2\u6570\u5408\u8BA1\uFF0C\u4E0D\u5BF9\u5404\u6B21\u901F\u5EA6\u7B80\u5355\u5E73\u5747\u3002\u672A\u7ECF\u8FC7 DSH \u6B65\u9AA4\u7684\u8BF7\u6C42\u4F7F\u7528\u4E0A\u6E38\u8BA1\u65F6\uFF0C\u65E0\u6CD5\u8BB0\u5F55\u5DE5\u5177\u6267\u884C\u7528\u65F6\u3002\u8FD9\u91CC\u53EA\u6C47\u603B\u672C\u63D2\u4EF6\u4FDD\u7559\u4E14\u7B5B\u9009\u5185\u7684\u8C03\u7528\uFF0C\u5E76\u975E\u67D0\u4E2A\u4F1A\u8BDD\u7684\u5B8C\u6574\u5386\u53F2\u3002\u5931\u8D25\u4E0E\u53D6\u6D88\u4FDD\u7559\u9010\u6B21\u8017\u65F6\uFF0C\u4E0D\u8BA1\u5165\u4EE5\u4E0A\u6210\u529F\u8C03\u7528\u6307\u6807\u3002" }) })
963
+ ] })
964
+ ] });
965
+ }
810
966
  function UsageDetails({ data, busy, act }) {
811
967
  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);
812
968
  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" }));
@@ -923,8 +1079,9 @@ function UsageDetails({ data, busy, act }) {
923
1079
  missing ? fmt(missing) + " \u6761\u7F3A\u5C11\u8F93\u5165\u6216\u8F93\u51FA\u5206\u9879\uFF1B\u65E7\u8BB0\u5F55\u4E0D\u8865\u7B97\u3002" : "\u8F93\u5165\u3001\u8F93\u51FA\u4E3A\u5B9E\u9645\u8FD4\u56DE\u503C\u3002",
924
1080
  "\u7F13\u5B58\u8F93\u5165\u5305\u542B\u5728\u8F93\u5165\u4E2D\uFF0C\u63A8\u7406\u8F93\u51FA\u5305\u542B\u5728\u8F93\u51FA\u4E2D\uFF0C\u5747\u4E0D\u91CD\u590D\u76F8\u52A0\u3002\u672A\u8FD4\u56DE\u7684\u6570\u636E\u4EE5\u300C\u2014\u300D\u663E\u793A\u3002"
925
1081
  ] }),
1082
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PerformanceMetrics, { events }),
926
1083
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "box", children: [
927
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scroll", children: [
1084
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scroll", role: "region", "aria-label": "\u8BF7\u6C42\u7528\u91CF\u4E0E\u6027\u80FD\u660E\u7EC6", tabIndex: "0", children: [
928
1085
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { className: "table", children: [
929
1086
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
930
1087
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u65F6\u95F4" }),
@@ -933,6 +1090,7 @@ function UsageDetails({ data, busy, act }) {
933
1090
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u5165" }),
934
1091
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u51FA" }),
935
1092
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u603B Token" }),
1093
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u6027\u80FD" }),
936
1094
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7ED3\u679C" })
937
1095
  ] }) }),
938
1096
  /* @__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: [
@@ -968,6 +1126,7 @@ function UsageDetails({ data, busy, act }) {
968
1126
  ] })
969
1127
  ] }),
970
1128
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmt(e.tokens) }),
1129
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RequestPerformance, { event: e }) }),
971
1130
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
972
1131
  e.outcome === "completed" ? "\u6210\u529F" : e.outcome === "cancelled" ? "\u5DF2\u53D6\u6D88" : "\u5931\u8D25",
973
1132
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
@@ -993,6 +1152,33 @@ function UsageDetails({ data, busy, act }) {
993
1152
  ] })
994
1153
  ] });
995
1154
  }
1155
+ function RequestPerformance({ event: e }) {
1156
+ const t = e.timing;
1157
+ if (!t) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: "\u672A\u8BB0\u5F55" });
1158
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "help", children: [
1159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: duration(t.modelMs) }),
1160
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { minWidth: 170 }, children: [
1161
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { children: [
1162
+ "\u6A21\u578B\uFF1A",
1163
+ duration(t.modelMs)
1164
+ ] }),
1165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { children: [
1166
+ "\u9996 Token\uFF1A",
1167
+ duration(t.ttftMs)
1168
+ ] }),
1169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { children: [
1170
+ "TPS\uFF1A",
1171
+ t.decodeMs > 0 && t.decodeTokens != null ? `${fmt(t.decodeTokens / (t.decodeMs / 1e3))} tok/s` : "\u2014"
1172
+ ] }),
1173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { children: [
1174
+ "\u5DE5\u5177\uFF1A",
1175
+ duration(t.toolMs),
1176
+ t.toolCount != null ? ` \xB7 ${t.toolsCompleted} / ${t.toolCount} \u5B8C\u6210` : ""
1177
+ ] }),
1178
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: e.timingSource === "dsh-step" ? "DSH \u6B65\u9AA4\u8BA1\u65F6" : "\u4E0A\u6E38\u8BF7\u6C42\u8BA1\u65F6" })
1179
+ ] })
1180
+ ] });
1181
+ }
996
1182
  function AccountForm({ modal, provider, data, busy, act, close, replace, error, notice }) {
997
1183
  const [revision, setRevision] = (0, import_react.useState)(modal.revision);
998
1184
  const edit = modal.mode === "edit", account = edit ? data.accounts.find((a) => a.id === modal.account.id) : null;