@yuhere/js-jsonrpc2 2.1.2 → 2.1.4

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 (84) hide show
  1. package/dist/cli/cli.js +4 -4
  2. package/dist/cli/demo-methods.d.ts +1 -1
  3. package/dist/cli/demo-methods.js +1 -1
  4. package/dist/cli/express.js +2 -2
  5. package/dist/cli/stdio.js +1 -1
  6. package/dist/core/Repository.d.ts +2 -2
  7. package/dist/core/Repository.js +9 -9
  8. package/dist/core/convert.d.ts +2 -2
  9. package/dist/core/convert.js +2 -2
  10. package/dist/core/descriptor.d.ts +3 -3
  11. package/dist/core/descriptor.js +3 -3
  12. package/dist/core/index.d.ts +3 -3
  13. package/dist/core/index.js +2 -2
  14. package/dist/core/permission.d.ts +2 -2
  15. package/dist/core/permission.js +2 -2
  16. package/dist/core/spec-check.d.ts +1 -1
  17. package/dist/core/spec-check.js +3 -3
  18. package/dist/core/system-methods.d.ts +1 -1
  19. package/dist/core/system-methods.js +4 -4
  20. package/dist/errors/{RpcError.d.ts → RPCError.d.ts} +1 -1
  21. package/dist/errors/index.d.ts +2 -2
  22. package/dist/errors/index.js +2 -2
  23. package/dist/index.d.ts +11 -11
  24. package/dist/index.js +8 -8
  25. package/dist/schema/json/index.d.ts +16 -16
  26. package/dist/schema/json/index.js +4 -4
  27. package/dist/schema/json/marker.d.ts +1 -1
  28. package/dist/schema/z/protocol.d.ts +5 -5
  29. package/dist/schema/z/protocol.js +1 -1
  30. package/dist/schema/z/register.d.ts +1 -1
  31. package/dist/schema/z/register.js +1 -1
  32. package/dist/transport/express/body.js +2 -2
  33. package/dist/transport/express/index.d.ts +2 -2
  34. package/dist/transport/express/index.js +1 -1
  35. package/dist/transport/express/middleware.d.ts +2 -2
  36. package/dist/transport/express/middleware.js +5 -5
  37. package/dist/transport/express/respond.d.ts +1 -1
  38. package/dist/transport/express/respond.js +1 -1
  39. package/dist/transport/stdio/index.d.ts +1 -1
  40. package/dist/transport/stdio/index.js +1 -1
  41. package/dist/transport/stdio/middleware.d.ts +2 -2
  42. package/dist/transport/stdio/middleware.js +4 -4
  43. package/dist/transport/ws/index.d.ts +2 -2
  44. package/dist/transport/ws/index.js +2 -2
  45. package/dist/transport/ws/middleware.d.ts +2 -2
  46. package/dist/transport/ws/middleware.js +3 -3
  47. package/dist/transport/ws/standalone.d.ts +1 -1
  48. package/dist/transport/ws/standalone.js +2 -2
  49. package/dist/types.d.ts +4 -4
  50. package/htdoc/assets/{index-hcP2-9jw.css → index-BAZmvr5x.css} +13 -0
  51. package/htdoc/assets/{index-CQr-lnlD.js → index-sVQlhxjI.js} +223 -32
  52. package/htdoc/assets/index-sVQlhxjI.js.map +1 -0
  53. package/htdoc/index.html +2 -2
  54. package/package.json +5 -4
  55. package/webui/src/App.tsx +30 -3
  56. package/webui/src/api/docGen.ts +188 -0
  57. package/webui/src/components/SchemaForm.tsx +0 -1
  58. package/webui/src/components/TestPanel.tsx +53 -10
  59. package/webui/src/components/Toolbar.tsx +13 -1
  60. package/webui/src/styles/geek.css +13 -0
  61. package/dist/schema/index.d.ts +0 -4
  62. package/dist/schema/index.d.ts.map +0 -1
  63. package/dist/schema/index.js +0 -4
  64. package/dist/schema/index.js.map +0 -1
  65. package/dist/schema/primitives.d.ts +0 -25
  66. package/dist/schema/primitives.d.ts.map +0 -1
  67. package/dist/schema/primitives.js +0 -153
  68. package/dist/schema/primitives.js.map +0 -1
  69. package/dist/schema/rpc.d.ts +0 -21
  70. package/dist/schema/rpc.d.ts.map +0 -1
  71. package/dist/schema/rpc.js +0 -17
  72. package/dist/schema/rpc.js.map +0 -1
  73. package/dist/stream/index.d.ts +0 -3
  74. package/dist/stream/index.d.ts.map +0 -1
  75. package/dist/stream/index.js +0 -2
  76. package/dist/stream/index.js.map +0 -1
  77. package/dist/stream/stringify.d.ts +0 -19
  78. package/dist/stream/stringify.d.ts.map +0 -1
  79. package/dist/stream/stringify.js +0 -217
  80. package/dist/stream/stringify.js.map +0 -1
  81. package/htdoc/assets/index-CQr-lnlD.js.map +0 -1
  82. /package/dist/errors/{RpcError.d.ts.map → RPCError.d.ts.map} +0 -0
  83. /package/dist/errors/{RpcError.js → RPCError.js} +0 -0
  84. /package/dist/errors/{RpcError.js.map → RPCError.js.map} +0 -0
