@yuhere/js-jsonrpc2 2.1.12 → 2.1.14

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 (181) hide show
  1. package/dist/cli/cli.d.ts +0 -1
  2. package/dist/cli/cli.js +0 -1
  3. package/dist/cli/demo-methods.d.ts +0 -1
  4. package/dist/cli/demo-methods.js +0 -1
  5. package/dist/cli/express.d.ts +0 -1
  6. package/dist/cli/express.js +0 -1
  7. package/dist/cli/stdio.d.ts +0 -1
  8. package/dist/cli/stdio.js +0 -1
  9. package/dist/core/Repository.d.ts +0 -1
  10. package/dist/core/Repository.js +0 -1
  11. package/dist/core/convert.d.ts +0 -1
  12. package/dist/core/convert.js +0 -1
  13. package/dist/core/descriptor.d.ts +0 -1
  14. package/dist/core/descriptor.js +0 -1
  15. package/dist/core/index.d.ts +0 -1
  16. package/dist/core/index.js +0 -1
  17. package/dist/core/llm-tools.d.ts +0 -1
  18. package/dist/core/llm-tools.js +41 -43
  19. package/dist/core/permission.d.ts +0 -1
  20. package/dist/core/permission.js +0 -1
  21. package/dist/core/spec-check.d.ts +0 -1
  22. package/dist/core/spec-check.js +0 -1
  23. package/dist/core/system-methods.d.ts +0 -1
  24. package/dist/core/system-methods.js +0 -1
  25. package/dist/errors/RPCError.d.ts +0 -1
  26. package/dist/errors/RPCError.js +0 -1
  27. package/dist/errors/codes.d.ts +0 -1
  28. package/dist/errors/codes.js +0 -1
  29. package/dist/errors/index.d.ts +0 -1
  30. package/dist/errors/index.js +0 -1
  31. package/dist/index.d.ts +0 -1
  32. package/dist/index.js +0 -1
  33. package/dist/metrics/memory.d.ts +0 -1
  34. package/dist/metrics/memory.js +0 -1
  35. package/dist/metrics/prom.d.ts +0 -1
  36. package/dist/metrics/prom.js +0 -1
  37. package/dist/schema/json/index.d.ts +1 -2
  38. package/dist/schema/json/index.js +0 -1
  39. package/dist/schema/json/marker.d.ts +0 -1
  40. package/dist/schema/json/marker.js +0 -1
  41. package/dist/schema/json/primitives.d.ts +49 -70
  42. package/dist/schema/json/primitives.js +70 -77
  43. package/dist/schema/z/protocol.d.ts +1 -2
  44. package/dist/schema/z/protocol.js +0 -1
  45. package/dist/schema/z/register.d.ts +0 -1
  46. package/dist/schema/z/register.js +0 -1
  47. package/dist/stream/stringify-stream.d.ts +0 -1
  48. package/dist/stream/stringify-stream.js +94 -70
  49. package/dist/transport/express/body.d.ts +0 -1
  50. package/dist/transport/express/body.js +0 -1
  51. package/dist/transport/express/index.d.ts +0 -1
  52. package/dist/transport/express/index.js +0 -1
  53. package/dist/transport/express/middleware.d.ts +0 -1
  54. package/dist/transport/express/middleware.js +0 -1
  55. package/dist/transport/express/respond.d.ts +0 -1
  56. package/dist/transport/express/respond.js +0 -1
  57. package/dist/transport/stdio/index.d.ts +0 -1
  58. package/dist/transport/stdio/index.js +0 -1
  59. package/dist/transport/stdio/middleware.d.ts +0 -1
  60. package/dist/transport/stdio/middleware.js +0 -1
  61. package/dist/transport/ws/index.d.ts +0 -1
  62. package/dist/transport/ws/index.js +0 -1
  63. package/dist/transport/ws/middleware.d.ts +0 -1
  64. package/dist/transport/ws/middleware.js +0 -1
  65. package/dist/transport/ws/standalone.d.ts +0 -1
  66. package/dist/transport/ws/standalone.js +0 -1
  67. package/dist/types.d.ts +0 -1
  68. package/dist/types.js +0 -1
  69. package/dist/webclient/webui/src/client/httpTransport.d.ts +8 -0
  70. package/dist/webclient/webui/src/client/httpTransport.js +114 -0
  71. package/dist/webclient/webui/src/client/index.d.ts +4 -0
  72. package/dist/webclient/webui/src/client/index.js +2 -0
  73. package/dist/webclient/webui/src/client/rpc.d.ts +24 -0
  74. package/dist/webclient/webui/src/client/rpc.js +32 -0
  75. package/dist/webclient/webui/src/client/types.d.ts +14 -0
  76. package/dist/webclient/webui/src/client/types.js +1 -0
  77. package/dist/webclient/webui/src/client/wsTransport.d.ts +8 -0
  78. package/{webui/src/api/wsTransport.ts → dist/webclient/webui/src/client/wsTransport.js} +263 -305
  79. package/htdoc/assets/favicon-CXdFuLlv.png +0 -0
  80. package/{webui/src/styles/geek.css → htdoc/assets/index-Rq1yIwo2.css} +58 -0
  81. package/htdoc/assets/{index-Cr0Jnrgb.js → index-mYqtDOwe.js} +1344 -398
  82. package/htdoc/assets/index-mYqtDOwe.js.map +1 -0
  83. package/htdoc/index.html +4 -2
  84. package/package.json +10 -7
  85. package/dist/cli/cli.d.ts.map +0 -1
  86. package/dist/cli/cli.js.map +0 -1
  87. package/dist/cli/demo-methods.d.ts.map +0 -1
  88. package/dist/cli/demo-methods.js.map +0 -1
  89. package/dist/cli/express.d.ts.map +0 -1
  90. package/dist/cli/express.js.map +0 -1
  91. package/dist/cli/stdio.d.ts.map +0 -1
  92. package/dist/cli/stdio.js.map +0 -1
  93. package/dist/core/Repository.d.ts.map +0 -1
  94. package/dist/core/Repository.js.map +0 -1
  95. package/dist/core/convert.d.ts.map +0 -1
  96. package/dist/core/convert.js.map +0 -1
  97. package/dist/core/descriptor.d.ts.map +0 -1
  98. package/dist/core/descriptor.js.map +0 -1
  99. package/dist/core/index.d.ts.map +0 -1
  100. package/dist/core/index.js.map +0 -1
  101. package/dist/core/llm-tools.d.ts.map +0 -1
  102. package/dist/core/llm-tools.js.map +0 -1
  103. package/dist/core/permission.d.ts.map +0 -1
  104. package/dist/core/permission.js.map +0 -1
  105. package/dist/core/spec-check.d.ts.map +0 -1
  106. package/dist/core/spec-check.js.map +0 -1
  107. package/dist/core/system-methods.d.ts.map +0 -1
  108. package/dist/core/system-methods.js.map +0 -1
  109. package/dist/errors/RPCError.d.ts.map +0 -1
  110. package/dist/errors/RPCError.js.map +0 -1
  111. package/dist/errors/codes.d.ts.map +0 -1
  112. package/dist/errors/codes.js.map +0 -1
  113. package/dist/errors/index.d.ts.map +0 -1
  114. package/dist/errors/index.js.map +0 -1
  115. package/dist/index.d.ts.map +0 -1
  116. package/dist/index.js.map +0 -1
  117. package/dist/metrics/memory.d.ts.map +0 -1
  118. package/dist/metrics/memory.js.map +0 -1
  119. package/dist/metrics/prom.d.ts.map +0 -1
  120. package/dist/metrics/prom.js.map +0 -1
  121. package/dist/schema/json/index.d.ts.map +0 -1
  122. package/dist/schema/json/index.js.map +0 -1
  123. package/dist/schema/json/marker.d.ts.map +0 -1
  124. package/dist/schema/json/marker.js.map +0 -1
  125. package/dist/schema/json/primitives.d.ts.map +0 -1
  126. package/dist/schema/json/primitives.js.map +0 -1
  127. package/dist/schema/z/protocol.d.ts.map +0 -1
  128. package/dist/schema/z/protocol.js.map +0 -1
  129. package/dist/schema/z/register.d.ts.map +0 -1
  130. package/dist/schema/z/register.js.map +0 -1
  131. package/dist/stream/stringify-stream.d.ts.map +0 -1
  132. package/dist/stream/stringify-stream.js.map +0 -1
  133. package/dist/transport/express/body.d.ts.map +0 -1
  134. package/dist/transport/express/body.js.map +0 -1
  135. package/dist/transport/express/index.d.ts.map +0 -1
  136. package/dist/transport/express/index.js.map +0 -1
  137. package/dist/transport/express/middleware.d.ts.map +0 -1
  138. package/dist/transport/express/middleware.js.map +0 -1
  139. package/dist/transport/express/respond.d.ts.map +0 -1
  140. package/dist/transport/express/respond.js.map +0 -1
  141. package/dist/transport/stdio/index.d.ts.map +0 -1
  142. package/dist/transport/stdio/index.js.map +0 -1
  143. package/dist/transport/stdio/middleware.d.ts.map +0 -1
  144. package/dist/transport/stdio/middleware.js.map +0 -1
  145. package/dist/transport/ws/index.d.ts.map +0 -1
  146. package/dist/transport/ws/index.js.map +0 -1
  147. package/dist/transport/ws/middleware.d.ts.map +0 -1
  148. package/dist/transport/ws/middleware.js.map +0 -1
  149. package/dist/transport/ws/standalone.d.ts.map +0 -1
  150. package/dist/transport/ws/standalone.js.map +0 -1
  151. package/dist/types.d.ts.map +0 -1
  152. package/dist/types.js.map +0 -1
  153. package/htdoc/assets/index-Ck-39Igh.css +0 -963
  154. package/htdoc/assets/index-Cr0Jnrgb.js.map +0 -1
  155. package/webui/index.html +0 -12
  156. package/webui/src/App.tsx +0 -164
  157. package/webui/src/api/docGen.ts +0 -188
  158. package/webui/src/api/glob.ts +0 -90
  159. package/webui/src/api/httpTransport.ts +0 -148
  160. package/webui/src/api/metrics.ts +0 -135
  161. package/webui/src/api/paramForm.ts +0 -80
  162. package/webui/src/api/rpc.ts +0 -57
  163. package/webui/src/api/schema.ts +0 -12
  164. package/webui/src/api/types.ts +0 -16
  165. package/webui/src/components/MethodDetail.tsx +0 -43
  166. package/webui/src/components/MethodTree.tsx +0 -118
  167. package/webui/src/components/MetricsPanel.md +0 -126
  168. package/webui/src/components/MetricsPanel.tsx +0 -290
  169. package/webui/src/components/SchemaForm.tsx +0 -697
  170. package/webui/src/components/SchemaView.tsx +0 -151
  171. package/webui/src/components/TestPanel.tsx +0 -322
  172. package/webui/src/components/Toolbar.tsx +0 -82
  173. package/webui/src/hooks/useMethodDetail.ts +0 -61
  174. package/webui/src/hooks/useMethods.ts +0 -27
  175. package/webui/src/hooks/useMetrics.ts +0 -91
  176. package/webui/src/main.tsx +0 -13
  177. package/webui/src/theme/ThemeProvider.tsx +0 -79
  178. package/webui/src/vite-env.d.ts +0 -1
  179. package/webui/tsconfig.json +0 -34
  180. package/webui/tsconfig.node.json +0 -13
  181. package/webui/vite.config.ts +0 -15
@@ -7537,9 +7537,7 @@ var require_client = /* @__PURE__ */ __commonJSMin(((exports) => {
7537
7537
  exports.hydrateRoot = m.hydrateRoot;
7538
7538
  }));
7539
7539
  //#endregion
7540
- //#region webui/src/api/httpTransport.ts
7541
- var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
7542
- var import_client = /* @__PURE__ */ __toESM(require_client(), 1);
7540
+ //#region webui/src/client/httpTransport.ts
7543
7541
  var idCounter$1 = 0;
