dsh-multi-router 0.5.4 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js 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.6";
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;
@@ -176,9 +279,9 @@ function RouterPage({ view = "page" }) {
176
279
  try {
177
280
  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 }) });
178
281
  const next = await readManagementResponse(res);
179
- const { exported, ...state } = next;
282
+ const { exported, workbuddyLogin, loginResult, ...state } = next;
180
283
  setData(state);
181
- setNotice(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");
284
+ 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");
182
285
  return next;
183
286
  } catch (e) {
184
287
  setError(e.message);
@@ -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)
@@ -670,7 +783,9 @@ function CheckinPanel({ data, busy, act }) {
670
783
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "muted", children: [r.last.activity.streakDays !== void 0 && `\u8FDE\u7B7E ${fmt(r.last.activity.streakDays)} \u5929`, r.last.activity.totalCredits !== void 0 && `\u6D3B\u52A8\u7D2F\u8BA1 ${fmt(r.last.activity.totalCredits)} \u79EF\u5206`, r.last.activity.nextStreakDay !== void 0 && `\u4E0B\u4E00\u8FDE\u7B7E\u6863\u4F4D ${fmt(r.last.activity.nextStreakDay)} \u5929`, r.last.activity.endsAt !== void 0 && `\u6D3B\u52A8\u622A\u6B62 ${when(r.last.activity.endsAt)}`].filter(Boolean).join(" \xB7 ") })
671
784
  ] })
672
785
  ] }),
673
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "cell-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: blocked || !ready(r), onClick: () => {
786
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "cell-actions", children: p.id === "workbuddy" && r.last?.status === "auth-invalid" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: blocked, onClick: () => {
787
+ void act({ action: "checkins.verify", id: a.id });
788
+ }, children: "\u91CD\u65B0\u9A8C\u8BC1\u767B\u5F55" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: blocked || !ready(r), onClick: () => {
674
789
  void act({ action: "checkins.run", ids: [a.id] });
675
790
  }, children: r.last?.status === "auth-invalid" ? "\u8BF7\u66F4\u65B0\u51ED\u8BC1" : last?.retryReady ? "\u8865\u67E5\u7B7E\u5230" : last?.retryAt ? "\u7B49\u5F85\u665A\u95F4\u8865\u67E5" : last ? "\u4ECA\u65E5\u5DF2\u5904\u7406" : "\u7ACB\u5373\u7B7E\u5230" }) })
676
791
  ] }, a.id);
@@ -699,6 +814,7 @@ function Overview({ data, live, setLive, busy, act, go }) {
699
814
  metric("\u8D26\u53F7\u6C60", `${data.accounts.filter((a) => a.enabled).length} / ${data.accounts.length}`, "\u542F\u7528 / \u5168\u90E8\u8D26\u53F7"),
700
815
  metric("\u7D2F\u8BA1 Token", fmt(totals.tokens), `${fmt(totals.requests)} \u6B21\u8BF7\u6C42`)
701
816
  ] }),
817
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PerformanceMetrics, { events: (t.events || []).filter((e) => e.at >= usageDateBounds("1")[0]) }),
702
818
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bar", children: [
703
819
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u5E73\u53F0\u989D\u5EA6" }),
704
820
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
@@ -785,6 +901,7 @@ function Overview({ data, live, setLive, busy, act, go }) {
785
901
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8D26\u53F7" }),
786
902
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8DEF\u7531 / \u6A21\u578B" }),
787
903
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "Token" }),
904
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u6027\u80FD" }),
788
905
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7ED3\u679C" })
789
906
  ] }) }),