@@ -8129,7 +8129,7 @@ var THEME_OPTIONS = [
8129
8129
  label: "暗"
8130
8130
  }
8131
8131
  ];
8132
- function Toolbar({ transport, onTransportChange, onRefresh, loading, error }) {
8132
+ function Toolbar({ transport, onTransportChange, onRefresh, loading, error, onGenerateDocs, generatingDocs }) {
8133
8133
  const { mode, setMode } = useTheme();
8134
8134
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
8135
8135
  className: "toolbar",
@@ -8156,6 +8156,12 @@ function Toolbar({ transport, onTransportChange, onRefresh, loading, error }) {
8156
8156
  disabled: loading,
8157
8157
  children: loading ? "刷新中…" : "刷新方法列表"
8158
8158
  }),
8159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
8160
+ onClick: onGenerateDocs,
8161
+ disabled: generatingDocs || loading || loading,
8162
+ title: "拉取所有方法的帮助与签名,生成 Markdown 文档并下载",
8163
+ children: generatingDocs ? "生成中…" : "生成文档 ⬇"
8164
+ }),
8159
8165
  error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
8160
8166
  className: "error",
8161
8167
  children: error
@@ -8446,7 +8452,7 @@ function MethodDetail({ method, detail }) {
8446
8452
  //#endregion
8447
8453
  //#region webui/src/api/paramForm.ts
8448
8454
  /** 判断是否为合法 schema 描述(kind 为字符串) */
8449
- function isSchema(v) {
8455
+ function isSchema$1(v) {
8450
8456
  return !!v && typeof v === "object" && typeof v.kind === "string";
8451
8457
  }
8452
8458
  /**
@@ -8470,20 +8476,20 @@ function sampleFromSchema(schema) {
8470
8476
  const values = schema.constraints?.values;
8471
8477
  return Array.isArray(values) && values.length > 0 ? values[0] : "";
8472
8478
  }
8473
- case "nullable": return isSchema(c.inner) ? sampleFromSchema(c.inner) : null;
8474
- case "array": return [isSchema(c.items) ? sampleFromSchema(c.items) : null];
8475
- case "tuple": return (Array.isArray(c.items) ? c.items : []).map((it) => isSchema(it) ? sampleFromSchema(it) : null);
8479
+ case "nullable": return isSchema$1(c.inner) ? sampleFromSchema(c.inner) : null;
8480
+ case "array": return [isSchema$1(c.items) ? sampleFromSchema(c.items) : null];
8481
+ case "tuple": return (Array.isArray(c.items) ? c.items : []).map((it) => isSchema$1(it) ? sampleFromSchema(it) : null);
8476
8482
  case "union": {
8477
8483
  const options = Array.isArray(c.options) ? c.options : [];
8478
- return options.length > 0 && isSchema(options[0]) ? sampleFromSchema(options[0]) : null;
8484
+ return options.length > 0 && isSchema$1(options[0]) ? sampleFromSchema(options[0]) : null;
8479
8485
  }
8480
8486
  case "object": {
8481
8487
  const props = c.properties ?? {};
8482
8488
  const out = {};
8483
- for (const [k, v] of Object.entries(props)) if (isSchema(v)) out[k] = sampleFromSchema(v);
8489
+ for (const [k, v] of Object.entries(props)) if (isSchema$1(v)) out[k] = sampleFromSchema(v);
8484
8490
  return out;
8485
8491
  }
8486
- case "record": return { key: isSchema(c.values) ? sampleFromSchema(c.values) : null };
8492
+ case "record": return { key: isSchema$1(c.values) ? sampleFromSchema(c.values) : null };
8487
8493
  default: return null;
8488
8494
  }
8489
8495
  }
@@ -8531,7 +8537,7 @@ function SchemaField({ schema, value, onChange, label, depth = 0, defaultOpen =
8531
8537
  const [open, setOpen] = (0, import_react.useState)(defaultOpen);
8532
8538
  const kind = schema.kind;
8533
8539
  if (kind === "nullable") {
8534
- const inner = schema.children && isSchema(schema.children.inner) ? schema.children.inner : void 0;
8540
+ const inner = schema.children && isSchema$1(schema.children.inner) ? schema.children.inner : void 0;
8535
8541
  if (!inner) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UnknownLeaf, {
8536
8542
  schema,
8537
8543
  value,
@@ -8556,10 +8562,6 @@ function SchemaField({ schema, value, onChange, label, depth = 0, defaultOpen =
8556
8562
  style: { color: colorOf("nullable") },
8557
8563
  children: "nullable"
8558
8564
  }),
8559
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
8560
- className: "sf-nullable",
8561
- children: "| null"
8562
- }),
8563
8565
  !isNull && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
8564
8566
  type: "button",
8565
8567
  className: "tiny",
@@ -8623,7 +8625,7 @@ function SchemaField({ schema, value, onChange, label, depth = 0, defaultOpen =
8623
8625
  })]
8624
8626
  });
8625
8627
  if (kind === "union") {
8626
- const options = Array.isArray(schema.children?.options) ? schema.children.options.filter(isSchema) : [];
8628
+ const options = Array.isArray(schema.children?.options) ? schema.children.options.filter(isSchema$1) : [];
8627
8629
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
8628
8630
  className: "sf-node sf-row",
8629
8631
  style: { paddingLeft: depth * 14 },
@@ -8799,7 +8801,7 @@ function UnionField({ options, value, onChange, depth, disabled }) {
8799
8801
  function ContainerChildren({ schema, value, onChange, depth, disabled }) {
8800
8802
  const c = schema.children ?? {};
8801
8803
  if (schema.kind === "array") {
8802
- const itemSchema = isSchema(c.items) ? c.items : void 0;
8804
+ const itemSchema = isSchema$1(c.items) ? c.items : void 0;
8803
8805
  if (!itemSchema) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
8804
8806
  className: "hint",
8805
8807
  children: "(数组无 item schema)"
@@ -8835,7 +8837,7 @@ function ContainerChildren({ schema, value, onChange, depth, disabled }) {
8835
8837
  })] });
8836
8838
  }
8837
8839
  if (schema.kind === "tuple") {
8838
- const items = Array.isArray(c.items) ? c.items.filter(isSchema) : [];
8840
+ const items = Array.isArray(c.items) ? c.items.filter(isSchema$1) : [];
8839
8841
  const arr = Array.isArray(value) ? value : [];
8840
8842
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: items.map((itemSchema, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaField, {
8841
8843
  schema: itemSchema,
@@ -8856,7 +8858,7 @@ function ContainerChildren({ schema, value, onChange, depth, disabled }) {
8856
8858
  const obj = value && typeof value === "object" && !Array.isArray(value) ? value : {};
8857
8859
  const strict = Boolean(schema.constraints?.strict);
8858
8860
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [Object.entries(props).map(([key, prop]) => {
8859
- if (!isSchema(prop)) return null;
8861
+ if (!isSchema$1(prop)) return null;
8860
8862
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaField, {
8861
8863
  schema: prop,
8862
8864
  value: obj[key],
@@ -8881,7 +8883,7 @@ function ContainerChildren({ schema, value, onChange, depth, disabled }) {
8881
8883
  })] });
8882
8884
  }
8883
8885
  if (schema.kind === "record") {
8884
- const valueSchema = isSchema(c.values) ? c.values : void 0;
8886
+ const valueSchema = isSchema$1(c.values) ? c.values : void 0;
8885
8887
  if (!valueSchema) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
8886
8888
  className: "hint",
8887
8889
  children: "(record 无 value schema)"
@@ -9009,11 +9011,17 @@ var COPY_OPTIONS = [{
9009
9011
  value: "fetch",
9010
9012
  label: "复制 fetch"
9011
9013
  }];
9014
+ /** 格式化耗时:<1s 用 ms,否则用 s */
9015
+ function formatElapsed(ms) {
9016
+ if (ms < 1e3) return `${ms.toFixed(0)} ms`;
9017
+ return `${(ms / 1e3).toFixed(2)} s`;
9018
+ }
9012
9019
  function TestPanel({ method, detail, rpc, transport, endpoint }) {
9013
9020
  const paramSchemas = (0, import_react.useMemo)(() => detail.signature.slice(1), [detail.signature]);
9014
9021
  const isStreamReturn = detail.signature[0]?.kind === "stream";
9015
9022
  const [values, setValues] = (0, import_react.useState)([]);
9016
9023
  const [output, setOutput] = (0, import_react.useState)("");
9024
+ const [elapsed, setElapsed] = (0, import_react.useState)(null);
9017
9025
  const [running, setRunning] = (0, import_react.useState)(false);
9018
9026
  const [copyKind, setCopyKind] = (0, import_react.useState)("curl");
9019
9027
  const [copied, setCopied] = (0, import_react.useState)(false);
@@ -9025,6 +9033,7 @@ function TestPanel({ method, detail, rpc, transport, endpoint }) {
9025
9033
  if (!method) return;
9026
9034
  setValues(paramSchemas.map(sampleFromSchema));
9027
9035
  setOutput("");
9036
+ setElapsed(null);
9028
9037
  }, [method, paramSchemas]);
9029
9038
  (0, import_react.useEffect)(() => {
9030
9039
  setCopied(false);
@@ -9050,25 +9059,37 @@ function TestPanel({ method, detail, rpc, transport, endpoint }) {
9050
9059
  if (!method) return;
9051
9060
  setRunning(true);
9052
9061
  setOutput("");
9062
+ setElapsed(null);
9063
+ const startedAt = performance.now();
9053
9064
  try {
9054
9065
  const result = await rpc.call(method, ...values);
9066
+ const firstFrameAt = performance.now() - startedAt;
9055
9067
  if (isStreamResult(result)) {
9056
9068
  let idx = 0;
9057
9069
  const lines = [];
9058
9070
  for await (const value of result) {
9059
- lines.push(`[${idx}] ${JSON.stringify(value, null, 2)}`);
9071
+ const now = performance.now() - startedAt;
9072
+ lines.push(`[${idx}] (+${formatElapsed(now)}) ${JSON.stringify(value, null, 2)}`);
9060
9073
  idx++;
9061
9074
  setOutput(lines.join("\n"));
9062
9075
  }
9063
- if (idx === 0) setOutput("(流已结束,无数据)");
9064
- else lines.push(`(流已结束,共 ${idx} 帧)`), setOutput(lines.join("\n"));
9065
- } else setOutput(JSON.stringify(result, null, 2));
9066
- if (isStreamReturn) {} else {
9067
- const result = await rpc.call(method, ...values);
9076
+ const totalAt = performance.now() - startedAt;
9077
+ if (idx === 0) lines.push(`(流已结束,无数据,总耗时 ${formatElapsed(totalAt)})`);
9078
+ else lines.push(`(流已结束,共 ${idx} 帧,总耗时 ${formatElapsed(totalAt)})`);
9079
+ setOutput(lines.join("\n"));
9080
+ setElapsed({
9081
+ total: totalAt,
9082
+ firstFrame: firstFrameAt
9083
+ });
9084
+ } else {
9085
+ const totalAt = performance.now() - startedAt;
9068
9086
  setOutput(JSON.stringify(result, null, 2));
9087
+ setElapsed({ total: totalAt });
9069
9088
  }
9070
9089
  } catch (e) {
9090
+ const totalAt = performance.now() - startedAt;
9071
9091
  setOutput(formatError(e));
9092
+ setElapsed({ total: totalAt });
9072
9093
  } finally {
9073
9094
  setRunning(false);
9074
9095
  requestAnimationFrame(() => outRef.current?.scrollIntoView({ block: "nearest" }));
@@ -9180,16 +9201,38 @@ function TestPanel({ method, detail, rpc, transport, endpoint }) {
9180
9201
  children: "重置为示例"
9181
9202
  }),
9182
9203
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
9183
- onClick: () => setOutput(""),
9184
- disabled: !output,
9204
+ onClick: () => {
9205
+ setOutput("");
9206
+ setElapsed(null);
9207
+ },
9208
+ disabled: !output && !elapsed,
9185
9209
  children: "清空输出"
9186
9210
  })
9187
9211
  ]
9188
9212
  }),