7544
7542
  async function readLine(reader, decoder, state) {
7545
7543
  while (true) {
@@ -7562,7 +7560,7 @@ async function readLine(reader, decoder, state) {
7562
7560
  function isStreamFrame$1(v) {
7563
7561
  return !!v && typeof v === "object" && typeof v.done === "boolean";
7564
7562
  }
7565
- function createHttpRpc(options = {}) {
7563
+ function createHttpRPC(options = {}) {
7566
7564
  const { endpoint = "", headers = {}, fetchImpl = fetch } = options;
7567
7565
  function call(method, ...params) {
7568
7566
  const body = {
@@ -7576,7 +7574,7 @@ function createHttpRpc(options = {}) {
7576
7574
  method: "POST",
7577
7575
  headers: {
7578
7576
  "Content-Type": "application/json",
7579
- "Accept": "application/x-ndjson",
7577
+ "Accept": "application/json, application/x-ndjson",
7580
7578
  ...headers
7581
7579
  },
7582
7580
  body: JSON.stringify(body)
@@ -7643,7 +7641,7 @@ function absoluteEndpoint(endpoint) {
7643
7641
  }
7644
7642
  }
7645
7643
  //#endregion
7646
- //#region webui/src/api/wsTransport.ts
7644
+ //#region webui/src/client/wsTransport.ts
7647
7645
  var idCounter = 0;
7648
7646
  function deriveWsEndpoint() {
7649
7647
  const loc = window.location;
@@ -7652,7 +7650,7 @@ function deriveWsEndpoint() {
7652
7650
  function isStreamFrame(v) {
7653
7651
  return !!v && typeof v === "object" && typeof v.done === "boolean";
7654
7652
  }
7655
- function createWsRpc(options = {}) {
7653
+ function createWsRPC(options = {}) {
7656
7654
  const { endpoint = deriveWsEndpoint(), connectTimeoutMs = 5e3, callTimeoutMs = 15e3 } = options;
7657
7655
  const pending = /* @__PURE__ */ new Map();
7658
7656
  const streams = /* @__PURE__ */ new Map();
@@ -7688,6 +7686,7 @@ function createWsRpc(options = {}) {
7688
7686
  }
7689
7687
  const list = Array.isArray(data) ? data : [data];
7690
7688
  for (const msg of list) {
7689
+ if (!msg || typeof msg !== "object") continue;
7691
7690
  if (msg.id == null) continue;
7692
7691
  const fw = firstWaiters.get(msg.id);
7693
7692
  if (fw) {
@@ -7902,7 +7901,7 @@ function _defineProperty(e, r, t) {
7902
7901
  }) : e[r] = t, e;
7903
7902
  }
7904
7903
  //#endregion
7905
- //#region webui/src/api/rpc.ts
7904
+ //#region webui/src/client/rpc.ts
7906
7905
  var RPCError = class extends Error {
7907
7906
  constructor(payload) {
7908
7907
  super(payload.message);
@@ -7920,13 +7919,13 @@ var RPCError = class extends Error {
7920
7919
  function isStreamResult(v) {
7921
7920
  return !!v && typeof v === "object" && typeof v[Symbol.asyncIterator] === "function";
7922
7921
  }
7923
- function createRpc(options) {
7924
- if (options.transport === "ws") return createWsRpc({
7922
+ function createRPC(options) {
7923
+ if (options.transport === "ws") return createWsRPC({
7925
7924
  endpoint: options.endpoint,
7926
7925
  connectTimeoutMs: options.connectTimeoutMs,
7927
7926
  callTimeoutMs: options.callTimeoutMs
7928
7927
  });
7929
- return createHttpRpc({
7928
+ return createHttpRPC({
7930
7929
  endpoint: options.endpoint,
7931
7930
  headers: options.headers,
7932
7931
  fetchImpl: options.fetchImpl
@@ -7934,6 +7933,8 @@ function createRpc(options) {
7934
7933
  }
7935
7934
  //#endregion
7936
7935
  //#region webui/src/hooks/useMethods.ts
7936
+ var import_client = /* @__PURE__ */ __toESM(require_client(), 1);
7937
+ var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
7937
7938
  function useMethods(rpc) {
7938
7939
  const [methods, setMethods] = (0, import_react.useState)([]);
7939
7940
  const [loading, setLoading] = (0, import_react.useState)(false);
@@ -8013,6 +8014,24 @@ function useMethodDetail(rpc, method) {
8013
8014
  return detail;
8014
8015
  }
8015
8016
  //#endregion
8017
+ //#region webui/src/hooks/useDebouncedValue.ts
8018
+ /**
8019
+ * 返回 `value` 的防抖版本:`value` 停止变化 `delayMs` 毫秒后才更新返回值。
8020
+ * 初始值立即生效(首次渲染不会延迟)。
8021
+ */
8022
+ function useDebouncedValue(value, delayMs = 250) {
8023
+ const [debounced, setDebounced] = (0, import_react.useState)(value);
8024
+ (0, import_react.useEffect)(() => {
8025
+ if (delayMs <= 0) {
8026
+ setDebounced(value);
8027
+ return;
8028
+ }
8029
+ const t = setTimeout(() => setDebounced(value), delayMs);
8030
+ return () => clearTimeout(t);
8031
+ }, [value, delayMs]);
8032
+ return debounced;
8033
+ }
8034
+ //#endregion
8016
8035
  //#region webui/src/api/metrics.ts
8017
8036
  /** 拉取 system.metrics;如果系统方法不可用(withSystemMethods=false),返回 undefined。 */
8018
8037
  async function fetchMetrics(rpc) {
@@ -8187,11 +8206,11 @@ var require_react_jsx_runtime_production_min = /* @__PURE__ */ __commonJSMin(((e
8187
8206
  var import_jsx_runtime = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
8188
8207
  module.exports = require_react_jsx_runtime_production_min();
8189
8208
  })))();
8190
- var STORAGE_KEY = "jsonrpc2.theme";
8209
+ var STORAGE_KEY$1 = "jsonrpc2.theme";
8191
8210
  var ThemeContext = (0, import_react.createContext)(null);
8192
- function readStoredMode() {
8211
+ function readStoredMode$1() {
8193
8212
  try {
8194
- const v = localStorage.getItem(STORAGE_KEY);
8213
+ const v = localStorage.getItem(STORAGE_KEY$1);
8195
8214
  if (v === "light" || v === "dark" || v === "system") return v;
8196
8215
  } catch {}
8197
8216
  return "system";
@@ -8200,7 +8219,7 @@ function prefersDark() {
8200
8219
  return typeof window !== "undefined" && window.matchMedia?.("(prefers-color-scheme: dark)").matches;
8201
8220
  }
8202
8221
  function ThemeProvider({ children }) {
8203
- const [mode, setModeState] = (0, import_react.useState)(readStoredMode);
8222
+ const [mode, setModeState] = (0, import_react.useState)(readStoredMode$1);
8204
8223
  const [systemDark, setSystemDark] = (0, import_react.useState)(prefersDark);
8205
8224
  (0, import_react.useEffect)(() => {
8206
8225
  const mql = window.matchMedia("(prefers-color-scheme: dark)");
@@ -8219,7 +8238,7 @@ function ThemeProvider({ children }) {
8219
8238
  (0, import_react.useEffect)(() => {
8220
8239
  document.documentElement.setAttribute("data-theme", resolved);
8221
8240
  try {
8222
- localStorage.setItem(STORAGE_KEY, mode);
8241
+ localStorage.setItem(STORAGE_KEY$1, mode);
8223
8242
  } catch {}
8224
8243
  }, [resolved, mode]);
8225
8244
  const setMode = (0, import_react.useCallback)((m) => setModeState(m), []);
@@ -8242,54 +8261,812 @@ function useTheme() {
8242
8261
  if (!ctx) throw new Error("useTheme must be used within <ThemeProvider>");
8243
8262
  return ctx;
8244
8263
  }
8245
- //#endregion
8246
- //#region webui/src/components/Toolbar.tsx
8247
- var THEME_OPTIONS = [
8248
- {
8249
- value: "system",
8250
- label: "跟随系统"
8264
+ var MESSAGES = {
8265
+ en: {
8266
+ common: {
8267
+ loading: "Loading…",
8268
+ refresh: "Refresh",
8269
+ refreshing: "Refreshing…",
8270
+ reset: "Reset",
8271
+ clear: "Clear",
8272
+ delete: "Delete",
8273
+ add: "Add",
8274
+ copy: "Copy",
8275
+ copied: "Copied ✓",
8276
+ copyFailed: "Copy failed: {error}",
8277
+ generate: "Generate",
8278
+ generating: "Generating…",
8279
+ none: "None",
8280
+ empty: "Empty"
8281
+ },
8282
+ toolbar: {
8283
+ title: "JSON-RPC 2.0",
8284
+ transport: "Transport",
8285
+ http: "HTTP",
8286
+ ws: "WebSocket",
8287
+ refreshMethods: "Refresh methods",
8288
+ refreshMethodsLoading: "Refreshing…",
8289
+ generateDocs: "Generate docs ⬇",
8290
+ generateDocsLoading: "Generating…",
8291
+ generateDocsTitle: "Fetch help and signatures of all methods, render Markdown and download",
8292
+ theme: "Theme",
8293
+ themeSystem: "System",
8294
+ themeLight: "Light",
8295
+ themeDark: "Dark",
8296
+ language: "Language",
8297
+ languageSystem: "Follow system",
8298
+ languageEn: "English",
8299
+ languageZhCN: "简体中文",
8300
+ languageZhTW: "繁體中文",
8301
+ languageJa: "日本語"
8302
+ },
8303
+ sidebar: {
8304
+ methods: "Methods",
8305
+ metrics: "Metrics",
8306
+ search: "Search",
8307
+ searchPlaceholder: "Type keywords, space separated",
8308
+ matchCount: "Matched {matched} / {total} methods",
8309
+ noMatch: "No matching methods.",
8310
+ noMethods: "No methods available."
8311
+ },
8312
+ view: {
8313
+ label: "View",
8314
+ methods: "Methods",
8315
+ metrics: "Metrics"
8316
+ },
8317
+ method: {
8318
+ selectHint: "← Select a method from the left to view help and signature.",
8319
+ loading: "Loading…",
8320
+ loadFailed: "Load failed: {error}",
8321
+ help: "Help",
8322
+ noHelp: "(no help available)",
8323
+ returnType: "Return type",
8324
+ returnTypeHint: "(the server does not validate execution results against this type; actual values may differ)",
8325
+ none: "(none)"
8326
+ },
8327
+ test: {
8328
+ params: "Parameters",
8329
+ paramsHint: "({count}, auto-generated from signature)",
8330
+ noParams: "This method takes no parameters.",
8331
+ execute: "Execute ▶",
8332
+ executing: "Executing…",
8333
+ executeStream: "Execute & receive stream ▶",
8334
+ receiving: "Receiving…",
8335
+ resetToSample: "Reset to sample",
8336
+ clearOutput: "Clear output",
8337
+ result: "Result",
8338
+ resultStream: " (streaming)",
8339
+ notExecuted: "(not executed yet)",
8340
+ copyCurl: "Copy curl (bash)",
8341
+ copyCurlCmd: "Copy curl (cmd)",
8342
+ copyFetch: "Copy fetch",
8343
+ copyTitle: "Copy equivalent code for current call",
8344
+ copyDisabledTitle: "Only HTTP transport can generate code",
8345
+ chooseFormat: "Choose format",
8346
+ streamEnded: "(stream ended, {count} frames, total {duration})",
8347
+ streamEndedEmpty: "(stream ended, no data, total {duration})",
8348
+ elapsed: "elapsed",
8349
+ firstFrame: "first frame",
8350
+ jsonError: "JSON error: {error}"
8351
+ },
8352
+ schema: {
8353
+ nullable: "nullable",
8354
+ setNull: "Set null",
8355
+ fillValue: "Fill value",
8356
+ fixedValue: "(fixed value)",
8357
+ unionNoBranch: "(union has no branch)",
8358
+ arrayNoItem: "(array has no item schema)",
8359
+ recordNoValue: "(record has no value schema)",
8360
+ recordAddPlaceholder: "New key",
8361
+ addRecordKey: "+ Add key",
8362
+ addItem: "+ Add item",
8363
+ strictHint: "(non-strict mode, extra fields allowed)",
8364
+ paramIndex: "Parameter [{index}]",
8365
+ items: "items",
8366
+ values: "values",
8367
+ optionIndex: "option[{index}]",
8368
+ inner: "inner"
8369
+ },
8370
+ metrics: {
8371
+ title: "Metrics",
8372
+ unavailable: "Server did not enable system.metrics (withSystemMethods: false or not registered).",
8373
+ autoRefresh: "Auto refresh",
8374
+ interval: "Interval",
8375
+ refreshNow: "Refresh now",
8376
+ refreshing: "Refreshing…",
8377
+ uptime: "uptime",
8378
+ loading: "Loading…",
8379
+ methods: "Methods",
8380
+ calls: "Calls",
8381
+ streams: "Streams",
8382
+ errors: "Errors",
8383
+ errorRate: "Error rate",
8384
+ invalidRequests: "Invalid requests",
8385
+ nonStreamTitle: "Non-stream methods",
8386
+ streamTitle: "Stream methods",
8387
+ noData: "No data.",
8388
+ noStreams: "No streaming calls.",
8389
+ countHint: " ({count})",
8390
+ colMethod: "method",
8391
+ colCalls: "calls",
8392
+ colRate: "rate/s",
8393
+ colErrors: "errors",
8394
+ colErrPct: "err%",
8395
+ colP50: "p50",
8396
+ colP95: "p95",
8397
+ colP99: "p99",
8398
+ colAvg: "avg",
8399
+ colOpened: "opened",
8400
+ colCompleted: "completed",
8401
+ colFailed: "failed",
8402
+ colAborted: "aborted",
8403
+ colCompletePct: "complete%",
8404
+ colAbortPct: "abort%",
8405
+ colAvgChunks: "avg chunks",
8406
+ colTtfbP50: "ttfb p50",
8407
+ colTtfbP95: "ttfb p95",
8408
+ colDurationAvg: "duration avg"
8409
+ },
8410
+ footer: {
8411
+ version: "version",
8412
+ npm: "npm package",
8413
+ github: "GitHub repository"
8414
+ },
8415
+ alerts: {
8416
+ emptyMethods: "Method list is empty. Please refresh methods first.",
8417
+ docsFailed: "Failed to generate docs: {error}"
8418
+ }
8419
+ },
8420
+ "zh-CN": {
8421
+ common: {
8422
+ loading: "加载中…",
8423
+ refresh: "刷新",
8424
+ refreshing: "刷新中…",
8425
+ reset: "重置",
8426
+ clear: "清空",
8427
+ delete: "删除",
8428
+ add: "添加",
8429
+ copy: "复制",
8430
+ copied: "已复制 ✓",
8431
+ copyFailed: "复制失败:{error}",
8432
+ generate: "生成",
8433
+ generating: "生成中…",
8434
+ none: "无",
8435
+ empty: "空"
8436
+ },
8437
+ toolbar: {
8438
+ title: "JSON-RPC 2.0",
8439
+ transport: "传输方式",
8440
+ http: "HTTP",
8441
+ ws: "WebSocket",
8442
+ refreshMethods: "刷新方法列表",
8443
+ refreshMethodsLoading: "刷新中…",
8444
+ generateDocs: "生成文档 ⬇",
8445
+ generateDocsLoading: "生成中…",
8446
+ generateDocsTitle: "拉取所有方法的帮助与签名,生成 Markdown 文档并下载",
8447
+ theme: "主题",
8448
+ themeSystem: "跟随系统",
8449
+ themeLight: "亮",
8450
+ themeDark: "暗",
8451
+ language: "语言",
8452
+ languageSystem: "跟随系统语言",
8453
+ languageEn: "English",
8454
+ languageZhCN: "简体中文",
8455
+ languageZhTW: "繁體中文",
8456
+ languageJa: "日本語"
8457
+ },
8458
+ sidebar: {
8459
+ methods: "Methods",
8460
+ metrics: "Metrics",
8461
+ search: "搜索",
8462
+ searchPlaceholder: "输入关键词,空格分隔",
8463
+ matchCount: "匹配 {matched} / {total} 个方法",
8464
+ noMatch: "没有匹配的方法。",
8465
+ noMethods: "暂无可用方法。"
8466
+ },
8467
+ view: {
8468
+ label: "视图",
8469
+ methods: "Methods",
8470
+ metrics: "Metrics"
8471
+ },
8472
+ method: {
8473
+ selectHint: "← 从左侧选择一个方法查看帮助与签名。",
8474
+ loading: "加载中…",
8475
+ loadFailed: "加载失败:{error}",
8476
+ help: "Help",
8477
+ noHelp: "(无帮助信息)",
8478
+ returnType: "返回类型",
8479
+ returnTypeHint: "(服务端不会校验执行结果是否符合此类型,实际返回值可能会有所不同)",
8480
+ none: "(暂无)"
8481
+ },
8482
+ test: {
8483
+ params: "参数",
8484
+ paramsHint: "({count} 个,按签名自动生成)",
8485
+ noParams: "此方法无需参数。",
8486
+ execute: "执行 ▶",
8487
+ executing: "执行中…",
8488
+ executeStream: "执行并接收流 ▶",
8489
+ receiving: "接收中…",
8490
+ resetToSample: "重置为示例",
8491
+ clearOutput: "清空输出",
8492
+ result: "结果",
8493
+ resultStream: "(流式)",
8494
+ notExecuted: "(尚未执行)",
8495
+ copyCurl: "复制 curl (bash)",
8496
+ copyCurlCmd: "复制 curl (cmd)",
8497
+ copyFetch: "复制 fetch",
8498
+ copyTitle: "复制当前调用的等价代码",
8499
+ copyDisabledTitle: "仅 HTTP 传输可生成代码",
8500
+ chooseFormat: "选择复制格式",
8501
+ streamEnded: "(流已结束,共 {count} 帧,总耗时 {duration})",
8502
+ streamEndedEmpty: "(流已结束,无数据,总耗时 {duration})",
8503
+ elapsed: "耗时",
8504
+ firstFrame: "首帧",
8505
+ jsonError: "JSON 错误:{error}"
8506
+ },
8507
+ schema: {
8508
+ nullable: "nullable",
8509
+ setNull: "置 null",
8510
+ fillValue: "填值",
8511
+ fixedValue: "(固定值)",
8512
+ unionNoBranch: "(union 无分支)",
8513
+ arrayNoItem: "(数组无 item schema)",
8514
+ recordNoValue: "(record 无 value schema)",
8515
+ recordAddPlaceholder: "新键名",
8516
+ addRecordKey: "+ 添加键",
8517
+ addItem: "+ 添加项",
8518
+ strictHint: "(非 strict 模式,允许额外字段)",
8519
+ paramIndex: "参数 [{index}]",
8520
+ items: "items",
8521
+ values: "values",
8522
+ optionIndex: "option[{index}]",
8523
+ inner: "inner"
8524
+ },
8525
+ metrics: {
8526
+ title: "Metrics",
8527
+ unavailable: "服务端未启用 system.metrics(withSystemMethods: false 或未注册)。",
8528
+ autoRefresh: "自动刷新",
8529
+ interval: "刷新间隔",
8530
+ refreshNow: "立即刷新",
8531
+ refreshing: "刷新中…",
8532
+ uptime: "uptime",
8533
+ loading: "加载中…",
8534
+ methods: "Methods",
8535
+ calls: "Calls",
8536
+ streams: "Streams",
8537
+ errors: "Errors",
8538
+ errorRate: "Error rate",
8539
+ invalidRequests: "Invalid requests",
8540
+ nonStreamTitle: "Non-stream methods",
8541
+ streamTitle: "Stream methods",
8542
+ noData: "暂无数据。",
8543
+ noStreams: "暂无流式调用。",
8544
+ countHint: "({count})",
8545
+ colMethod: "method",
8546
+ colCalls: "calls",
8547
+ colRate: "rate/s",
8548
+ colErrors: "errors",
8549
+ colErrPct: "err%",
8550
+ colP50: "p50",
8551
+ colP95: "p95",
8552
+ colP99: "p99",
8553
+ colAvg: "avg",
8554
+ colOpened: "opened",
8555
+ colCompleted: "completed",
8556
+ colFailed: "failed",
8557
+ colAborted: "aborted",
8558
+ colCompletePct: "complete%",
8559
+ colAbortPct: "abort%",
8560
+ colAvgChunks: "avg chunks",
8561
+ colTtfbP50: "ttfb p50",
8562
+ colTtfbP95: "ttfb p95",
8563
+ colDurationAvg: "duration avg"
8564
+ },
8565
+ footer: {
8566
+ version: "版本",
8567
+ npm: "npm 包",
8568
+ github: "GitHub 仓库"
8569
+ },
8570
+ alerts: {
8571
+ emptyMethods: "方法列表为空,请先刷新方法列表。",
8572
+ docsFailed: "生成文档失败:{error}"
8573
+ }
8251
8574
  },
8252
- {
8253
- value: "light",
8254
- label: "亮"
8575
+ "zh-TW": {
8576
+ common: {
8577
+ loading: "載入中…",
8578
+ refresh: "重新整理",
8579
+ refreshing: "重新整理中…",
8580
+ reset: "重設",
8581
+ clear: "清除",
8582
+ delete: "刪除",
8583
+ add: "新增",
8584
+ copy: "複製",
8585
+ copied: "已複製 ✓",
8586
+ copyFailed: "複製失敗:{error}",
8587
+ generate: "產生",
8588
+ generating: "產生中…",
8589
+ none: "無",
8590
+ empty: "空"
8591
+ },
8592
+ toolbar: {
8593
+ title: "JSON-RPC 2.0",
8594
+ transport: "傳輸方式",
8595
+ http: "HTTP",
8596
+ ws: "WebSocket",
8597
+ refreshMethods: "重新整理方法清單",
8598
+ refreshMethodsLoading: "重新整理中…",
8599
+ generateDocs: "產生文件 ⬇",
8600
+ generateDocsLoading: "產生中…",
8601
+ generateDocsTitle: "取得所有方法的說明與簽章,產生 Markdown 文件並下載",
8602
+ theme: "主題",
8603
+ themeSystem: "跟隨系統",
8604
+ themeLight: "亮",
8605
+ themeDark: "暗",
8606
+ language: "語言",
8607
+ languageSystem: "跟隨系統語言",
8608
+ languageEn: "English",
8609
+ languageZhCN: "简体中文",
8610
+ languageZhTW: "繁體中文",
8611
+ languageJa: "日本語"
8612
+ },
8613
+ sidebar: {
8614
+ methods: "Methods",
8615
+ metrics: "Metrics",
8616
+ search: "搜尋",
8617
+ searchPlaceholder: "輸入關鍵字,以空格分隔",
8618
+ matchCount: "符合 {matched} / {total} 個方法",
8619
+ noMatch: "沒有符合的方法。",
8620
+ noMethods: "尚無可用方法。"
8621
+ },
8622
+ view: {
8623
+ label: "檢視",
8624
+ methods: "Methods",
8625
+ metrics: "Metrics"
8626
+ },
8627
+ method: {
8628
+ selectHint: "← 從左側選擇一個方法以檢視說明與簽章。",
8629
+ loading: "載入中…",
8630
+ loadFailed: "載入失敗:{error}",
8631
+ help: "Help",
8632
+ noHelp: "(無說明資訊)",
8633
+ returnType: "回傳型別",
8634
+ returnTypeHint: "(伺服器不會驗證執行結果是否符合此型別,實際回傳值可能有所不同)",
8635
+ none: "(暫無)"
8636
+ },
8637
+ test: {
8638
+ params: "參數",
8639
+ paramsHint: "({count} 個,依簽章自動產生)",
8640
+ noParams: "此方法無需參數。",
8641
+ execute: "執行 ▶",
8642
+ executing: "執行中…",
8643
+ executeStream: "執行並接收串流 ▶",
8644
+ receiving: "接收中…",
8645
+ resetToSample: "重設為範例",
8646
+ clearOutput: "清除輸出",
8647
+ result: "結果",
8648
+ resultStream: "(串流)",
8649
+ notExecuted: "(尚未執行)",
8650
+ copyCurl: "複製 curl (bash)",
8651
+ copyCurlCmd: "複製 curl (cmd)",
8652
+ copyFetch: "複製 fetch",
8653
+ copyTitle: "複製目前呼叫的等效程式碼",
8654
+ copyDisabledTitle: "僅 HTTP 傳輸可產生程式碼",
8655
+ chooseFormat: "選擇複製格式",
8656
+ streamEnded: "(串流已結束,共 {count} 幀,總耗時 {duration})",
8657
+ streamEndedEmpty: "(串流已結束,無資料,總耗時 {duration})",
8658
+ elapsed: "耗時",
8659
+ firstFrame: "首幀",
8660
+ jsonError: "JSON 錯誤:{error}"
8661
+ },
8662
+ schema: {
8663
+ nullable: "nullable",
8664
+ setNull: "設為 null",
8665
+ fillValue: "填入值",
8666
+ fixedValue: "(固定值)",
8667
+ unionNoBranch: "(union 無分支)",
8668
+ arrayNoItem: "(陣列無 item schema)",
8669
+ recordNoValue: "(record 無 value schema)",
8670
+ recordAddPlaceholder: "新鍵名",
8671
+ addRecordKey: "+ 新增鍵",
8672
+ addItem: "+ 新增項目",
8673
+ strictHint: "(非 strict 模式,允許額外欄位)",
8674
+ paramIndex: "參數 [{index}]",
8675
+ items: "items",
8676
+ values: "values",
8677
+ optionIndex: "option[{index}]",
8678
+ inner: "inner"
8679
+ },
8680
+ metrics: {
8681
+ title: "Metrics",
8682
+ unavailable: "伺服器未啟用 system.metrics(withSystemMethods: false 或未註冊)。",
8683
+ autoRefresh: "自動重新整理",
8684
+ interval: "重新整理間隔",
8685
+ refreshNow: "立即重新整理",
8686
+ refreshing: "重新整理中…",
8687
+ uptime: "uptime",
8688
+ loading: "載入中…",
8689
+ methods: "Methods",
8690
+ calls: "Calls",
8691
+ streams: "Streams",
8692
+ errors: "Errors",
8693
+ errorRate: "Error rate",
8694
+ invalidRequests: "Invalid requests",
8695
+ nonStreamTitle: "Non-stream methods",
8696
+ streamTitle: "Stream methods",
8697
+ noData: "尚無資料。",
8698
+ noStreams: "尚無串流呼叫。",
8699
+ countHint: "({count})",
8700
+ colMethod: "method",
8701
+ colCalls: "calls",
8702
+ colRate: "rate/s",
8703
+ colErrors: "errors",
8704
+ colErrPct: "err%",
8705
+ colP50: "p50",
8706
+ colP95: "p95",
8707
+ colP99: "p99",
8708
+ colAvg: "avg",
8709
+ colOpened: "opened",
8710
+ colCompleted: "completed",
8711
+ colFailed: "failed",
8712
+ colAborted: "aborted",
8713
+ colCompletePct: "complete%",
8714
+ colAbortPct: "abort%",
8715
+ colAvgChunks: "avg chunks",
8716
+ colTtfbP50: "ttfb p50",
8717
+ colTtfbP95: "ttfb p95",
8718
+ colDurationAvg: "duration avg"
8719
+ },
8720
+ footer: {
8721
+ version: "版本",
8722
+ npm: "npm 套件",
8723
+ github: "GitHub 儲存庫"
8724
+ },
8725
+ alerts: {
8726
+ emptyMethods: "方法清單為空,請先重新整理方法清單。",
8727
+ docsFailed: "產生文件失敗:{error}"
8728
+ }
8255
8729
  },
8256
- {
8257
- value: "dark",
8258
- label: "暗"
8730
+ ja: {
8731
+ common: {
8732
+ loading: "読み込み中…",
8733
+ refresh: "更新",
8734
+ refreshing: "更新中…",
8735
+ reset: "リセット",
8736
+ clear: "クリア",
8737
+ delete: "削除",
8738
+ add: "追加",
8739
+ copy: "コピー",
8740
+ copied: "コピーしました ✓",
8741
+ copyFailed: "コピーに失敗しました:{error}",
8742
+ generate: "生成",
8743
+ generating: "生成中…",
8744
+ none: "なし",
8745
+ empty: "空"
8746
+ },
8747
+ toolbar: {
8748
+ title: "JSON-RPC 2.0",
8749
+ transport: "トランスポート",
8750
+ http: "HTTP",
8751
+ ws: "WebSocket",
8752
+ refreshMethods: "メソッド一覧を更新",
8753
+ refreshMethodsLoading: "更新中…",
8754
+ generateDocs: "ドキュメント生成 ⬇",
8755
+ generateDocsLoading: "生成中…",
8756
+ generateDocsTitle: "全メソッドのヘルプとシグネチャを取得し、Markdown を生成してダウンロード",
8757
+ theme: "テーマ",
8758
+ themeSystem: "システムに従う",
8759
+ themeLight: "ライト",
8760
+ themeDark: "ダーク",
8761
+ language: "言語",
8762
+ languageSystem: "システム言語に従う",
8763
+ languageEn: "English",
8764
+ languageZhCN: "简体中文",
8765
+ languageZhTW: "繁體中文",
8766
+ languageJa: "日本語"
8767
+ },
8768
+ sidebar: {
8769
+ methods: "Methods",
8770
+ metrics: "Metrics",
8771
+ search: "検索",
8772
+ searchPlaceholder: "キーワードを入力(スペース区切り)",
8773
+ matchCount: "{matched} / {total} 件のメソッドが一致",
8774
+ noMatch: "一致するメソッドはありません。",
8775
+ noMethods: "利用可能なメソッドがありません。"
8776
+ },
8777
+ view: {
8778
+ label: "ビュー",
8779
+ methods: "Methods",
8780
+ metrics: "Metrics"
8781
+ },
8782
+ method: {
8783
+ selectHint: "← 左側からメソッドを選択すると、ヘルプとシグネチャが表示されます。",
8784
+ loading: "読み込み中…",
8785
+ loadFailed: "読み込み失敗:{error}",
8786
+ help: "Help",
8787
+ noHelp: "(ヘルプ情報なし)",
8788
+ returnType: "戻り値の型",
8789
+ returnTypeHint: "(サーバーは実行結果がこの型に一致するか検証しません。実際の戻り値は異なる場合があります)",
8790
+ none: "(なし)"
8791
+ },
8792
+ test: {
8793
+ params: "パラメータ",
8794
+ paramsHint: "({count} 個、シグネチャから自動生成)",
8795
+ noParams: "このメソッドはパラメータを取りません。",
8796
+ execute: "実行 ▶",
8797
+ executing: "実行中…",
8798
+ executeStream: "実行してストリーム受信 ▶",
8799
+ receiving: "受信中…",
8800
+ resetToSample: "サンプルに戻す",
8801
+ clearOutput: "出力をクリア",
8802
+ result: "結果",
8803
+ resultStream: "(ストリーミング)",
8804
+ notExecuted: "(未実行)",
8805
+ copyCurl: "curl をコピー (bash)",
8806
+ copyCurlCmd: "curl をコピー (cmd)",
8807
+ copyFetch: "fetch をコピー",
8808
+ copyTitle: "現在の呼び出しに相当するコードをコピー",
8809
+ copyDisabledTitle: "コード生成は HTTP トランスポートのみ対応",
8810
+ chooseFormat: "コピー形式を選択",
8811
+ streamEnded: "(ストリーム終了、{count} フレーム、合計 {duration})",
8812
+ streamEndedEmpty: "(ストリーム終了、データなし、合計 {duration})",
8813
+ elapsed: "経過",
8814
+ firstFrame: "初回フレーム",
8815
+ jsonError: "JSON エラー:{error}"
8816
+ },
8817
+ schema: {
8818
+ nullable: "nullable",
8819
+ setNull: "null にする",
8820
+ fillValue: "値を入力",
8821
+ fixedValue: "(固定値)",
8822
+ unionNoBranch: "(union に分岐がありません)",
8823
+ arrayNoItem: "(配列に item schema がありません)",
8824
+ recordNoValue: "(record に value schema がありません)",
8825
+ recordAddPlaceholder: "新しいキー",
8826
+ addRecordKey: "+ キーを追加",
8827
+ addItem: "+ 項目を追加",
8828
+ strictHint: "(非 strict モード、追加フィールドを許可)",
8829
+ paramIndex: "パラメータ [{index}]",
8830
+ items: "items",
8831
+ values: "values",
8832
+ optionIndex: "option[{index}]",
8833
+ inner: "inner"
8834
+ },
8835
+ metrics: {
8836
+ title: "Metrics",
8837
+ unavailable: "サーバーで system.metrics が有効になっていません(withSystemMethods: false または未登録)。",
8838
+ autoRefresh: "自動更新",
8839
+ interval: "更新間隔",
8840
+ refreshNow: "今すぐ更新",
8841
+ refreshing: "更新中…",
8842
+ uptime: "uptime",
8843
+ loading: "読み込み中…",
8844
+ methods: "Methods",
8845
+ calls: "Calls",
8846
+ streams: "Streams",
8847
+ errors: "Errors",
8848
+ errorRate: "Error rate",
8849
+ invalidRequests: "Invalid requests",
8850
+ nonStreamTitle: "Non-stream methods",
8851
+ streamTitle: "Stream methods",
8852
+ noData: "データがありません。",
8853
+ noStreams: "ストリーミング呼び出しはありません。",
8854
+ countHint: "({count})",
8855
+ colMethod: "method",
8856
+ colCalls: "calls",
8857
+ colRate: "rate/s",
8858
+ colErrors: "errors",
8859
+ colErrPct: "err%",
8860
+ colP50: "p50",
8861
+ colP95: "p95",
8862
+ colP99: "p99",
8863
+ colAvg: "avg",
8864
+ colOpened: "opened",
8865
+ colCompleted: "completed",
8866
+ colFailed: "failed",
8867
+ colAborted: "aborted",
8868
+ colCompletePct: "complete%",
8869
+ colAbortPct: "abort%",
8870
+ colAvgChunks: "avg chunks",
8871
+ colTtfbP50: "ttfb p50",
8872
+ colTtfbP95: "ttfb p95",
8873
+ colDurationAvg: "duration avg"
8874
+ },
8875
+ footer: {
8876
+ version: "バージョン",
8877
+ npm: "npm パッケージ",
8878
+ github: "GitHub リポジトリ"
8879
+ },
8880
+ alerts: {
8881
+ emptyMethods: "メソッド一覧が空です。先にメソッド一覧を更新してください。",
8882
+ docsFailed: "ドキュメント生成に失敗しました:{error}"
8883
+ }
8259
8884
  }
8885
+ };
8886
+ //#endregion
8887
+ //#region webui/src/i18n/detect.ts
8888
+ /** 把 BCP-47 语言标签映射到我们支持的 4 种 locale。 */
8889
+ function normalizeLocale(tag) {
8890
+ if (!tag) return void 0;
8891
+ const t = tag.toLowerCase();
8892
+ if (t === "ja" || t.startsWith("ja-")) return "ja";
8893
+ if (t === "zh" || t.startsWith("zh-")) {
8894
+ if (t.includes("hant") || t.startsWith("zh-tw") || t.startsWith("zh-hk") || t.startsWith("zh-mo")) return "zh-TW";
8895
+ return "zh-CN";
8896
+ }
8897
+ if (t === "en" || t.startsWith("en-")) return "en";
8898
+ }
8899
+ /** 读取浏览器语言偏好,取第一个能映射的;否则回退 en。 */
8900
+ function detectSystemLocale() {
8901
+ const nav = typeof navigator !== "undefined" ? navigator : void 0;
8902
+ const candidates = [];
8903
+ if (nav?.languages && nav.languages.length > 0) candidates.push(...nav.languages);
8904
+ if (nav?.language) candidates.push(nav.language);
8905
+ for (const c of candidates) {
8906
+ const loc = normalizeLocale(c);
8907
+ if (loc) return loc;
8908
+ }
8909
+ return "en";
8910
+ }
8911
+ //#endregion
8912
+ //#region webui/src/i18n/I18nProvider.tsx
8913
+ var STORAGE_KEY = "jsonrpc2.locale";
8914
+ var I18nContext = (0, import_react.createContext)(null);
8915
+ var VALID_MODES = [
8916
+ "system",
8917
+ "en",
8918
+ "zh-CN",
8919
+ "zh-TW",
8920
+ "ja"
8260
8921
  ];
8922
+ function readStoredMode() {
8923
+ try {
8924
+ const v = localStorage.getItem(STORAGE_KEY);
8925
+ if (v && VALID_MODES.includes(v)) return v;
8926
+ } catch {}
8927
+ return "system";
8928
+ }
8929
+ function getByPath(obj, path) {
8930
+ const parts = path.split(".");
8931
+ let cur = obj;
8932
+ for (const p of parts) {
8933
+ if (cur == null || typeof cur !== "object") return void 0;
8934
+ cur = cur[p];
8935
+ }
8936
+ return cur;
8937
+ }
8938
+ function interpolate(text, vars) {
8939
+ if (!vars) return text;
8940
+ return text.replace(/\{(\w+)\}/g, (_, name) => {
8941
+ const v = vars[name];
8942
+ return v === void 0 ? `{${name}}` : String(v);
8943
+ });
8944
+ }
8945
+ function I18nProvider({ children }) {
8946
+ const [mode, setModeState] = (0, import_react.useState)(readStoredMode);
8947
+ const [systemLocale, setSystemLocale] = (0, import_react.useState)(detectSystemLocale);
8948
+ (0, import_react.useEffect)(() => {
8949
+ const onChange = () => setSystemLocale(detectSystemLocale());
8950
+ window.addEventListener("languagechange", onChange);
8951
+ return () => window.removeEventListener("languagechange", onChange);
8952
+ }, []);
8953
+ const locale = (0, import_react.useMemo)(() => {
8954
+ if (mode === "system") return systemLocale;
8955
+ return normalizeLocale(mode) ?? systemLocale;
8956
+ }, [mode, systemLocale]);
8957
+ (0, import_react.useEffect)(() => {
8958
+ document.documentElement.setAttribute("lang", locale);
8959
+ try {
8960
+ localStorage.setItem(STORAGE_KEY, mode);
8961
+ } catch {}
8962
+ }, [locale, mode]);
8963
+ const messages = MESSAGES[locale];
8964
+ const t = (0, import_react.useCallback)((key, vars) => {
8965
+ const raw = getByPath(messages, key);
8966
+ if (typeof raw === "string") return interpolate(raw, vars);
8967
+ const fallback = getByPath(MESSAGES.en, key);
8968
+ if (typeof fallback === "string") return interpolate(fallback, vars);
8969
+ return key;
8970
+ }, [messages]);
8971
+ const setMode = (0, import_react.useCallback)((m) => setModeState(m), []);
8972
+ const value = (0, import_react.useMemo)(() => ({
8973
+ mode,
8974
+ locale,
8975
+ messages,
8976
+ setMode,
8977
+ t
8978
+ }), [
8979
+ mode,
8980
+ locale,
8981
+ messages,
8982
+ setMode,
8983
+ t
8984
+ ]);
8985
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(I18nContext.Provider, {
8986
+ value,
8987
+ children
8988
+ });
8989
+ }
8990
+ function useI18n() {
8991
+ const ctx = (0, import_react.useContext)(I18nContext);
8992
+ if (!ctx) throw new Error("useI18n must be used within <I18nProvider>");
8993
+ return ctx;
8994
+ }
8995
+ /** 便捷 hook:只取 t */
8996
+ function useT() {
8997
+ return useI18n().t;
8998
+ }
8999
+ //#endregion
9000
+ //#region webui/src/components/Toolbar.tsx
8261
9001
  function Toolbar({ transport, onTransportChange, onRefresh, loading, error, onGenerateDocs, generatingDocs }) {
8262
9002
  const { mode, setMode } = useTheme();
9003
+ const { t, mode: localeMode, setMode: setLocaleMode } = useI18n();
9004
+ const themeOptions = [
9005
+ {
9006
+ value: "system",
9007
+ label: t("toolbar.themeSystem")
9008
+ },
9009
+ {
9010
+ value: "light",
9011
+ label: t("toolbar.themeLight")
9012
+ },
9013
+ {
9014
+ value: "dark",
9015
+ label: t("toolbar.themeDark")
9016
+ }
9017
+ ];
9018
+ const localeOptions = [
9019
+ {
9020
+ value: "system",
9021
+ label: t("toolbar.languageSystem")
9022
+ },
9023
+ {
9024
+ value: "en",
9025
+ label: t("toolbar.languageEn")
9026
+ },
9027
+ {
9028
+ value: "zh-CN",
9029
+ label: t("toolbar.languageZhCN")
9030
+ },
9031
+ {
9032
+ value: "zh-TW",
9033
+ label: t("toolbar.languageZhTW")
9034
+ },
9035
+ {
9036
+ value: "ja",
9037
+ label: t("toolbar.languageJa")
9038
+ }
9039
+ ];
8263
9040
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
8264
9041
  className: "toolbar",
8265
9042
  children: [
8266
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: "JSON-RPC 2.0" }),
9043
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: t("toolbar.title") }),
8267
9044
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
8268
9045
  className: "segmented",
8269
9046
  role: "group",
8270
- "aria-label": "传输方式",
9047
+ "aria-label": t("toolbar.transport"),
8271
9048
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
8272
9049
  type: "button",
8273
9050
  className: transport === "http" ? "active" : "",
8274
9051
  onClick: () => onTransportChange("http"),
8275
- children: "HTTP"
9052
+ children: t("toolbar.http")
8276
9053
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
8277
9054
  type: "button",
8278
9055
  className: transport === "ws" ? "active" : "",
8279
9056
  onClick: () => onTransportChange("ws"),
8280
- children: "WebSocket"
9057
+ children: t("toolbar.ws")
8281
9058
  })]
8282
9059
  }),
8283
9060
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
8284
9061
  onClick: onRefresh,
8285
9062
  disabled: loading,
8286
- children: loading ? "刷新中…" : "刷新方法列表"
9063
+ children: loading ? t("toolbar.refreshMethodsLoading") : t("toolbar.refreshMethods")
8287
9064
  }),
8288
9065
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
8289
9066
  onClick: onGenerateDocs,
8290
- disabled: generatingDocs || loading || loading,
8291
- title: "拉取所有方法的帮助与签名,生成 Markdown 文档并下载",
8292
- children: generatingDocs ? "生成中…" : "生成文档 ⬇"
9067
+ disabled: generatingDocs || loading,
9068
+ title: t("toolbar.generateDocsTitle"),
9069
+ children: generatingDocs ? t("toolbar.generateDocsLoading") : t("toolbar.generateDocs")
8293
9070
  }),
8294
9071
  error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
8295
9072
  className: "error",
@@ -8297,14 +9074,29 @@ function Toolbar({ transport, onTransportChange, onRefresh, loading, error, onGe
8297
9074
  }),
8298
9075
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
8299
9076
  className: "theme-select",
8300
- title: "主题",
9077
+ title: t("toolbar.language"),
8301
9078
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
8302
9079
  className: "theme-select-label",
8303
- children: "主题"
9080
+ children: t("toolbar.language")
9081
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", {
9082
+ value: localeMode,
9083
+ onChange: (e) => setLocaleMode(e.target.value),
9084
+ children: localeOptions.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
9085
+ value: opt.value,
9086
+ children: opt.label
9087
+ }, opt.value))
9088
+ })]
9089
+ }),
9090
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
9091
+ className: "theme-select",
9092
+ title: t("toolbar.theme"),
9093
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
9094
+ className: "theme-select-label",
9095
+ children: t("toolbar.theme")
8304
9096
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", {
8305
9097
  value: mode,
8306
9098
  onChange: (e) => setMode(e.target.value),
8307
- children: THEME_OPTIONS.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
9099
+ children: themeOptions.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
8308
9100
  value: opt.value,
8309
9101
  children: opt.label
8310
9102
  }, opt.value))
@@ -8351,10 +9143,11 @@ function buildTree(methods) {
8351
9143
  return root.children;
8352
9144
  }
8353
9145
  function MethodTree({ methods, selected, onSelect }) {
9146
+ const t = useT();
8354
9147
  const tree = (0, import_react.useMemo)(() => buildTree(methods), [methods]);
8355
9148
  if (methods.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
8356
9149
  className: "empty",
8357
- children: "暂无可用方法。"
9150
+ children: t("sidebar.noMethods")
8358
9151
  });
8359
9152
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
8360
9153
  className: "tree",
@@ -8424,8 +9217,9 @@ function formatConstraints$1(constraints) {
8424
9217
  return entries.map(([k, v]) => `${k}=${typeof v === "string" ? JSON.stringify(v) : JSON.stringify(v)}`).join(", ");
8425
9218
  }
8426
9219
  function SchemaNode({ schema, depth, defaultOpen, label }) {
9220
+ const t = useT();
8427
9221
  const [open, setOpen] = (0, import_react.useState)(defaultOpen);
8428
- const hasChildren = schema.children && Object.keys(schema.children).length > 0;
9222
+ const hasChildren = hasChildNodes(schema);
8429
9223
  const constraintsText = formatConstraints$1(schema.constraints);
8430
9224
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
8431
9225
  className: "schema-node",
@@ -8471,59 +9265,107 @@ function SchemaNode({ schema, depth, defaultOpen, label }) {
8471
9265
  ]
8472
9266
  }), open && hasChildren && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
8473
9267
  className: "schema-children",
8474
- children: renderChildren(schema, depth + 1)
9268
+ children: renderChildren(schema, depth + 1, t)
8475
9269
  })]
8476
9270
  });
8477
9271
  }
8478
- function renderChildren(schema, depth) {
8479
- const c = schema.children ?? {};
9272
+ function isSchemaDescription(v) {
9273
+ return !!v && typeof v === "object" && typeof v.kind === "string";
9274
+ }
9275
+ /** 是否存在可展开的子节点(新结构 + stream 的兼容路径) */
9276
+ function hasChildNodes(schema) {
9277
+ switch (schema.kind) {
9278
+ case "array": return isSchemaDescription(schema.element);
9279
+ case "record": return isSchemaDescription(schema.value);
9280
+ case "tuple": {
9281
+ const els = schema.elements;
9282
+ return Array.isArray(els) && els.some(isSchemaDescription);
9283
+ }
9284
+ case "union": {
9285
+ const opts = schema.options;
9286
+ return Array.isArray(opts) && opts.some(isSchemaDescription);
9287
+ }
9288
+ case "object": {
9289
+ const props = schema.properties;
9290
+ return !!props && typeof props === "object" && !Array.isArray(props) && Object.keys(props).length > 0;
9291
+ }
9292
+ case "stream": {
9293
+ const any = schema;
9294
+ return isSchemaDescription(any.inner) || isSchemaDescription(any.children?.inner);
9295
+ }
9296
+ default: return false;
9297
+ }
9298
+ }
9299
+ function renderChildren(schema, depth, t) {
8480
9300
  const out = [];
8481
- if (c.inner && isSchemaDescription(c.inner)) out.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaNode, {
8482
- schema: c.inner,
8483
- depth,
8484
- defaultOpen: true
8485
- }, "inner"));
8486
- if (Array.isArray(c.items)) c.items.forEach((item, i) => {
8487
- if (isSchemaDescription(item)) out.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaNode, {
8488
- schema: item,
8489
- depth,
8490
- defaultOpen: true,
8491
- label: String(i)
8492
- }, `item-${i}`));
8493
- });
8494
- else if (c.items && isSchemaDescription(c.items)) out.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaNode, {
8495
- schema: c.items,
8496
- depth,
8497
- defaultOpen: true,
8498
- label: "items"
8499
- }, "items"));
8500
- if (Array.isArray(c.options)) c.options.forEach((opt, i) => {
8501
- if (isSchemaDescription(opt)) out.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaNode, {
8502
- schema: opt,
8503
- depth,
8504
- defaultOpen: false,
8505
- label: `option[${i}]`
8506
- }, `opt-${i}`));
8507
- });
8508
- if (c.properties && typeof c.properties === "object" && !Array.isArray(c.properties)) {
8509
- for (const [key, prop] of Object.entries(c.properties)) if (isSchemaDescription(prop)) out.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaNode, {
8510
- schema: prop,
8511
- depth,
8512
- defaultOpen: true,
8513
- label: key
8514
- }, `prop-${key}`));
9301
+ switch (schema.kind) {
9302
+ case "array": {
9303
+ const element = schema.element;
9304
+ if (isSchemaDescription(element)) out.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaNode, {
9305
+ schema: element,
9306
+ depth,
9307
+ defaultOpen: true
9308
+ }, "element"));
9309
+ break;
9310
+ }
9311
+ case "record": {
9312
+ const value = schema.value;
9313
+ if (isSchemaDescription(value)) out.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaNode, {
9314
+ schema: value,
9315
+ depth,
9316
+ defaultOpen: true
9317
+ }, "value"));
9318
+ break;
9319
+ }
9320
+ case "tuple": {
9321
+ const elements = schema.elements;
9322
+ if (Array.isArray(elements)) elements.forEach((item, i) => {
9323
+ if (isSchemaDescription(item)) out.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaNode, {
9324
+ schema: item,
9325
+ depth,
9326
+ defaultOpen: true,
9327
+ label: String(i)
9328
+ }, `el-${i}`));
9329
+ });
9330
+ break;
9331
+ }
9332
+ case "union": {
9333
+ const options = schema.options;
9334
+ if (Array.isArray(options)) options.forEach((opt, i) => {
9335
+ if (isSchemaDescription(opt)) out.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaNode, {
9336
+ schema: opt,
9337
+ depth,
9338
+ defaultOpen: false,
9339
+ label: t("schema.optionIndex", { index: i })
9340
+ }, `opt-${i}`));
9341
+ });
9342
+ break;
9343
+ }
9344
+ case "object": {
9345
+ const props = schema.properties;
9346
+ if (props && typeof props === "object" && !Array.isArray(props)) {
9347
+ for (const [key, prop] of Object.entries(props)) if (isSchemaDescription(prop)) out.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaNode, {
9348
+ schema: prop,
9349
+ depth,
9350
+ defaultOpen: true,
9351
+ label: key
9352
+ }, `prop-${key}`));
9353
+ }
9354
+ break;
9355
+ }
9356
+ case "stream": {
9357
+ const any = schema;
9358
+ const inner = isSchemaDescription(any.inner) ? any.inner : isSchemaDescription(any.children?.inner) ? any.children.inner : void 0;
9359
+ if (inner) out.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaNode, {
9360
+ schema: inner,
9361
+ depth,
9362
+ defaultOpen: true
9363
+ }, "inner"));
9364
+ break;
9365
+ }
8515
9366
  }