790
907
  /* @__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 +909,7 @@ function Overview({ data, live, setLive, busy, act, go }) {
792
909
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: data.accounts.find((a) => a.id === e.account)?.name || e.account }),
793
910
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "mono", children: e.alias }),
794
911
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmt(e.tokens) }),
912
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RequestPerformance, { event: e }) }),
795
913
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: e.outcome === "completed" ? "\u6210\u529F" : e.outcome === "cancelled" ? "\u5DF2\u53D6\u6D88" : `\u5931\u8D25 ${e.status}` })
796
914
  ] }, `${e.at}:${i}`)) })
797
915
  ] }),
@@ -807,6 +925,46 @@ function usageDateBounds(period, start, end, now = Date.now()) {
807
925
  if (period === "all") return [-Infinity, Infinity];
808
926
  return [midnight(today) - (Number(period) - 1) * 864e5, midnight(today) + 864e5];
809
927
  }
928
+ 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`;
929
+ function performanceSummary(events) {
930
+ const rows = events.filter((e) => e.outcome === "completed" && e.timing);
931
+ const sum = (key, source = rows) => source.some((e) => e.timing[key] != null) ? source.reduce((n, e) => n + (e.timing[key] ?? 0), 0) : null;
932
+ 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");
933
+ return {
934
+ modelMs: sum("modelMs"),
935
+ toolMs: sum("toolMs", tools),
936
+ ttftMs: first.length ? sum("ttftMs", first) / first.length : null,
937
+ tps: decode.length ? sum("decodeTokens", decode) / (sum("decodeMs", decode) / 1e3) : null,
938
+ samples: rows.length,
939
+ firstSamples: first.length,
940
+ decodeSamples: decode.length,
941
+ toolSamples: tools.length,
942
+ pendingTools: tools.reduce((n, e) => n + (e.timing.toolCount ?? 0) - (e.timing.toolsCompleted ?? 0), 0)
943
+ };
944
+ }
945
+ function PerformanceMetrics({ events }) {
946
+ const stats = performanceSummary(events);
947
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "stack", "aria-label": "\u8C03\u7528\u6027\u80FD\u7EDF\u8BA1", children: [
948
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bar", children: [
949
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u8C03\u7528\u6027\u80FD" }),
950
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "notice", children: [
951
+ "\u6210\u529F\u8C03\u7528 \xB7 ",
952
+ fmt(stats.samples),
953
+ " \u6761\u6027\u80FD\u8BB0\u5F55"
954
+ ] })
955
+ ] }),
956
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "metrics", children: [
957
+ metric("\u6A21\u578B\u7528\u65F6", duration(stats.modelMs), "\u6210\u529F\u8C03\u7528\u7528\u65F6\u5408\u8BA1"),
958
+ 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` : ""}`),
959
+ metric("\u9996 Token \u5E73\u5747\uFF08TTFT\uFF09", duration(stats.ttftMs), `${stats.firstSamples} \u6761\u9996 Token \u8BB0\u5F55`),
960
+ 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`)
961
+ ] }),
962
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "help", children: [
963
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u7EDF\u8BA1\u53E3\u5F84\u4E0E\u7F3A\u5931\u6570\u636E" }),
964
+ /* @__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" }) })
965
+ ] })
966
+ ] });
967
+ }
810
968
  function UsageDetails({ data, busy, act }) {
811
969
  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
970
  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 +1081,9 @@ function UsageDetails({ data, busy, act }) {
923
1081
  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
1082
  "\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
1083
  ] }),
1084
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PerformanceMetrics, { events }),
926
1085
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "box", children: [
927
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scroll", children: [
1086
+ /* @__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
1087
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { className: "table", children: [
929
1088
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
930
1089
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u65F6\u95F4" }),
@@ -933,6 +1092,7 @@ function UsageDetails({ data, busy, act }) {
933
1092
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u5165" }),
934
1093
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u8F93\u51FA" }),
935
1094
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u603B Token" }),
1095
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u6027\u80FD" }),
936
1096
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7ED3\u679C" })
937
1097
  ] }) }),
938
1098
  /* @__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 +1128,7 @@ function UsageDetails({ data, busy, act }) {
968
1128
  ] })
969
1129
  ] }),
970
1130
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: fmt(e.tokens) }),
1131
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RequestPerformance, { event: e }) }),
971
1132
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
972
1133
  e.outcome === "completed" ? "\u6210\u529F" : e.outcome === "cancelled" ? "\u5DF2\u53D6\u6D88" : "\u5931\u8D25",
973
1134
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
@@ -993,6 +1154,33 @@ function UsageDetails({ data, busy, act }) {
993
1154
  ] })
994
1155
  ] });
995
1156
  }
1157
+ function RequestPerformance({ event: e }) {
1158
+ const t = e.timing;
1159
+ if (!t) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: "\u672A\u8BB0\u5F55" });
1160
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "help", children: [
1161
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: duration(t.modelMs) }),
1162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { minWidth: 170 }, children: [
1163
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { children: [
1164
+ "\u6A21\u578B\uFF1A",
1165
+ duration(t.modelMs)
1166
+ ] }),
1167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { children: [
1168
+ "\u9996 Token\uFF1A",
1169
+ duration(t.ttftMs)
1170
+ ] }),
1171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { children: [
1172
+ "TPS\uFF1A",
1173
+ t.decodeMs > 0 && t.decodeTokens != null ? `${fmt(t.decodeTokens / (t.decodeMs / 1e3))} tok/s` : "\u2014"
1174
+ ] }),
1175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { children: [
1176
+ "\u5DE5\u5177\uFF1A",
1177
+ duration(t.toolMs),
1178
+ t.toolCount != null ? ` \xB7 ${t.toolsCompleted} / ${t.toolCount} \u5B8C\u6210` : ""
1179
+ ] }),
1180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: e.timingSource === "dsh-step" ? "DSH \u6B65\u9AA4\u8BA1\u65F6" : "\u4E0A\u6E38\u8BF7\u6C42\u8BA1\u65F6" })
1181
+ ] })
1182
+ ] });
1183
+ }
996
1184
  function AccountForm({ modal, provider, data, busy, act, close, replace, error, notice }) {
997
1185
  const [revision, setRevision] = (0, import_react.useState)(modal.revision);
998
1186
  const edit = modal.mode === "edit", account = edit ? data.accounts.find((a) => a.id === modal.account.id) : null;
@@ -1138,6 +1326,13 @@ function CredentialForm({ modal, provider, data, busy, act, close, error, notice
1138
1326
  const replacing = modal.mode === "credentials";
1139
1327
  const [selected, setSelected] = (0, import_react.useState)(modal.account?.provider || provider), [mode, setMode] = (0, import_react.useState)("client"), [localError, setLocalError] = (0, import_react.useState)(""), [json, setJson] = (0, import_react.useState)("");
1140
1328
  const [edition, setEdition] = (0, import_react.useState)(modal.account?.login?.edition || "auto");
1329
+ const [phone, setPhone] = (0, import_react.useState)(""), [smsCode, setSmsCode] = (0, import_react.useState)(""), [sms, setSms] = (0, import_react.useState)(null), [verify, setVerify] = (0, import_react.useState)(true), [clock, setClock] = (0, import_react.useState)(Date.now());
1330
+ (0, import_react.useEffect)(() => {
1331
+ if (mode !== "sms") return;
1332
+ const timer = setInterval(() => setClock(Date.now()), 1e3);
1333
+ return () => clearInterval(timer);
1334
+ }, [mode]);
1335
+ const resend = Math.max(0, Math.ceil(((sms?.resendAt || 0) - clock) / 1e3));
1141
1336
  const isTrae = selected.startsWith("trae"), isQoder = selected.startsWith("qoder"), global = selected === "trae-global" || selected === "workbuddy-ai" || selected === "qoder-global";
1142
1337
  const field = (label, name2, type = "text", required = true, value) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
1143
1338
  label,
@@ -1159,6 +1354,16 @@ function CredentialForm({ modal, provider, data, busy, act, close, error, notice
1159
1354
  e.preventDefault();
1160
1355
  setLocalError("");
1161
1356
  const f = Object.fromEntries(new FormData(e.currentTarget));
1357
+ if (mode === "sms") {
1358
+ const result = await act({ action: "workbuddy.login", revision: data.revision, challenge: sms?.challenge, code: smsCode, verify, id: replacing ? modal.account.id : void 0, name: f.name || void 0 });
1359
+ if (result) {
1360
+ setPhone("");
1361
+ setSmsCode("");
1362
+ setSms(null);
1363
+ close(selected);
1364
+ }
1365
+ return;
1366
+ }
1162
1367
  let document2, identityDocument;
1163
1368
  try {
1164
1369
  if (mode === "json") {
@@ -1206,8 +1411,12 @@ function CredentialForm({ modal, provider, data, busy, act, close, error, notice
1206
1411
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { disabled: busy || replacing, value: selected, onChange: (e) => {
1207
1412
  setSelected(e.target.value);
1208
1413
  setEdition("auto");
1414
+ setMode("client");
1209
1415
  setJson("");
1210
1416
  setLocalError("");
1417
+ setSms(null);
1418
+ setSmsCode("");
1419
+ setPhone("");
1211
1420
  }, children: data.providers.map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: p.id, children: p.name }, p.id)) })
1212
1421
  ] }),
1213
1422
  field("\u8D26\u53F7\u540D\u79F0\uFF08\u53EF\u9009\uFF09", "name", "text", false, modal.account?.name),
@@ -1225,6 +1434,7 @@ function CredentialForm({ modal, provider, data, busy, act, close, error, notice
1225
1434
  setLocalError("");
1226
1435
  }, children: [
1227
1436
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "client", children: "\u4E00\u952E\u8BFB\u53D6\u672C\u673A\u767B\u5F55" }),
1437
+ selected === "workbuddy" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "sms", children: "\u77ED\u4FE1\u767B\u5F55\u5E76\u4FDD\u5B58\u51ED\u8BC1" }),
1228
1438
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", { value: "manual", children: [
1229
1439
  "\u624B\u52A8\u586B\u5199",
1230
1440
  isQoder ? " PAT" : "\u51ED\u8BC1"
@@ -1232,7 +1442,38 @@ function CredentialForm({ modal, provider, data, busy, act, close, error, notice
1232
1442
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "json", children: "\u7C98\u8D34 / \u9009\u62E9\u51ED\u8BC1 JSON" })
1233
1443
  ] })
1234
1444
  ] }),
1235
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "steps stack", children: [
1445
+ mode === "sms" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "steps stack", children: [
1446
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "WorkBuddy \u624B\u673A\u53F7\u767B\u5F55" }),
1447
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u4EC5\u7528\u4E8E\u5DF2\u6709\u4E2D\u56FD\u7248\u8D26\u53F7\u3002\u624B\u673A\u53F7\u548C\u9A8C\u8BC1\u7801\u63D0\u4EA4\u81F3 WorkBuddy \u5B98\u65B9\uFF0C\u51ED\u8BC1\u4EC5\u4FDD\u5B58\u5728\u672C\u673A\uFF0C\u65E0\u9700\u5B89\u88C5 Python\u3002" }),
1448
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
1449
+ "\u624B\u673A\u53F7",
1450
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "tel", inputMode: "tel", autoComplete: "tel", required: true, pattern: "1[3-9][0-9]{9}", maxLength: "11", value: phone, disabled: busy || resend > 0, onChange: (e) => {
1451
+ setPhone(e.target.value);
1452
+ setSms(null);
1453
+ setSmsCode("");
1454
+ } })
1455
+ ] }),
1456
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
1457
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy || resend > 0 || !/^1[3-9]\d{9}$/.test(phone), onClick: async () => {
1458
+ const next = await act({ action: "workbuddy.sms", phone });
1459
+ if (next) {
1460
+ setSms(next.workbuddyLogin);
1461
+ setClock(Date.now());
1462
+ setSmsCode("");
1463
+ }
1464
+ }, children: resend ? `${resend} \u79D2\u540E\u53EF\u91CD\u53D1` : sms ? "\u91CD\u65B0\u53D1\u9001\u9A8C\u8BC1\u7801" : "\u53D1\u9001\u9A8C\u8BC1\u7801" }),
1465
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "notice", role: "status", children: sms ? clock >= sms.expiresAt ? "\u9A8C\u8BC1\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u83B7\u53D6" : "\u9A8C\u8BC1\u7801\u5DF2\u53D1\u9001\uFF0C\u8BF7\u67E5\u770B\u77ED\u4FE1" : "\u70B9\u51FB\u53D1\u9001\u624D\u4F1A\u8BF7\u6C42\u77ED\u4FE1\uFF0C\u4E0D\u81EA\u52A8\u91CD\u8BD5" })
1466
+ ] }),
1467
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
1468
+ "\u77ED\u4FE1\u9A8C\u8BC1\u7801",
1469
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "text", inputMode: "numeric", autoComplete: "one-time-code", required: true, pattern: "[0-9]{4,8}", maxLength: "8", value: smsCode, disabled: busy || !sms, onChange: (e) => setSmsCode(e.target.value) })
1470
+ ] }),
1471
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "check", children: [
1472
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: verify, disabled: busy, onChange: (e) => setVerify(e.target.checked) }),
1473
+ "\u767B\u5F55\u540E\u9A8C\u8BC1\u5237\u65B0\u4EE4\u724C\uFF08\u63A8\u8350\uFF09"
1474
+ ] }),
1475
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u540C\u4E00\u8D26\u53F7\u66F4\u65B0\u51ED\u8BC1\uFF0C\u65B0\u8D26\u53F7\u72EC\u7ACB\u4FDD\u5B58\uFF1B\u4E0D\u4F1A\u5F00\u542F\u81EA\u52A8\u7B7E\u5230\u3002\u9047\u5E73\u53F0\u9A8C\u8BC1\u6216\u9650\u5236\uFF0C\u8BF7\u6539\u7528\u672C\u673A\u5BA2\u6237\u7AEF\u8BFB\u53D6\u3002" })
1476
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "steps stack", children: [
1236
1477
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u83B7\u53D6\u51ED\u8BC1\u5F15\u5BFC" }),
1237
1478
  isQoder ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "\u5728\u5BF9\u5E94\u56FD\u5185 / \u56FD\u9645\u7AD9\u7684\u8D26\u53F7\u8BBE\u7F6E\u4E2D\u521B\u5EFA PAT\uFF0C\u7136\u540E\u9009\u62E9\u300C\u624B\u52A8\u586B\u5199 PAT\u300D\u7C98\u8D34\u3002\u5DF2\u6709\u65E7\u8FDE\u63A5\u5668\u767B\u5F55\u65F6\uFF0C\u53EF\u4E00\u952E\u8BFB\u53D6\u672C\u673A\u4FDD\u5B58\u7684 PAT\u3002\u7F51\u9875\u767B\u5F55 Cookie \u4E0D\u4F5C\u4E3A PAT \u4F7F\u7528\u3002" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
1238
1479
  "1. \u5728\u6240\u9009\u533A\u57DF\u7684\u5B98\u65B9\u5BA2\u6237\u7AEF\u767B\u5F55\u8D26\u53F7\u3002",
@@ -1295,14 +1536,14 @@ function CredentialForm({ modal, provider, data, busy, act, close, error, notice
1295
1536
  ] })
1296
1537
  ] })
1297
1538
  ] }, `${selected}:${mode}`),
1298
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "notice", children: [
1539
+ mode !== "sms" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "notice", children: [
1299
1540
  "\u51ED\u8BC1\u4EC5\u4FDD\u5B58\u5230\u672C\u673A\u63D2\u4EF6\u8D26\u53F7\u6C60\u3002\u4E0D\u4F1A\u5728\u8D26\u53F7\u5217\u8868\u663E\u793A Token\uFF1B\u6BCF\u4E2A\u8D26\u53F7\u72EC\u7ACB\u5237\u65B0\u3002",
1300
1541
  replacing ? "\u4FDD\u5B58\u540E\u542F\u7528\u6B64\u8D26\u53F7\uFF0C\u539F\u51ED\u8BC1\u526F\u672C\u4FDD\u7559\u3002" : "\u91CD\u590D\u8BFB\u53D6\u540C\u4E00\u8D26\u53F7\u4F1A\u63D0\u793A\u3002"
1301
1542
  ] })
1302
1543
  ] }),
1303
1544
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-foot bar", children: [
1304
1545
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy, onClick: close, children: "\u53D6\u6D88" }),
1305
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary", type: "submit", disabled: busy, children: busy ? "\u6B63\u5728\u5904\u7406\u2026" : mode === "client" ? "\u4E00\u952E\u83B7\u53D6\u5E76\u4FDD\u5B58\u767B\u5F55" : "\u4FDD\u5B58\u51ED\u8BC1\u5E76\u83B7\u53D6\u6A21\u578B" })
1546
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary", type: "submit", disabled: busy || mode === "sms" && (!sms || clock >= sms.expiresAt), children: busy ? "\u6B63\u5728\u5904\u7406\u2026" : mode === "sms" ? "\u767B\u5F55\u5E76\u4FDD\u5B58\u5230\u8D26\u53F7\u6C60" : mode === "client" ? "\u4E00\u952E\u83B7\u53D6\u5E76\u4FDD\u5B58\u767B\u5F55" : "\u4FDD\u5B58\u51ED\u8BC1\u5E76\u83B7\u53D6\u6A21\u578B" })
1306
1547
  ] })
1307
1548
  ] });
1308
1549
  }