9189
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: ["结果", isStreamReturn && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
9190
- className: "hint",
9191
- children: "(流式)"
9192
- })] }),
9213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: [
9214
+ "结果",
9215
+ isStreamReturn && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
9216
+ className: "hint",
9217
+ children: "(流式)"
9218
+ }),
9219
+ elapsed && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
9220
+ className: "elapsed-badge",
9221
+ children: [
9222
+ " ",
9223
+ "⏱ ",
9224
+ formatElapsed(elapsed.total),
9225
+ elapsed.firstFrame !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
9226
+ className: "hint",
9227
+ children: [
9228
+ " ",
9229
+ "· 首帧 ",
9230
+ formatElapsed(elapsed.firstFrame)
9231
+ ]
9232
+ })
9233
+ ]
9234
+ })
9235
+ ] }),
9193
9236
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
9194
9237
  className: "output",
9195
9238
  ref: outRef,
@@ -9232,11 +9275,142 @@ function formatError(e) {
9232
9275
  return `Error: ${e instanceof Error ? e.message : String(e)}`;
9233
9276
  }
9234
9277
  //#endregion
9278
+ //#region webui/src/api/docGen.ts
9279
+ function isSchema(v) {
9280
+ return !!v && typeof v === "object" && typeof v.kind === "string";
9281
+ }
9282
+ /** 逐个方法拉取 help + signature */
9283
+ async function collectApiDoc(rpc, methods, onProgress) {
9284
+ const out = [];
9285
+ for (let i = 0; i < methods.length; i++) {
9286
+ const name = methods[i];
9287
+ try {
9288
+ const [help, sig] = await Promise.all([rpc.call("system.methodHelp", name).catch(() => ""), rpc.call("system.methodSignature", name).catch(() => [])]);
9289
+ out.push({
9290
+ name,
9291
+ help: typeof help === "string" ? help : "",
9292
+ signature: Array.isArray(sig) ? sig.filter(isSchema) : []
9293
+ });
9294
+ } catch (e) {
9295
+ out.push({
9296
+ name,
9297
+ help: "",
9298
+ signature: [],
9299
+ error: e instanceof Error ? e.message : String(e)
9300
+ });
9301
+ }
9302
+ onProgress?.(i + 1, methods.length);
9303
+ }
9304
+ return {
9305
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9306
+ methods: out
9307
+ };
9308
+ }
9309
+ function renderSchemaTree(schema, indent = 0) {
9310
+ const pad = " ".repeat(indent);
9311
+ const lines = [];
9312
+ let head = `${pad}- \`${schema.kind}\``;
9313
+ if (schema.nullable) head += " _(nullable)_";
9314
+ if (schema.description) head += ` — ${schema.description}`;
9315
+ const c = schema.constraints;
9316
+ if (c && Object.keys(c).length > 0) {
9317
+ const parts = Object.entries(c).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
9318
+ if (parts.length) head += ` \`{ ${parts.join(", ")} }\``;
9319
+ }
9320
+ lines.push(head);
9321
+ const ch = schema.children ?? {};
9322
+ const pushChild = (label, child) => {
9323
+ const sub = renderSchemaTree(child, indent + 1);
9324
+ if (sub.length) {
9325
+ sub[0] = `${" ".repeat(indent + 1)}- **${label}**: ${sub[0].trim().replace(/^- /, "")}`;
9326
+ lines.push(...sub);
9327
+ }
9328
+ };
9329
+ if (isSchema(ch.inner)) pushChild("inner", ch.inner);
9330
+ if (Array.isArray(ch.items)) ch.items.forEach((it, i) => {
9331
+ if (isSchema(it)) pushChild(`[${i}]`, it);
9332
+ });
9333
+ else if (isSchema(ch.items)) pushChild("items", ch.items);
9334
+ if (Array.isArray(ch.options)) ch.options.forEach((opt, i) => {
9335
+ if (isSchema(opt)) pushChild(`option[${i}]`, opt);
9336
+ });
9337
+ if (ch.properties && typeof ch.properties === "object") {
9338
+ for (const [k, v] of Object.entries(ch.properties)) if (isSchema(v)) pushChild(k, v);
9339
+ }
9340
+ if (isSchema(ch.values)) pushChild("values", ch.values);
9341
+ return lines;
9342
+ }
9343
+ function renderMarkdown(doc) {
9344
+ const lines = [];
9345
+ lines.push("# JSON-RPC API 文档");
9346
+ lines.push("");
9347
+ lines.push(`> 生成时间:${doc.generatedAt}`);
9348
+ lines.push(`> 方法总数:${doc.methods.length}`);
9349
+ lines.push("");
9350
+ lines.push("## 目录");
9351
+ lines.push("");
9352
+ for (const m of doc.methods) {
9353
+ const anchor = m.name.toLowerCase().replace(/[^\w]+/g, "-");
9354
+ lines.push(`- [\`${m.name}\`](#${anchor})`);
9355
+ }
9356
+ lines.push("");
9357
+ for (const m of doc.methods) {
9358
+ lines.push(`## \`${m.name}\``);
9359
+ lines.push("");
9360
+ if (m.error) {
9361
+ lines.push(`> ⚠️ 获取失败:${m.error}`);
9362
+ lines.push("");
9363
+ continue;
9364
+ }
9365
+ lines.push("**帮助**");
9366
+ lines.push("");
9367
+ lines.push(m.help ? m.help : "_(无帮助信息)_");
9368
+ lines.push("");
9369
+ if (m.signature.length === 0) {
9370
+ lines.push("**签名**:_(无)_");
9371
+ lines.push("");
9372
+ continue;
9373
+ }
9374
+ const [ret, ...params] = m.signature;
9375
+ lines.push("**返回类型**");
9376
+ lines.push("");
9377
+ lines.push(...renderSchemaTree(ret, 0));
9378
+ lines.push("");
9379
+ lines.push(`**参数**(${params.length} 个)`);
9380
+ lines.push("");
9381
+ if (params.length === 0) lines.push("_(无参数)_");
9382
+ else params.forEach((p, i) => {
9383
+ const sub = renderSchemaTree(p, 0);
9384
+ if (sub.length) {
9385
+ sub[0] = `- **参数 [${i}]**: ${sub[0].trim().replace(/^- /, "")}`;
9386
+ lines.push(...sub);
9387
+ }
9388
+ });
9389
+ lines.push("");
9390
+ lines.push("---");
9391
+ lines.push("");
9392
+ }
9393
+ return lines.join("\n");
9394
+ }
9395
+ /** 触发浏览器下载 */
9396
+ function downloadText(filename, text, mime = "text/plain") {
9397
+ const blob = new Blob([text], { type: `${mime};charset=utf-8` });
9398
+ const url = URL.createObjectURL(blob);
9399
+ const a = document.createElement("a");
9400
+ a.href = url;
9401
+ a.download = filename;
9402
+ document.body.appendChild(a);
9403
+ a.click();
9404
+ document.body.removeChild(a);
9405
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
9406
+ }
9407
+ //#endregion
9235
9408
  //#region webui/src/App.tsx