8516
- if (c.values && isSchemaDescription(c.values)) out.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaNode, {
8517
- schema: c.values,
8518
- depth,
8519
- defaultOpen: true,
8520
- label: "values"
8521
- }, "values"));
8522
9367
  return out;
8523
9368
  }
8524
- function isSchemaDescription(v) {
8525
- return !!v && typeof v === "object" && typeof v.kind === "string";
8526
- }
8527
9369
  function SchemaView({ schema, defaultOpen = true, depth = 0 }) {
8528
9370
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaNode, {
8529
9371
  schema,
@@ -8534,11 +9376,12 @@ function SchemaView({ schema, defaultOpen = true, depth = 0 }) {
8534
9376
  //#endregion
8535
9377
  //#region webui/src/components/MethodDetail.tsx
8536
9378
  function MethodDetail({ method, detail }) {
9379
+ const t = useT();
8537
9380
  if (!method) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", {
8538
9381
  className: "detail",
8539
9382
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
8540
9383
  className: "hint",
8541
- children: "← 从左侧选择一个方法查看帮助与签名。"
9384
+ children: t("method.selectHint")
8542
9385
  })
8543
9386
  });
8544
9387
  const [returnSchema] = detail.signature;
@@ -8548,23 +9391,23 @@ function MethodDetail({ method, detail }) {
8548
9391
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: method }),
8549
9392
  detail.loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
8550
9393
  className: "hint",
8551
- children: "加载中…"
9394
+ children: t("method.loading")
8552
9395
  }),
8553
- detail.error && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
9396
+ detail.error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
8554
9397
  className: "error",
8555
- children: ["加载失败:", detail.error]
9398
+ children: t("method.loadFailed", { error: detail.error })
8556
9399
  }),
8557
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "Help" }),
9400
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: t("method.help") }),
8558
9401
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
8559
9402
  className: "help",
8560
- children: detail.help || "(无帮助信息)"
9403
+ children: detail.help || t("method.noHelp")
8561
9404
  }),
8562
9405
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: [
8563
- "返回类型",
9406
+ t("method.returnType"),
8564
9407
  " ",
8565
9408
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
8566
9409
  className: "hint",
8567
- children: "(服务端不会校验执行结果是否符合此类型,实际返回值可能会有所不同)"
9410
+ children: t("method.returnTypeHint")
8568
9411
  })
8569
9412
  ] }),
8570
9413
  returnSchema ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaView, {
@@ -8573,7 +9416,7 @@ function MethodDetail({ method, detail }) {
8573
9416
  defaultOpen: true
8574
9417
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
8575
9418
  className: "hint",
8576
- children: "(暂无)"
9419
+ children: t("method.none")
8577
9420
  })