9236
9409
  var ENDPOINT = "";
9237
9410
  function App() {
9238
9411
  const [transport, setTransport] = (0, import_react.useState)("http");
9239
9412
  const [selected, setSelected] = (0, import_react.useState)("");
9413
+ const [generatingDocs, setGeneratingDocs] = (0, import_react.useState)(false);
9240
9414
  const rpc = (0, import_react.useMemo)(() => {
9241
9415
  return createRpc({
9242
9416
  transport,
@@ -9248,6 +9422,21 @@ function App() {
9248
9422
  }, [rpc]);
9249
9423
  const { methods, loading, error, refresh } = useMethods(rpc);
9250
9424
  const detail = useMethodDetail(rpc, selected);
9425
+ const generateDocs = (0, import_react.useCallback)(async () => {
9426
+ if (methods.length === 0) {
9427
+ alert("方法列表为空,请先刷新方法列表。");
9428
+ return;
9429
+ }
9430
+ setGeneratingDocs(true);
9431
+ try {
9432
+ const md = renderMarkdown(await collectApiDoc(rpc, methods));
9433
+ downloadText(`jsonrpc-api-doc-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}.md`, md, "text/markdown");
9434
+ } catch (e) {
9435
+ alert(`生成文档失败:${e instanceof Error ? e.message : String(e)}`);
9436
+ } finally {
9437
+ setGeneratingDocs(false);
9438
+ }
9439
+ }, [rpc, methods]);
9251
9440
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
9252
9441
  className: "app",
9253
9442
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Toolbar, {
@@ -9255,7 +9444,9 @@ function App() {
9255
9444
  onTransportChange: setTransport,
9256
9445
  onRefresh: refresh,
9257
9446
  loading,
9258
- error
9447
+ error,
9448
+ onGenerateDocs: generateDocs,
9449
+ generatingDocs
9259
9450
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
9260
9451
  className: "sidebar",
9261
9452
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Methods" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MethodTree, {
@@ -9283,4 +9474,4 @@ function App() {
9283
9474
  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, {}) }) }));
9284
9475
  //#endregion
9285
9476
 
9286
- //# sourceMappingURL=index-CQr-lnlD.js.map
9477
+ //# sourceMappingURL=index-sVQlhxjI.js.map