8578
9421
  ]
8579
9422
  });
@@ -8591,12 +9434,11 @@ function isSchema$1(v) {
8591
9434
  * - 原始类型返回零值(''、0、false)
8592
9435
  * - literal 返回固定值
8593
9436
  * - enum 返回第一个枚举值
8594
- * - 容器类型递归展开
9437
+ * - 容器类型递归展开(按新字段:element / value / properties / options / elements)
8595
9438
  * - unknown 返回 null
8596
9439
  */
8597
9440
  function sampleFromSchema(schema) {
8598
9441
  if (schema.nullable) return null;
8599
- const c = schema.children ?? {};
8600
9442
  switch (schema.kind) {
8601
9443
  case "string": return "";
8602
9444
  case "number": return 0;
@@ -8607,19 +9449,19 @@ function sampleFromSchema(schema) {
8607
9449
  const values = schema.constraints?.values;
8608
9450
  return Array.isArray(values) && values.length > 0 ? values[0] : "";
8609
9451
  }
8610
- case "array": return [isSchema$1(c.items) ? sampleFromSchema(c.items) : null];
8611
- case "tuple": return (Array.isArray(c.items) ? c.items : []).map((it) => isSchema$1(it) ? sampleFromSchema(it) : null);
9452
+ case "array": return [isSchema$1(schema.element) ? sampleFromSchema(schema.element) : null];
9453
+ case "tuple": return (Array.isArray(schema.elements) ? schema.elements : []).map((it) => isSchema$1(it) ? sampleFromSchema(it) : null);
8612
9454
  case "union": {
8613
- const options = Array.isArray(c.options) ? c.options : [];
9455
+ const options = Array.isArray(schema.options) ? schema.options : [];
8614
9456
  return options.length > 0 && isSchema$1(options[0]) ? sampleFromSchema(options[0]) : null;
8615
9457
  }
8616
9458
  case "object": {
8617
- const props = c.properties ?? {};
9459
+ const props = schema.properties ?? {};
8618
9460
  const out = {};
8619
9461
  for (const [k, v] of Object.entries(props)) if (isSchema$1(v)) out[k] = sampleFromSchema(v);
8620
9462
  return out;
8621
9463
  }
8622
- case "record": return { key: isSchema$1(c.values) ? sampleFromSchema(c.values) : null };
9464
+ case "record": return { key: isSchema$1(schema.value) ? sampleFromSchema(schema.value) : null };
8623
9465
  default: return null;
8624
9466
  }
8625
9467
  }
@@ -8647,9 +9489,10 @@ function formatConstraints(c) {
8647
9489
  return Object.entries(c).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", ");
8648
9490
  }
8649
9491
  function SchemaForm({ params, values, onChange, disabled }) {
9492
+ const t = useT();
8650
9493
  if (params.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
8651
9494
  className: "hint",
8652
- children: "此方法无需参数。"
9495
+ children: t("test.noParams")
8653
9496
  });
8654
9497
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
8655
9498
  className: "schema-form",
@@ -8657,13 +9500,14 @@ function SchemaForm({ params, values, onChange, disabled }) {
8657
9500
  schema,
8658
9501
  value: values[i],
8659
9502
  onChange: (v) => onChange(i, v),
8660
- label: `参数 [${i}]`,
9503
+ label: t("schema.paramIndex", { index: i }),
8661
9504
  depth: 0,
8662
9505
  disabled
8663
9506
  }, i))
8664
9507
  });
8665
9508
  }
8666
9509
  function SchemaField({ schema, value, onChange, label, depth = 0, defaultOpen = true, disabled }) {
9510
+ const t = useT();
8667
9511
  const [open, setOpen] = (0, import_react.useState)(defaultOpen);
8668
9512
  const kind = schema.kind;
8669
9513
  if (schema.nullable) {
@@ -8685,7 +9529,7 @@ function SchemaField({ schema, value, onChange, label, depth = 0, defaultOpen =
8685
9529
  }),
8686
9530
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
8687
9531
  className: "sf-nullable-badge",
8688
- children: "nullable"
9532
+ children: t("schema.nullable")
8689
9533
  }),
8690
9534
  schema.description && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
8691
9535
  className: "sf-desc",
@@ -8696,7 +9540,7 @@ function SchemaField({ schema, value, onChange, label, depth = 0, defaultOpen =
8696
9540
  className: "tiny",
8697
9541
  disabled,
8698
9542
  onClick: () => onChange(null),
8699
- children: "置 null"
9543
+ children: t("schema.setNull")
8700
9544
  }),
8701
9545
  isNull && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
8702
9546
  type: "button",
@@ -8706,7 +9550,7 @@ function SchemaField({ schema, value, onChange, label, depth = 0, defaultOpen =
8706
9550
  ...schema,
8707
9551
  nullable: false
8708
9552
  })),
8709
- children: "填值"
9553
+ children: t("schema.fillValue")
8710
9554
  })
8711
9555
  ]
8712
9556
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -8760,7 +9604,7 @@ function SchemaField({ schema, value, onChange, label, depth = 0, defaultOpen =
8760
9604
  })]
8761
9605
  });
8762
9606
  if (kind === "union") {
8763
- const options = Array.isArray(schema.children?.options) ? schema.children.options.filter(isSchema$1) : [];
9607
+ const options = Array.isArray(schema.options) ? schema.options.filter(isSchema$1) : [];
8764
9608
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
8765
9609
  className: "sf-node sf-row",
8766
9610
  style: { paddingLeft: depth * 14 },
@@ -8843,6 +9687,7 @@ function isScalarKind(kind) {
8843
9687
  return kind === "string" || kind === "number" || kind === "boolean" || kind === "enum" || kind === "literal" || kind === "null";
8844
9688
  }
8845
9689
  function ScalarInput({ schema, value, onChange, disabled }) {
9690
+ const t = useT();
8846
9691
  switch (schema.kind) {
8847
9692
  case "boolean": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
8848
9693
  className: "sf-scalar",
@@ -8880,7 +9725,7 @@ function ScalarInput({ schema, value, onChange, disabled }) {
8880
9725
  children: JSON.stringify(schema.constraints?.value ?? null)
8881
9726
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
8882
9727
  className: "hint",
8883
- children: "(固定值)"
9728
+ children: t("schema.fixedValue")
8884
9729
  })]
8885
9730
  });
8886
9731
  case "null": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -8890,7 +9735,7 @@ function ScalarInput({ schema, value, onChange, disabled }) {
8890
9735
  children: "null"
8891
9736
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
8892
9737
  className: "hint",
8893
- children: "(固定值)"
9738
+ children: t("schema.fixedValue")
8894
9739
  })]
8895
9740
  });
8896
9741
  default: return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
@@ -8903,11 +9748,12 @@ function ScalarInput({ schema, value, onChange, disabled }) {
8903
9748
  }
8904
9749
  }
8905
9750
  function UnionField({ options, value, onChange, depth, disabled }) {
9751
+ const t = useT();
8906
9752
  const [selected, setSelected] = (0, import_react.useState)(0);
8907
9753
  const active = options[selected];
8908
9754
  if (!active) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
8909
9755
  className: "hint",
8910
- children: "(union 无分支)"
9756
+ children: t("schema.unionNoBranch")
8911
9757
  });
8912
9758
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
8913
9759
  style: { paddingLeft: depth * 14 },
@@ -8926,9 +9772,8 @@ function UnionField({ options, value, onChange, depth, disabled }) {
8926
9772
  children: options.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", {
8927
9773
  value: i,
8928
9774
  children: [
8929
- "option[",
8930
- i,
8931
- "] — ",
9775
+ t("schema.optionIndex", { index: i }),
9776
+ " — ",
8932
9777
  opt.kind
8933
9778
  ]
8934
9779
  }, i))
@@ -8944,12 +9789,12 @@ function UnionField({ options, value, onChange, depth, disabled }) {
8944
9789
  });
8945
9790
  }
8946
9791
  function ContainerChildren({ schema, value, onChange, depth, disabled }) {
8947
- const c = schema.children ?? {};
9792
+ const t = useT();
8948
9793
  if (schema.kind === "array") {
8949
- const itemSchema = isSchema$1(c.items) ? c.items : void 0;
9794
+ const itemSchema = isSchema$1(schema.element) ? schema.element : void 0;
8950
9795
  if (!itemSchema) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
8951
9796
  className: "hint",
8952
- children: "(数组无 item schema)"
9797
+ children: t("schema.arrayNoItem")
8953
9798
  });
8954
9799
  const arr = Array.isArray(value) ? value : [];
8955
9800
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [arr.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -8971,18 +9816,18 @@ function ContainerChildren({ schema, value, onChange, depth, disabled }) {
8971
9816
  className: "tiny",
8972
9817
  disabled,
8973
9818
  onClick: () => onChange(arr.filter((_, j) => j !== i)),
8974
- children: "删除"
9819
+ children: t("common.delete")
8975
9820
  })]
8976
9821
  }, i)), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
8977
9822
  type: "button",
8978
9823
  className: "tiny",
8979
9824
  disabled,
8980
9825
  onClick: () => onChange([...arr, sampleFromSchema(itemSchema)]),
8981
- children: "+ 添加项"
9826
+ children: t("schema.addItem")
8982
9827
  })] });
8983
9828
  }
8984
9829
  if (schema.kind === "tuple") {
8985
- const items = Array.isArray(c.items) ? c.items.filter(isSchema$1) : [];
9830
+ const items = Array.isArray(schema.elements) ? schema.elements.filter(isSchema$1) : [];
8986
9831
  const arr = Array.isArray(value) ? value : [];
8987
9832
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: items.map((itemSchema, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaField, {
8988
9833
  schema: itemSchema,
@@ -8999,9 +9844,9 @@ function ContainerChildren({ schema, value, onChange, depth, disabled }) {
8999
9844
  }, i)) });
9000
9845
  }
9001
9846
  if (schema.kind === "object") {
9002
- const props = c.properties ?? {};
9847
+ const props = schema.properties ?? {};
9003
9848
  const obj = value && typeof value === "object" && !Array.isArray(value) ? value : {};
9004
- const strict = Boolean(schema.constraints?.strict);
9849
+ const allowUnknown = schema.constraints?.allowUnknownProps !== false;
9005
9850
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [Object.entries(props).map(([key, prop]) => {
9006
9851
  if (!isSchema$1(prop)) return null;
9007
9852
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaField, {
@@ -9018,20 +9863,20 @@ function ContainerChildren({ schema, value, onChange, depth, disabled }) {
9018
9863
  disabled,
9019
9864
  defaultOpen: false
9020
9865
  }, key);
9021
- }), !strict && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
9866
+ }), allowUnknown && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
9022
9867
  className: "hint",
9023
9868
  style: {
9024
9869
  paddingLeft: depth * 14,
9025
9870
  margin: "4px 0"
9026
9871
  },
9027
- children: "(非 strict 模式,允许额外字段)"
9872
+ children: t("schema.strictHint")
9028
9873
  })] });
9029
9874
  }
9030
9875
  if (schema.kind === "record") {
9031
- const valueSchema = isSchema$1(c.values) ? c.values : void 0;
9876
+ const valueSchema = isSchema$1(schema.value) ? schema.value : void 0;
9032
9877
  if (!valueSchema) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
9033
9878
  className: "hint",
9034
- children: "(record 无 value schema)"
9879
+ children: t("schema.recordNoValue")
9035
9880
  });
9036
9881
  const obj = value && typeof value === "object" && !Array.isArray(value) ? value : {};
9037
9882
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [Object.entries(obj).map(([key, v]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -9056,7 +9901,7 @@ function ContainerChildren({ schema, value, onChange, depth, disabled }) {
9056
9901
  delete next[key];
9057
9902
  onChange(next);
9058
9903
  },
9059
- children: "删除"
9904
+ children: t("common.delete")
9060
9905
  })]
9061
9906
  }, key)), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AddRecordKey, {
9062
9907
  onAdd: (key, v) => onChange({
@@ -9070,12 +9915,13 @@ function ContainerChildren({ schema, value, onChange, depth, disabled }) {
9070
9915
  return null;
9071
9916
  }
9072
9917
  function AddRecordKey({ onAdd, sample, disabled }) {
9918
+ const t = useT();
9073
9919
  const [key, setKey] = (0, import_react.useState)("");
9074
9920
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
9075
9921
  className: "sf-record-add",
9076
9922
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
9077
9923
  className: "sf-input",
9078
- placeholder: "新键名",
9924
+ placeholder: t("schema.recordAddPlaceholder"),
9079
9925
  value: key,
9080
9926
  onChange: (e) => setKey(e.target.value),
9081
9927
  disabled
@@ -9087,11 +9933,12 @@ function AddRecordKey({ onAdd, sample, disabled }) {
9087
9933
  onAdd(key, sample());
9088
9934
  setKey("");
9089
9935
  },
9090
- children: "+ 添加键"
9936
+ children: t("schema.addRecordKey")
9091
9937
  })]
9092
9938
  });
9093
9939
  }
9094
9940
  function UnknownLeaf({ schema, value, onChange, label, depth, disabled }) {
9941
+ const t = useT();
9095
9942
  const [text, setText] = (0, import_react.useState)(() => JSON.stringify(value ?? null, null, 2));
9096
9943
  const [error, setError] = (0, import_react.useState)(null);
9097
9944
  const commit = (next) => {
@@ -9133,9 +9980,9 @@ function UnknownLeaf({ schema, value, onChange, label, depth, disabled }) {
9133
9980
  value: text,
9134
9981
  disabled,
9135
9982
  onChange: (e) => commit(e.target.value)
9136
- }), error && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
9983
+ }), error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
9137
9984
  className: "error small",
9138
- children: ["JSON 错误:", error]
9985
+ children: t("test.jsonError", { error })
9139
9986
  })]
9140
9987
  })]
9141
9988
  });
@@ -9149,19 +9996,27 @@ function Constraints({ schema }) {
9149
9996
  }
9150
9997
  //#endregion
9151
9998
  //#region webui/src/components/TestPanel.tsx
9152
- var COPY_OPTIONS = [{
9153
- value: "curl",
9154
- label: "复制 curl"
9155
- }, {
9156
- value: "fetch",
9157
- label: "复制 fetch"
9158
- }];
9159
9999
  /** 格式化耗时:<1s 用 ms,否则用 s */
9160
10000
  function formatElapsed(ms) {
9161
10001
  if (ms < 1e3) return `${ms.toFixed(0)} ms`;
9162
10002
  return `${(ms / 1e3).toFixed(2)} s`;
9163
10003
  }
9164
10004
  function TestPanel({ method, detail, rpc, transport, endpoint }) {
10005
+ const t = useT();
10006
+ const COPY_OPTIONS = [
10007
+ {
10008
+ value: "curl",
10009
+ label: t("test.copyCurl")
10010
+ },
10011
+ {
10012
+ value: "curl-cmd",
10013
+ label: t("test.copyCurlCmd")
10014
+ },
10015
+ {
10016
+ value: "fetch",
10017
+ label: t("test.copyFetch")
10018
+ }
10019
+ ];
9165
10020
  const paramSchemas = (0, import_react.useMemo)(() => detail.signature.slice(1), [detail.signature]);
9166
10021
  const isStreamReturn = detail.signature[0]?.kind === "stream";
9167
10022
  const [values, setValues] = (0, import_react.useState)([]);
@@ -9219,8 +10074,11 @@ function TestPanel({ method, detail, rpc, transport, endpoint }) {
9219
10074
  setOutput(lines.join("\n"));
9220
10075
  }
9221
10076
  const totalAt = performance.now() - startedAt;
9222
- if (idx === 0) lines.push(`(流已结束,无数据,总耗时 ${formatElapsed(totalAt)})`);
9223
- else lines.push(`(流已结束,共 ${idx} 帧,总耗时 ${formatElapsed(totalAt)})`);
10077
+ if (idx === 0) lines.push(t("test.streamEndedEmpty", { duration: formatElapsed(totalAt) }));
10078
+ else lines.push(t("test.streamEnded", {
10079
+ count: idx,
10080
+ duration: formatElapsed(totalAt)
10081
+ }));
9224
10082
  setOutput(lines.join("\n"));
9225
10083
  setElapsed({
9226
10084
  total: totalAt,
@@ -9252,7 +10110,7 @@ function TestPanel({ method, detail, rpc, transport, endpoint }) {
9252
10110
  id: 1
9253
10111
  };
9254
10112
  const url = absoluteEndpoint(endpoint);
9255
- code = kind === "curl" ? buildCurl(url, body) : buildFetch(url, body);
10113
+ code = kind === "curl" ? buildCurl(url, body) : kind === "curl-cmd" ? buildCurlCmd(url, body) : buildFetch(url, body);
9256
10114
  } catch (e) {
9257
10115
  setCopyError(e instanceof Error ? e.message : String(e));
9258
10116
  return;
@@ -9262,24 +10120,20 @@ function TestPanel({ method, detail, rpc, transport, endpoint }) {
9262
10120
  setCopied(true);
9263
10121
  setTimeout(() => setCopied(false), 2e3);
9264
10122
  } catch (e) {
9265
- setCopyError(`复制失败:${e instanceof Error ? e.message : String(e)}`);
10123
+ setCopyError(t("common.copyFailed", { error: e instanceof Error ? e.message : String(e) }));
9266
10124
  }
9267
10125
  };
9268
10126
  const canCopy = transport === "http";
9269
- const currentLabel = COPY_OPTIONS.find((o) => o.value === copyKind)?.label ?? "复制";
10127
+ const currentLabel = COPY_OPTIONS.find((o) => o.value === copyKind)?.label ?? t("common.copy");
9270
10128
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
9271
10129
  className: "test",
9272
10130
  children: [
9273
10131
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: [
9274
- "参数",
10132
+ t("test.params"),
9275
10133
  " ",
9276
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
10134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
9277
10135
  className: "hint",
9278
- children: [
9279
- "(",
9280
- paramSchemas.length,
9281
- " 个,按签名自动生成)"
9282
- ]
10136
+ children: t("test.paramsHint", { count: paramSchemas.length })
9283
10137
  })
9284
10138
  ] }),
9285
10139
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaForm, {
@@ -9294,7 +10148,7 @@ function TestPanel({ method, detail, rpc, transport, endpoint }) {
9294
10148
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
9295
10149
  onClick: execute,
9296
10150
  disabled: !method || running,
9297
- children: running ? isStreamReturn ? "接收中…" : "执行中…" : isStreamReturn ? "执行并接收流 ▶" : "执行 ▶"
10151
+ children: running ? isStreamReturn ? t("test.receiving") : t("test.executing") : isStreamReturn ? t("test.executeStream") : t("test.execute")
9298
10152
  }),
9299
10153
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
9300
10154
  className: "split-button",
@@ -9305,8 +10159,8 @@ function TestPanel({ method, detail, rpc, transport, endpoint }) {
9305
10159
  className: "split-button-main",
9306
10160
  onClick: () => void copyAs(copyKind),
9307
10161
  disabled: !method || !canCopy,
9308
- title: canCopy ? "复制当前调用的等价代码" : "仅 HTTP 传输可生成代码",
9309
- children: copied ? "已复制 ✓" : currentLabel
10162
+ title: canCopy ? t("test.copyTitle") : t("test.copyDisabledTitle"),
10163
+ children: copied ? t("common.copied") : currentLabel
9310
10164
  }),
9311
10165
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
9312
10166
  type: "button",
@@ -9315,7 +10169,7 @@ function TestPanel({ method, detail, rpc, transport, endpoint }) {
9315
10169
  disabled: !method || !canCopy,
9316
10170
  "aria-haspopup": "menu",
9317
10171
  "aria-expanded": menuOpen,
9318
- title: "选择复制格式",
10172
+ title: t("test.chooseFormat"),
9319
10173
  children: "▾"
9320
10174
  }),
9321
10175
  menuOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
@@ -9343,7 +10197,7 @@ function TestPanel({ method, detail, rpc, transport, endpoint }) {
9343
10197
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
9344
10198
  onClick: () => setValues(paramSchemas.map(sampleFromSchema)),
9345
10199
  disabled: !method,
9346
- children: "重置为示例"
10200
+ children: t("test.resetToSample")
9347
10201
  }),
9348
10202
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
9349
10203
  onClick: () => {
@@ -9351,15 +10205,15 @@ function TestPanel({ method, detail, rpc, transport, endpoint }) {
9351
10205
  setElapsed(null);
9352
10206
  },
9353
10207
  disabled: !output && !elapsed,
9354
- children: "清空输出"
10208
+ children: t("test.clearOutput")
9355
10209
  })
9356
10210
  ]
9357
10211
  }),
9358
10212
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: [
9359
- "结果",
10213
+ t("test.result"),
9360
10214
  isStreamReturn && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
9361
10215
  className: "hint",
9362
- children: "(流式)"
10216
+ children: t("test.resultStream")
9363
10217
  }),
9364
10218
  elapsed && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
9365
10219
  className: "elapsed-badge",
@@ -9371,7 +10225,9 @@ function TestPanel({ method, detail, rpc, transport, endpoint }) {
9371
10225
  className: "hint",
9372
10226
  children: [
9373
10227
  " ",
9374
- "· 首帧 ",
10228
+ "· ",
10229
+ t("test.firstFrame"),
10230
+ " ",
9375
10231
  formatElapsed(elapsed.firstFrame)
9376
10232
  ]
9377
10233
  })
@@ -9381,11 +10237,12 @@ function TestPanel({ method, detail, rpc, transport, endpoint }) {
9381
10237
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
9382
10238
  className: "output",
9383
10239
  ref: outRef,
9384
- children: output || "(尚未执行)"
10240
+ children: output || t("test.notExecuted")
9385
10241
  })
9386
10242
  ]
9387
10243
  });
9388
10244
  }
10245
+ /** bash / POSIX sh:单引号包裹,内部单引号用 '\'' 转义;反斜杠续行。 */
9389
10246
  function buildCurl(url, body) {
9390
10247
  const quotedBody = `'${JSON.stringify(body, null, 2).replace(/'/g, `'\\''`)}'`;
9391
10248
  return [
@@ -9394,6 +10251,23 @@ function buildCurl(url, body) {
9394
10251
  ` -d ${quotedBody}`
9395
10252
  ].join(" \\\n");
9396
10253
  }
10254
+ /**
10255
+ * Windows CMD:双引号包裹。
10256
+ * - 双引号 " 用 \" 转义(curl.exe 在 cmd 下按 C 运行时规则解析)
10257
+ * - % 用 %% 转义(避免被 cmd 当作变量展开)
10258
+ * - 换行用 ^ 续行
10259
+ * - 其余字符原样保留
10260
+ */
10261
+ function buildCurlCmd(url, body) {
10262
+ const bodyText = JSON.stringify(body);
10263
+ const escapeCmdDoubleQuoted = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/%/g, "%%");
10264
+ const quotedBody = `"${escapeCmdDoubleQuoted(bodyText)}"`;
10265
+ return [
10266
+ `curl -X POST ${`"${escapeCmdDoubleQuoted(url)}"`} ^`,
10267
+ ` -H "Content-Type: application/json" ^`,
10268
+ ` -d ${quotedBody}`
10269
+ ].join("\n");
10270
+ }
9397
10271
  function buildFetch(url, body) {
9398
10272
  const indented = JSON.stringify(body, null, 2).split("\n").map((line, i) => i === 0 ? line : " " + line).join("\n");
9399
10273
  return [
@@ -9445,6 +10319,7 @@ function rateClass(v) {
9445
10319
  return "rate-cold";
9446
10320
  }
9447
10321
  function MetricsPanel({ snapshot, previous, fetchedAt, loading, error, available, onRefresh, autoRefresh, onAutoRefreshChange, intervalMs, onIntervalChange }) {
10322
+ const t = useT();
9448
10323
  const derived = (0, import_react.useMemo)(() => {
9449
10324
  if (!snapshot) return [];
9450
10325
  const dtSeconds = previous && fetchedAt ? (fetchedAt - previous.fetchedAt) / 1e3 : void 0;
@@ -9486,15 +10361,9 @@ function MetricsPanel({ snapshot, previous, fetchedAt, loading, error, available
9486
10361
  }, [snapshot]);
9487
10362
  if (!available) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
9488
10363
  className: "metrics",
9489
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Metrics" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
10364
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: t("metrics.title") }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
9490
10365
  className: "hint",
9491
- children: [
9492
- "服务端未启用 ",
9493
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "system.metrics" }),
9494
- "(",
9495
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "withSystemMethods: false" }),
9496
- " 或未注册)。"
9497
- ]
10366
+ children: t("metrics.unavailable")
9498
10367
  })]
9499
10368
  });
9500
10369
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
@@ -9502,7 +10371,7 @@ function MetricsPanel({ snapshot, previous, fetchedAt, loading, error, available
9502
10371
  children: [
9503
10372
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
9504
10373
  className: "metrics-header",
9505
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Metrics" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
10374
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: t("metrics.title") }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
9506
10375
  className: "metrics-controls",
9507
10376
  children: [
9508
10377
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
@@ -9511,14 +10380,14 @@ function MetricsPanel({ snapshot, previous, fetchedAt, loading, error, available
9511
10380
  type: "checkbox",
9512
10381
  checked: autoRefresh,
9513
10382
  onChange: (e) => onAutoRefreshChange(e.target.checked)
9514
- }), "自动刷新"]
10383
+ }), t("metrics.autoRefresh")]
9515
10384
  }),
9516
10385
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
9517
10386
  className: "sf-input",
9518
10387
  value: intervalMs,
9519
10388
  onChange: (e) => onIntervalChange(Number(e.target.value)),
9520
10389
  disabled: !autoRefresh,
9521
- title: "刷新间隔",
10390
+ title: t("metrics.interval"),
9522
10391
  children: [
9523
10392
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
9524
10393
  value: 1e3,
@@ -9541,11 +10410,15 @@ function MetricsPanel({ snapshot, previous, fetchedAt, loading, error, available
9541
10410
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
9542
10411
  onClick: onRefresh,
9543
10412
  disabled: loading,
9544
- children: loading ? "刷新中…" : "立即刷新"
10413
+ children: loading ? t("metrics.refreshing") : t("metrics.refreshNow")
9545
10414
  }),
9546
10415
  snapshot && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
9547
10416
  className: "hint",
9548
- children: ["uptime ", formatDuration(snapshot.uptimeMs / 1e3)]
10417
+ children: [
10418
+ t("metrics.uptime"),
10419
+ " ",
10420
+ formatDuration(snapshot.uptimeMs / 1e3)
10421
+ ]
9549
10422
  })
9550
10423
  ]
9551
10424
  })]
@@ -9556,64 +10429,56 @@ function MetricsPanel({ snapshot, previous, fetchedAt, loading, error, available
9556
10429
  }),
9557
10430
  !snapshot && !error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
9558
10431
  className: "hint",
9559
- children: "加载中…"
10432
+ children: t("metrics.loading")
9560
10433
  }),
9561
10434
  snapshot && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
9562
10435
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
9563
10436
  className: "metrics-cards",
9564
10437
  children: [
9565
10438
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricCard, {
9566
- label: "Methods",
10439
+ label: t("metrics.methods"),
9567
10440
  value: String(totals.methods)
9568
10441
  }),
9569
10442
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricCard, {
9570
- label: "Calls",
10443
+ label: t("metrics.calls"),
9571
10444
  value: String(totals.calls)
9572
10445
  }),
9573
10446
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricCard, {
9574
- label: "Streams",
10447
+ label: t("metrics.streams"),
9575
10448
  value: String(totals.streams)
9576
10449
  }),
9577
10450
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricCard, {
9578
- label: "Errors",
10451
+ label: t("metrics.errors"),
9579
10452
  value: String(totals.errors),
9580
10453
  tone: totals.errors > 0 ? "danger" : void 0
9581
10454
  }),
9582
10455
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricCard, {
9583
- label: "Error rate",
10456
+ label: t("metrics.errorRate"),
9584
10457
  value: totals.calls > 0 ? formatPercent(totals.errors / totals.calls) : "—",
9585
10458
  tone: totals.calls > 0 && totals.errors / totals.calls > .05 ? "danger" : void 0
9586
10459
  }),
9587
10460
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricCard, {
9588
- label: "Invalid requests",
10461
+ label: t("metrics.invalidRequests"),
9589
10462
  value: String(snapshot.invalidRequests),
9590
10463
  tone: snapshot.invalidRequests > 0 ? "warn" : void 0
9591
10464
  })
9592
10465
  ]
9593
10466
  }),
9594
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: ["Non-stream methods", /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
10467
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: [t("metrics.nonStreamTitle"), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
9595
10468
  className: "hint",
9596
- children: [
9597
- " (",
9598
- nonStreamMethods.length,
9599
- ")"
9600
- ]
10469
+ children: [" ", t("metrics.countHint", { count: nonStreamMethods.length })]
9601
10470
  })] }),
9602
10471
  nonStreamMethods.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
9603
10472
  className: "hint",
9604
- children: "暂无数据。"
10473
+ children: t("metrics.noData")
9605
10474
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NonStreamTable, { rows: nonStreamMethods }),
9606
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: ["Stream methods", /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
10475
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: [t("metrics.streamTitle"), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
9607
10476
  className: "hint",
9608
- children: [
9609
- " (",
9610
- streamMethods.length,
9611
- ")"
9612
- ]
10477
+ children: [" ", t("metrics.countHint", { count: streamMethods.length })]
9613
10478
  })] }),
9614
10479
  streamMethods.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
9615
10480
  className: "hint",
9616
- children: "暂无流式调用。"
10481
+ children: t("metrics.noStreams")
9617
10482
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StreamTable, { rows: streamMethods })
9618
10483
  ] })
9619
10484
  ]
@@ -9632,41 +10497,42 @@ function MetricCard({ label, value, tone }) {
9632
10497
  });
9633
10498
  }
9634
10499
  function NonStreamTable({ rows }) {
10500
+ const t = useT();
9635
10501
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", {
9636
10502
  className: "metrics-table",
9637
10503
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
9638
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "method" }),
10504
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: t("metrics.colMethod") }),
9639
10505
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9640
10506
  className: "num",
9641
- children: "calls"
10507
+ children: t("metrics.colCalls")
9642
10508
  }),
9643
10509
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9644
10510
  className: "num",
9645
- children: "rate/s"
10511
+ children: t("metrics.colRate")
9646
10512
  }),
9647
10513
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9648
10514
  className: "num",
9649
- children: "errors"
10515
+ children: t("metrics.colErrors")
9650
10516
  }),
9651
10517
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9652
10518
  className: "num",
9653
- children: "err%"
10519
+ children: t("metrics.colErrPct")
9654
10520
  }),
9655
10521
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9656
10522
  className: "num",
9657
- children: "p50"
10523
+ children: t("metrics.colP50")
9658
10524
  }),
9659
10525
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9660
10526
  className: "num",
9661
- children: "p95"
10527
+ children: t("metrics.colP95")
9662
10528
  }),
9663
10529
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9664
10530
  className: "num",
9665
- children: "p99"
10531
+ children: t("metrics.colP99")
9666
10532
  }),
9667
10533
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9668
10534
  className: "num",
9669
- children: "avg"
10535
+ children: t("metrics.colAvg")
9670
10536
  })
9671
10537
  ] }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: rows.map((r) => {
9672
10538
  const errorCount = Object.values(r.stats.errorsByCode).reduce((a, b) => a + b, 0);
@@ -9714,49 +10580,50 @@ function NonStreamTable({ rows }) {
9714
10580
  });
9715
10581
  }
9716
10582
  function StreamTable({ rows }) {
10583
+ const t = useT();
9717
10584
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", {
9718
10585
  className: "metrics-table",
9719
10586
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
9720
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "method" }),
10587
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: t("metrics.colMethod") }),
9721
10588
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9722
10589
  className: "num",
9723
- children: "opened"
10590
+ children: t("metrics.colOpened")
9724
10591
  }),
9725
10592
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9726
10593
  className: "num",
9727
- children: "completed"
10594
+ children: t("metrics.colCompleted")
9728
10595
  }),
9729
10596
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9730
10597
  className: "num",
9731
- children: "failed"
10598
+ children: t("metrics.colFailed")
9732
10599
  }),
9733
10600
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9734
10601
  className: "num",
9735
- children: "aborted"
10602
+ children: t("metrics.colAborted")
9736
10603
  }),
9737
10604
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9738
10605
  className: "num",
9739
- children: "complete%"
10606
+ children: t("metrics.colCompletePct")
9740
10607
  }),
9741
10608
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9742
10609
  className: "num",
9743
- children: "abort%"
10610
+ children: t("metrics.colAbortPct")
9744
10611
  }),
9745
10612
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9746
10613
  className: "num",
9747
- children: "avg chunks"
10614
+ children: t("metrics.colAvgChunks")
9748
10615
  }),
9749
10616
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9750
10617
  className: "num",
9751
- children: "ttfb p50"
10618
+ children: t("metrics.colTtfbP50")
9752
10619
  }),
9753
10620
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9754
10621
  className: "num",
9755
- children: "ttfb p95"
10622
+ children: t("metrics.colTtfbP95")
9756
10623
  }),
9757
10624
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
9758
10625
  className: "num",
9759
- children: "duration avg"
10626
+ children: t("metrics.colDurationAvg")
9760
10627
  })
9761
10628
  ] }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: rows.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
9762
10629
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
@@ -9808,58 +10675,88 @@ function StreamTable({ rows }) {
9808
10675
  });
9809
10676
  }
9810
10677
  //#endregion
9811
- //#region webui/src/api/glob.ts
9812
- function globToRegExp(glob) {
9813
- let re = "^";
9814
- for (let i = 0; i < glob.length; i++) {
9815
- const ch = glob[i];
9816
- if (ch === "*") {
9817
- while (i + 1 < glob.length && glob[i + 1] === "*") i++;
9818
- re += ".*";
9819
- } else if (ch === "?") re += ".";
9820
- else if ("\\^$+{}()|[]".includes(ch)) re += "\\" + ch;
9821
- else re += ch;
9822
- }
9823
- re += "$";
9824
- return new RegExp(re);
10678
+ //#region webui/src/components/Footer.tsx
10679
+ var NPM_URL = "https://www.npmjs.com/package/@yuhere/js-jsonrpc2/";
10680
+ var GITHUB_URL = "https://github.com/yuhere/js-jsonrpc2";
10681
+ function Footer() {
10682
+ const t = useT();
10683
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", {
10684
+ className: "footer",
10685
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
10686
+ className: "footer-version",
10687
+ title: t("footer.version"),
10688
+ children: ["v", "2.1.14"]
10689
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
10690
+ className: "footer-links",
10691
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
10692
+ className: "npm-link",
10693
+ href: NPM_URL,
10694
+ target: "_blank",
10695
+ rel: "noopener noreferrer",
10696
+ title: t("footer.npm"),
10697
+ "aria-label": t("footer.npm"),
10698
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
10699
+ viewBox: "0 0 18 7",
10700
+ width: "36",
10701
+ height: "14",
10702
+ role: "img",
10703
+ "aria-hidden": "true",
10704
+ focusable: "false",
10705
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
10706
+ fill: "#CB3837",
10707
+ d: "M0 0h18v7H9v-1.5H7.5V7H0z"
10708
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
10709
+ fill: "#fff",
10710
+ d: "M1.5 1.5h3v4h-1v-3h-1v3h-1zm4 0h3v4h-1v-3h-1v3h-1zm4 0h4v4h-1v-3h-1v3h-1v-3h-1z"
10711
+ })]
10712
+ })
10713
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
10714
+ className: "github-link",
10715
+ href: GITHUB_URL,
10716
+ target: "_blank",
10717
+ rel: "noopener noreferrer",
10718
+ title: t("footer.github"),
10719
+ "aria-label": t("footer.github"),
10720
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
10721
+ viewBox: "0 0 16 16",
10722
+ width: "16",
10723
+ height: "16",
10724
+ role: "img",
10725
+ "aria-hidden": "true",
10726
+ focusable: "false",
10727
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
10728
+ fill: "currentColor",
10729
+ d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"
10730
+ })
10731
+ })
10732
+ })]
10733
+ })]
10734
+ });
9825
10735
  }
9826
- function compileGlobFilter(input) {
10736
+ //#endregion
10737
+ //#region webui/src/utils/fuzzy.ts
10738
+ function compileFuzzyQuery(input) {
9827
10739
  const raw = input ?? "";
9828
- const trimmed = raw.trim();
9829
- if (trimmed.length === 0) return {
9830
- includes: [],
9831
- excludes: [],
9832
- raw,
9833
- passthrough: true
9834
- };
9835
- const includes = [];
9836
- const excludes = [];
9837
- for (const part of trimmed.split(",")) {
9838
- const pat = part.trim();
9839
- if (pat.length === 0) continue;
9840
- if (pat.startsWith("!")) {
9841
- const body = pat.slice(1).trim();
9842
- if (body.length > 0) excludes.push(globToRegExp(body));
9843
- } else includes.push(globToRegExp(pat));
9844
- }
10740
+ const terms = raw.trim().toLowerCase().split(/\s+/).filter((t) => t.length > 0);
9845
10741
  return {
9846
- includes,
9847
- excludes,
10742
+ terms,
9848
10743
  raw,
9849
- passthrough: false
10744
+ passthrough: terms.length === 0
9850
10745
  };
9851
10746
  }
9852
- function matchMethod(filter, method) {
9853
- if (filter.passthrough) return true;
9854
- if (filter.excludes.some((re) => re.test(method))) return false;
9855
- if (filter.includes.length === 0) return true;
9856
- return filter.includes.some((re) => re.test(method));
10747
+ function matchFuzzy(query, method) {
10748
+ if (query.passthrough) return true;
10749
+ const target = method.toLowerCase();
10750
+ return query.terms.every((t) => target.includes(t));
10751
+ }
10752
+ function filterFuzzyMethods(query, methods) {
10753
+ if (query.passthrough) return methods;
10754
+ return methods.filter((m) => matchFuzzy(query, m));
9857
10755
  }
9858
- /** 便捷:直接对 MetricsSnapshot 的 methods 做过滤,返回浅拷贝。 */
9859
- function filterSnapshotMethods(filter, methods) {
9860
- if (filter.passthrough) return methods;
10756
+ function filterFuzzySnapshotMethods(query, methods) {
10757
+ if (query.passthrough) return methods;
9861
10758
  const out = {};
9862
- for (const [method, stats] of Object.entries(methods)) if (matchMethod(filter, method)) out[method] = stats;
10759
+ for (const [method, stats] of Object.entries(methods)) if (matchFuzzy(query, method)) out[method] = stats;
9863
10760
  return out;
9864
10761
  }
9865
10762
  //#endregion
@@ -9894,6 +10791,13 @@ async function collectApiDoc(rpc, methods, onProgress) {
9894
10791
  methods: out
9895
10792
  };
9896
10793
  }
10794
+ /**
10795
+ * 树状渲染 schema。
10796
+ * - array / record / stream:直接展开子节点,不挂 element / value / inner 标签
10797
+ * - object:属性名作标签
10798
+ * - union:option[i]
10799
+ * - tuple:[i]
10800
+ */
9897
10801
  function renderSchemaTree(schema, indent = 0) {
9898
10802
  const pad = " ".repeat(indent);
9899
10803
  const lines = [];
@@ -9906,26 +10810,57 @@ function renderSchemaTree(schema, indent = 0) {
9906
10810
  if (parts.length) head += ` \`{ ${parts.join(", ")} }\``;
9907
10811
  }
9908
10812
  lines.push(head);
9909
- const ch = schema.children ?? {};
9910
- const pushChild = (label, child) => {
10813
+ /** 带标签的子节点(object 属性 / union 分支 / tuple 位) */
10814
+ const pushLabeled = (label, child) => {
9911
10815
  const sub = renderSchemaTree(child, indent + 1);
9912
10816
  if (sub.length) {
9913
10817
  sub[0] = `${" ".repeat(indent + 1)}- **${label}**: ${sub[0].trim().replace(/^- /, "")}`;
9914
10818
  lines.push(...sub);
9915
10819
  }
9916
10820
  };
9917
- if (isSchema(ch.inner)) pushChild("inner", ch.inner);
9918
- if (Array.isArray(ch.items)) ch.items.forEach((it, i) => {
9919
- if (isSchema(it)) pushChild(`[${i}]`, it);
9920
- });
9921
- else if (isSchema(ch.items)) pushChild("items", ch.items);
9922
- if (Array.isArray(ch.options)) ch.options.forEach((opt, i) => {
9923
- if (isSchema(opt)) pushChild(`option[${i}]`, opt);
9924
- });
9925
- if (ch.properties && typeof ch.properties === "object") {
9926
- for (const [k, v] of Object.entries(ch.properties)) if (isSchema(v)) pushChild(k, v);
10821
+ /** 无标签,直接展开(array element / record value / stream inner) */
10822
+ const pushPlain = (child) => {
10823
+ lines.push(...renderSchemaTree(child, indent + 1));
10824
+ };
10825
+ switch (schema.kind) {
10826
+ case "array": {
10827
+ const element = schema.element;
10828
+ if (isSchema(element)) pushPlain(element);
10829
+ break;
10830
+ }
10831
+ case "record": {
10832
+ const value = schema.value;
10833
+ if (isSchema(value)) pushPlain(value);
10834
+ break;
10835
+ }
10836
+ case "tuple": {
10837
+ const elements = schema.elements;
10838
+ if (Array.isArray(elements)) elements.forEach((it, i) => {
10839
+ if (isSchema(it)) pushLabeled(`[${i}]`, it);
10840
+ });
10841
+ break;
10842
+ }
10843
+ case "union": {
10844
+ const options = schema.options;
10845
+ if (Array.isArray(options)) options.forEach((opt, i) => {
10846
+ if (isSchema(opt)) pushLabeled(`option[${i}]`, opt);
10847
+ });
10848
+ break;
10849
+ }
10850
+ case "object": {
10851
+ const props = schema.properties;
10852
+ if (props && typeof props === "object" && !Array.isArray(props)) {
10853
+ for (const [k, v] of Object.entries(props)) if (isSchema(v)) pushLabeled(k, v);
10854
+ }
10855
+ break;
10856
+ }
10857
+ case "stream": {
10858
+ const any = schema;
10859
+ const inner = isSchema(any.inner) ? any.inner : isSchema(any.children?.inner) ? any.children.inner : void 0;
10860
+ if (inner) pushPlain(inner);
10861
+ break;
10862
+ }
9927
10863
  }
9928
- if (isSchema(ch.values)) pushChild("values", ch.values);
9929
10864
  return lines;
9930
10865
  }
9931
10866
  function renderMarkdown(doc) {
@@ -9962,7 +10897,7 @@ function renderMarkdown(doc) {
9962
10897
  const [ret, ...params] = m.signature;
9963
10898
  lines.push("**返回类型**");
9964
10899
  lines.push("");
9965
- lines.push(...renderSchemaTree(ret, 0));
10900
+ if (ret) lines.push(...renderSchemaTree(ret, 0));
9966
10901
  lines.push("");
9967
10902
  lines.push(`**参数**(${params.length} 个)`);
9968
10903
  lines.push("");
@@ -9980,7 +10915,6 @@ function renderMarkdown(doc) {
9980
10915
  }
9981
10916
  return lines.join("\n");
9982
10917
  }
9983
- /** 触发浏览器下载 */
9984
10918
  function downloadText(filename, text, mime = "text/plain") {
9985
10919
  const blob = new Blob([text], { type: `${mime};charset=utf-8` });
9986
10920
  const url = URL.createObjectURL(blob);
@@ -9996,12 +10930,16 @@ function downloadText(filename, text, mime = "text/plain") {
9996
10930
  //#region webui/src/App.tsx
9997
10931
  var ENDPOINT = "";
9998
10932
  function App() {
10933
+ const { t } = useI18n();
9999
10934
  const [transport, setTransport] = (0, import_react.useState)("http");
10000
10935
  const [selected, setSelected] = (0, import_react.useState)("");
10001
10936
  const [generatingDocs, setGeneratingDocs] = (0, import_react.useState)(false);
10002
10937
  const [view, setView] = (0, import_react.useState)("methods");
10003
- const [methodFilter, setMethodFilter] = (0, import_react.useState)("");
10004
- const rpc = (0, import_react.useMemo)(() => createRpc({
10938
+ const [methodSearch, setMethodSearch] = (0, import_react.useState)("");
10939
+ const [metricsSearch, setMetricsSearch] = (0, import_react.useState)("");
10940
+ const debouncedMethodSearch = useDebouncedValue(methodSearch, 250);
10941
+ const debouncedMetricsSearch = useDebouncedValue(metricsSearch, 250);
10942
+ const rpc = (0, import_react.useMemo)(() => createRPC({
10005
10943
  transport,
10006
10944
  endpoint: ENDPOINT
10007
10945
  }), [transport]);
@@ -10009,18 +10947,20 @@ function App() {
10009
10947
  const { methods, loading, error, refresh } = useMethods(rpc);
10010
10948
  const detail = useMethodDetail(rpc, selected);
10011
10949
  const metrics = useMetrics(rpc, view === "metrics");
10012
- const compiledFilter = (0, import_react.useMemo)(() => compileGlobFilter(methodFilter), [methodFilter]);
10013
- const filteredSnapshot = (0, import_react.useMemo)(() => {
10950
+ const methodQuery = (0, import_react.useMemo)(() => compileFuzzyQuery(debouncedMethodSearch), [debouncedMethodSearch]);
10951
+ const metricsQuery = (0, import_react.useMemo)(() => compileFuzzyQuery(debouncedMetricsSearch), [debouncedMetricsSearch]);
10952
+ const filteredMethods = (0, import_react.useMemo)(() => filterFuzzyMethods(methodQuery, methods), [methodQuery, methods]);
10953
+ const filteredMetrics = (0, import_react.useMemo)(() => {
10014
10954
  if (!metrics.snapshot) return void 0;
10015
- if (compiledFilter.passthrough) return metrics.snapshot;
10955
+ if (metricsQuery.passthrough) return metrics.snapshot;
10016
10956
  return {
10017
10957
  ...metrics.snapshot,
10018
- methods: filterSnapshotMethods(compiledFilter, metrics.snapshot.methods)
10958
+ methods: filterFuzzySnapshotMethods(metricsQuery, metrics.snapshot.methods)
10019
10959
  };
10020
- }, [metrics.snapshot, compiledFilter]);
10960
+ }, [metrics.snapshot, metricsQuery]);
10021
10961
  const generateDocs = (0, import_react.useCallback)(async () => {
10022
10962
  if (methods.length === 0) {
10023
- alert("方法列表为空,请先刷新方法列表。");
10963
+ alert(t("alerts.emptyMethods"));
10024
10964
  return;
10025
10965
  }
10026
10966
  setGeneratingDocs(true);
@@ -10028,131 +10968,137 @@ function App() {
10028
10968
  const md = renderMarkdown(await collectApiDoc(rpc, methods));
10029
10969
  downloadText(`jsonrpc-api-doc-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}.md`, md, "text/markdown");
10030
10970
  } catch (e) {
10031
- alert(`生成文档失败:${e instanceof Error ? e.message : String(e)}`);
10971
+ alert(t("alerts.docsFailed", { error: e instanceof Error ? e.message : String(e) }));
10032
10972
  } finally {
10033
10973
  setGeneratingDocs(false);
10034
10974
  }
10035
- }, [rpc, methods]);
10975
+ }, [
10976
+ rpc,
10977
+ methods,
10978
+ t
10979
+ ]);
10036
10980
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
10037
10981
  className: "app",
10038
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Toolbar, {
10039
- transport,
10040
- onTransportChange: setTransport,
10041
- onRefresh: refresh,
10042
- loading,
10043
- error,
10044
- onGenerateDocs: generateDocs,
10045
- generatingDocs
10046
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
10047
- className: "sidebar",
10048
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
10049
- className: "view-switch segmented",
10050
- role: "group",
10051
- "aria-label": "视图",
10052
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
10053
- type: "button",
10054
- className: view === "methods" ? "active" : "",
10055
- onClick: () => setView("methods"),
10056
- children: "Methods"
10057
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
10058
- type: "button",
10059
- className: view === "metrics" ? "active" : "",
10060
- onClick: () => setView("metrics"),
10061
- children: "Metrics"
10062
- })]
10063
- }), view === "methods" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Methods" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MethodTree, {
10064
- methods,
10065
- selected,
10066
- onSelect: setSelected
10067
- })] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
10068
- className: "metrics-sidebar",
10069
- children: [
10070
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Metrics" }),
10071
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
10072
- className: "hint",
10073
- style: { margin: "0 0 8px" },
10074
- children: "📊 服务端 RPC 调用统计。"
10075
- }),
10076
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
10077
- className: "metrics-filter",
10078
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
10079
- className: "metrics-filter-label",
10080
- children: "Filter"
10081
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
10082
- className: "sf-input",
10083
- type: "text",
10084
- placeholder: "user.*, !system.*",
10085
- value: methodFilter,
10086
- onChange: (e) => setMethodFilter(e.target.value),
10087
- spellCheck: false
10088
- })]
10089
- }),
10090
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
10091
- className: "hint metrics-filter-help",
10092
- children: [
10093
- "支持 ",
10094
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "*" }),
10095
- " / ",
10096
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "?" }),
10097
- " / ",
10098
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "!" }),
10099
- ",多个模式用 ",
10100
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "," }),
10101
- " 分隔。",
10102
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
10103
- "例:",
10104
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "user.*" }),
10105
- "、",
10106
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "*.get" }),
10107
- "、",
10108
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "!system.*" }),
10109
- "、",
10110
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "user.*,!user.secret" })
10111
- ]
10112
- }),
10113
- metrics.snapshot && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
10114
- className: "hint metrics-filter-stat",
10115
- children: [
10116
- "匹配 ",
10117
- Object.keys(filteredSnapshot?.methods ?? {}).length,
10118
- " /",
10119
- " ",
10120
- Object.keys(metrics.snapshot.methods).length,
10121
- " 个方法"
10122
- ]
10123
- })
10124
- ]
10125
- })]
10126
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
10127
- className: "content",
10128
- children: view === "methods" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MethodDetail, {
10129
- method: selected,
10130
- detail
10131
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TestPanel, {
10132
- method: selected,
10133
- detail,
10134
- rpc,
10982
+ children: [
10983
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Toolbar, {
10135
10984
  transport,
10136
- endpoint: ENDPOINT
10137
- })] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricsPanel, {
10138
- snapshot: filteredSnapshot,
10139
- previous: metrics.previous,
10140
- fetchedAt: metrics.fetchedAt,
10141
- loading: metrics.loading,
10142
- error: metrics.error,
10143
- available: metrics.available,
10144
- onRefresh: () => void metrics.refresh(),
10145
- autoRefresh: metrics.autoRefresh,
10146
- onAutoRefreshChange: metrics.setAutoRefresh,
10147
- intervalMs: metrics.intervalMs,
10148
- onIntervalChange: metrics.setIntervalMs
10149
- })
10150
- })] })]
10985
+ onTransportChange: setTransport,
10986
+ onRefresh: refresh,
10987
+ loading,
10988
+ error,
10989
+ onGenerateDocs: generateDocs,
10990
+ generatingDocs
10991
+ }),
10992
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
10993
+ className: "sidebar",
10994
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
10995
+ className: "view-switch segmented",
10996
+ role: "group",
10997
+ "aria-label": t("view.label"),
10998
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
10999
+ type: "button",
11000
+ className: view === "methods" ? "active" : "",
11001
+ onClick: () => setView("methods"),
11002
+ children: t("view.methods")
11003
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
11004
+ type: "button",
11005
+ className: view === "metrics" ? "active" : "",
11006
+ onClick: () => setView("metrics"),
11007
+ children: t("view.metrics")
11008
+ })]
11009
+ }), view === "methods" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11010
+ className: "metrics-sidebar",
11011
+ children: [
11012
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: t("sidebar.methods") }),
11013
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
11014
+ className: "metrics-filter",
11015
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
11016
+ className: "metrics-filter-label",
11017
+ children: t("sidebar.search")
11018
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
11019
+ className: "sf-input",
11020
+ type: "text",
11021
+ placeholder: t("sidebar.searchPlaceholder"),
11022
+ value: methodSearch,
11023
+ onChange: (e) => setMethodSearch(e.target.value),
11024
+ spellCheck: false
11025
+ })]
11026
+ }),
11027
+ !methodQuery.passthrough && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
11028
+ className: "hint metrics-filter-stat",
11029
+ children: t("sidebar.matchCount", {
11030
+ matched: filteredMethods.length,
11031
+ total: methods.length
11032
+ })
11033
+ }),
11034
+ filteredMethods.length === 0 && methods.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
11035
+ className: "empty",
11036
+ children: t("sidebar.noMatch")
11037
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MethodTree, {
11038
+ methods: filteredMethods,
11039
+ selected,
11040
+ onSelect: setSelected
11041
+ })
11042
+ ]
11043
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
11044
+ className: "metrics-sidebar",
11045
+ children: [
11046
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: t("sidebar.metrics") }),
11047
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
11048
+ className: "metrics-filter",
11049
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
11050
+ className: "metrics-filter-label",
11051
+ children: t("sidebar.search")
11052
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
11053
+ className: "sf-input",
11054
+ type: "text",
11055
+ placeholder: t("sidebar.searchPlaceholder"),
11056
+ value: metricsSearch,
11057
+ onChange: (e) => setMetricsSearch(e.target.value),
11058
+ spellCheck: false
11059
+ })]
11060
+ }),
11061
+ metrics.snapshot && !metricsQuery.passthrough && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
11062
+ className: "hint metrics-filter-stat",
11063
+ children: t("sidebar.matchCount", {
11064
+ matched: Object.keys(filteredMetrics?.methods ?? {}).length,
11065
+ total: Object.keys(metrics.snapshot.methods).length
11066
+ })
11067
+ })
11068
+ ]
11069
+ })]
11070
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
11071
+ className: "content",
11072
+ children: view === "methods" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MethodDetail, {
11073
+ method: selected,
11074
+ detail
11075
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TestPanel, {
11076
+ method: selected,
11077
+ detail,
11078
+ rpc,
11079
+ transport,
11080
+ endpoint: ENDPOINT
11081
+ })] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricsPanel, {
11082
+ snapshot: filteredMetrics,
11083
+ previous: metrics.previous,
11084
+ fetchedAt: metrics.fetchedAt,
11085
+ loading: metrics.loading,
11086
+ error: metrics.error,
11087
+ available: metrics.available,
11088
+ onRefresh: () => void metrics.refresh(),
11089
+ autoRefresh: metrics.autoRefresh,
11090
+ onAutoRefreshChange: metrics.setAutoRefresh,
11091
+ intervalMs: metrics.intervalMs,
11092
+ onIntervalChange: metrics.setIntervalMs
11093
+ })
11094
+ })] }),
11095
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Footer, {})
11096
+ ]
10151
11097
  });
10152
11098
  }
10153
11099
  //#endregion
10154
11100
  //#region webui/src/main.tsx
10155
- import_client.createRoot(document.getElementById("root")).render(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.StrictMode, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(App, {}) }) }));
11101
+ import_client.createRoot(document.getElementById("root")).render(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.StrictMode, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(I18nProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(App, {}) }) }) }));
10156
11102
  //#endregion
10157
11103
 
10158
- //# sourceMappingURL=index-Cr0Jnrgb.js.map
11104
+ //# sourceMappingURL=index-mYqtDOwe.js.map