@yuhere/js-jsonrpc2 2.1.11 → 2.1.13

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 +89 -3
  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 +12 -7
  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 +0 -1
  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 +0 -1
  42. package/dist/schema/json/primitives.js +0 -1
  43. package/dist/schema/z/protocol.d.ts +0 -1
  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 +8 -6
  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 +7 -5
  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 +7 -5
  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/htdoc/assets/{index-Cr0Jnrgb.js → index-CqI7vRAS.js} +1191 -327
  81. package/htdoc/assets/index-CqI7vRAS.js.map +1 -0
  82. package/htdoc/assets/{index-Ck-39Igh.css → index-Rq1yIwo2.css} +58 -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-Cr0Jnrgb.js.map +0 -1
  154. package/webui/index.html +0 -12
  155. package/webui/src/App.tsx +0 -164
  156. package/webui/src/api/docGen.ts +0 -188
  157. package/webui/src/api/glob.ts +0 -90
  158. package/webui/src/api/httpTransport.ts +0 -148
  159. package/webui/src/api/metrics.ts +0 -135
  160. package/webui/src/api/paramForm.ts +0 -80
  161. package/webui/src/api/rpc.ts +0 -57
  162. package/webui/src/api/schema.ts +0 -12
  163. package/webui/src/api/types.ts +0 -16
  164. package/webui/src/components/MethodDetail.tsx +0 -43
  165. package/webui/src/components/MethodTree.tsx +0 -118
  166. package/webui/src/components/MetricsPanel.md +0 -126
  167. package/webui/src/components/MetricsPanel.tsx +0 -290
  168. package/webui/src/components/SchemaForm.tsx +0 -697
  169. package/webui/src/components/SchemaView.tsx +0 -151
  170. package/webui/src/components/TestPanel.tsx +0 -322
  171. package/webui/src/components/Toolbar.tsx +0 -82
  172. package/webui/src/hooks/useMethodDetail.ts +0 -61
  173. package/webui/src/hooks/useMethods.ts +0 -27
  174. package/webui/src/hooks/useMetrics.ts +0 -91
  175. package/webui/src/main.tsx +0 -13
  176. package/webui/src/styles/geek.css +0 -963
  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
@@ -1,151 +0,0 @@
1
- import { useState } from 'react';
2
- import type { SchemaDescription } from '../api/schema';
3
-
4
- interface SchemaViewProps {
5
- schema: SchemaDescription;
6
- /** 顶层是否默认展开 */
7
- defaultOpen?: boolean;
8
- /** 缩进深度(内部递归使用) */
9
- depth?: number;
10
- }
11
-
12
- const KIND_COLORS: Record<string, string> = {
13
- string: 'var(--syn-string)',
14
- number: 'var(--syn-number)',
15
- boolean: 'var(--syn-boolean)',
16
- literal: 'var(--syn-literal)',
17
- array: 'var(--syn-container)',
18
- tuple: 'var(--syn-container)',
19
- object: 'var(--syn-object)',
20
- record: 'var(--syn-object)',
21
- union: 'var(--syn-union)',
22
- enum: 'var(--syn-union)',
23
- unknown: 'var(--syn-muted)',
24
- null: 'var(--syn-muted)',
25
- stream: 'var(--syn-container)',
26
- };
27
-
28
- function colorOf(kind: string): string {
29
- return KIND_COLORS[kind] ?? 'var(--fg)';
30
- }
31
-
32
- /** 把 constraints 渲染成紧凑的一行,如:min=1, max=10 */
33
- function formatConstraints(constraints?: Record<string, unknown>): string {
34
- if (!constraints) return '';
35
- const entries = Object.entries(constraints).filter(([, v]) => v !== undefined);
36
- if (entries.length === 0) return '';
37
- return entries
38
- .map(([k, v]) => `${k}=${typeof v === 'string' ? JSON.stringify(v) : JSON.stringify(v)}`)
39
- .join(', ');
40
- }
41
-
42
- interface NodeProps {
43
- schema: SchemaDescription;
44
- depth: number;
45
- defaultOpen: boolean;
46
- label?: string;
47
- }
48
-
49
- function SchemaNode({ schema, depth, defaultOpen, label }: NodeProps) {
50
- const [open, setOpen] = useState(defaultOpen);
51
- const hasChildren = schema.children && Object.keys(schema.children).length > 0;
52
- const constraintsText = formatConstraints(schema.constraints);
53
-
54
- return (
55
- <div className="schema-node" style={{ paddingLeft: depth * 14 }}>
56
- <div className="schema-head" onClick={() => hasChildren && setOpen((o) => !o)}>
57
- {hasChildren ? (
58
- <span className="schema-expander">{open ? '▾' : '▸'}</span>
59
- ) : (
60
- <span className="schema-expander" />
61
- )}
62
- {label !== undefined && <span className="schema-label">{label}:</span>}
63
- <span className="schema-kind" style={{ color: colorOf(schema.kind) }}>
64
- {schema.kind}
65
- </span>
66
- {schema.kind === 'stream' && <span className="schema-nullable">&lt;stream&gt;</span>}
67
- {schema.nullable && <span className="schema-nullable-badge">nullable</span>}
68
- {constraintsText && <span className="schema-constraints">{`{ ${constraintsText} }`}</span>}
69
- {schema.description && (
70
- <span className="schema-description" title={schema.description}>
71
- {' '}
72
- // {schema.description}
73
- </span>
74
- )}
75
- </div>
76
- {open && hasChildren && (
77
- <div className="schema-children">{renderChildren(schema, depth + 1)}</div>
78
- )}
79
- </div>
80
- );
81
- }
82
-
83
- function renderChildren(schema: SchemaDescription, depth: number): React.ReactNode {
84
- const c = schema.children ?? {};
85
- const out: React.ReactNode[] = [];
86
-
87
- // stream 包装器
88
- if (c.inner && isSchemaDescription(c.inner)) {
89
- out.push(<SchemaNode key="inner" schema={c.inner} depth={depth} defaultOpen />);
90
- }
91
-
92
- // array / tuple
93
- if (Array.isArray(c.items)) {
94
- (c.items as unknown[]).forEach((item, i) => {
95
- if (isSchemaDescription(item)) {
96
- out.push(
97
- <SchemaNode
98
- key={`item-${i}`}
99
- schema={item}
100
- depth={depth}
101
- defaultOpen
102
- label={String(i)}
103
- />
104
- );
105
- }
106
- });
107
- } else if (c.items && isSchemaDescription(c.items)) {
108
- out.push(<SchemaNode key="items" schema={c.items} depth={depth} defaultOpen label="items" />);
109
- }
110
-
111
- // union
112
- if (Array.isArray(c.options)) {
113
- (c.options as unknown[]).forEach((opt, i) => {
114
- if (isSchemaDescription(opt)) {
115
- out.push(
116
- <SchemaNode
117
- key={`opt-${i}`}
118
- schema={opt}
119
- depth={depth}
120
- defaultOpen={false}
121
- label={`option[${i}]`}
122
- />
123
- );
124
- }
125
- });
126
- }
127
-
128
- // object
129
- if (c.properties && typeof c.properties === 'object' && !Array.isArray(c.properties)) {
130
- for (const [key, prop] of Object.entries(c.properties)) {
131
- if (isSchemaDescription(prop)) {
132
- out.push(<SchemaNode key={`prop-${key}`} schema={prop} depth={depth} defaultOpen label={key} />);
133
- }
134
- }
135
- }
136
-
137
- // record
138
- if (c.values && isSchemaDescription(c.values)) {
139
- out.push(<SchemaNode key="values" schema={c.values} depth={depth} defaultOpen label="values" />);
140
- }
141
-
142
- return out;
143
- }
144
-
145
- function isSchemaDescription(v: unknown): v is SchemaDescription {
146
- return !!v && typeof v === 'object' && typeof (v as SchemaDescription).kind === 'string';
147
- }
148
-
149
- export function SchemaView({ schema, defaultOpen = true, depth = 0 }: SchemaViewProps) {
150
- return <SchemaNode schema={schema} depth={depth} defaultOpen={defaultOpen} />;
151
- }
@@ -1,322 +0,0 @@
1
- import { useEffect, useMemo, useRef, useState } from 'react';
2
- import { RPCError, isStreamResult, type RpcClient, type TransportKind } from '../api/rpc';
3
- import type { MethodDetail } from '../hooks/useMethodDetail';
4
- import { SchemaForm } from './SchemaForm';
5
- import { sampleFromSchema } from '../api/paramForm';
6
- import { absoluteEndpoint } from '../api/httpTransport';
7
-
8
- interface TestPanelProps {
9
- method: string;
10
- detail: MethodDetail;
11
- rpc: RpcClient;
12
- transport: TransportKind;
13
- endpoint: string;
14
- }
15
-
16
- type CopyKind = 'curl' | 'fetch';
17
-
18
- const COPY_OPTIONS: { value: CopyKind; label: string }[] = [
19
- { value: 'curl', label: '复制 curl' },
20
- { value: 'fetch', label: '复制 fetch' },
21
- ];
22
-
23
- /** 格式化耗时:<1s 用 ms,否则用 s */
24
- function formatElapsed(ms: number): string {
25
- if (ms < 1000) return `${ms.toFixed(0)} ms`;
26
- return `${(ms / 1000).toFixed(2)} s`;
27
- }
28
-
29
- export function TestPanel({
30
- method,
31
- detail,
32
- rpc,
33
- transport,
34
- endpoint,
35
- }: TestPanelProps) {
36
- const paramSchemas = useMemo(() => detail.signature.slice(1), [detail.signature]);
37
- const isStreamReturn = detail.signature[0]?.kind === 'stream';
38
-
39
- const [values, setValues] = useState<unknown[]>([]);
40
- const [output, setOutput] = useState('');
41
- const [elapsed, setElapsed] = useState<{ total: number; firstFrame?: number } | null>(null);
42
- const [running, setRunning] = useState(false);
43
- const [copyKind, setCopyKind] = useState<CopyKind>('curl');
44
- const [copied, setCopied] = useState(false);
45
- const [copyError, setCopyError] = useState<string | null>(null);
46
- const [menuOpen, setMenuOpen] = useState(false);
47
- const menuRef = useRef<HTMLDivElement>(null);
48
- const outRef = useRef<HTMLPreElement>(null);
49
-
50
- useEffect(() => {
51
- if (!method) return;
52
- setValues(paramSchemas.map(sampleFromSchema));
53
- setOutput('');
54
- setElapsed(null);
55
- }, [method, paramSchemas]);
56
-
57
- useEffect(() => {
58
- setCopied(false);
59
- setCopyError(null);
60
- setMenuOpen(false);
61
- }, [method]);
62
-
63
- // 点击外部关闭菜单
64
- useEffect(() => {
65
- if (!menuOpen) return;
66
- const onDocClick = (e: MouseEvent) => {
67
- if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
68
- setMenuOpen(false);
69
- }
70
- };
71
- document.addEventListener('mousedown', onDocClick);
72
- return () => document.removeEventListener('mousedown', onDocClick);
73
- }, [menuOpen]);
74
-
75
- const setValue = (i: number, v: unknown) => {
76
- setValues((prev) => {
77
- const next = prev.slice();
78
- next[i] = v;
79
- return next;
80
- });
81
- };
82
-
83
- const execute = async () => {
84
- if (!method) return;
85
- setRunning(true);
86
- setOutput('');
87
- setElapsed(null);
88
-
89
- const startedAt = performance.now();
90
- try {
91
- const result = await rpc.call(method, ...values);
92
-
93
- // 首帧/响应到达耗时
94
- const firstFrameAt = performance.now() - startedAt;
95
-
96
- if (isStreamResult(result)) {
97
- let idx = 0;
98
- const lines: string[] = [];
99
-
100
- // 每帧记录到达时间
101
- for await (const value of result) {
102
- const now = performance.now() - startedAt;
103
- lines.push(
104
- `[${idx}] (+${formatElapsed(now)}) ${JSON.stringify(value, null, 2)}`,
105
- );
106
- idx++;
107
- setOutput(lines.join('\n'));
108
- }
109
-
110
- const totalAt = performance.now() - startedAt;
111
- if (idx === 0) {
112
- lines.push(`(流已结束,无数据,总耗时 ${formatElapsed(totalAt)})`);
113
- } else {
114
- lines.push(`(流已结束,共 ${idx} 帧,总耗时 ${formatElapsed(totalAt)})`);
115
- }
116
- setOutput(lines.join('\n'));
117
- setElapsed({ total: totalAt, firstFrame: firstFrameAt });
118
- } else {
119
- const totalAt = performance.now() - startedAt;
120
- setOutput(JSON.stringify(result, null, 2));
121
- setElapsed({ total: totalAt });
122
- }
123
- } catch (e) {
124
- const totalAt = performance.now() - startedAt;
125
- setOutput(formatError(e));
126
- setElapsed({ total: totalAt });
127
- } finally {
128
- setRunning(false);
129
- requestAnimationFrame(() =>
130
- outRef.current?.scrollIntoView({ block: 'nearest' }),
131
- );
132
- }
133
- };
134
-
135
- const copyAs = async (kind: CopyKind) => {
136
- if (!method) return;
137
- setCopyError(null);
138
- let code: string;
139
- try {
140
- const body = {
141
- jsonrpc: '2.0' as const,
142
- method,
143
- params: values,
144
- id: 1,
145
- };
146
- const url = absoluteEndpoint(endpoint);
147
- code = kind === 'curl' ? buildCurl(url, body) : buildFetch(url, body);
148
- } catch (e) {
149
- setCopyError(e instanceof Error ? e.message : String(e));
150
- return;
151
- }
152
- try {
153
- await navigator.clipboard.writeText(code);
154
- setCopied(true);
155
- setTimeout(() => setCopied(false), 2000);
156
- } catch (e) {
157
- setCopyError(
158
- `复制失败:${e instanceof Error ? e.message : String(e)}`,
159
- );
160
- }
161
- };
162
-
163
- const canCopy = transport === 'http';
164
- const currentLabel =
165
- COPY_OPTIONS.find((o) => o.value === copyKind)?.label ?? '复制';
166
-
167
- return (
168
- <section className="test">
169
- <h3>
170
- 参数{' '}
171
- <span className="hint">
172
- ({paramSchemas.length} 个,按签名自动生成)
173
- </span>
174
- </h3>
175
-
176
- <SchemaForm
177
- params={paramSchemas}
178
- values={values}
179
- onChange={setValue}
180
- disabled={running}
181
- />
182
-
183
- <div className="actions">
184
- <button onClick={execute} disabled={!method || running}>
185
- {running ? (isStreamReturn ? '接收中…' : '执行中…') : (isStreamReturn ? '执行并接收流 ▶' : '执行 ▶')}
186
- </button>
187
-
188
- <div className="split-button" ref={menuRef}>
189
- <button
190
- type="button"
191
- className="split-button-main"
192
- onClick={() => void copyAs(copyKind)}
193
- disabled={!method || !canCopy}
194
- title={canCopy ? '复制当前调用的等价代码' : '仅 HTTP 传输可生成代码'}
195
- >
196
- {copied ? '已复制 ✓' : currentLabel}
197
- </button>
198
- <button
199
- type="button"
200
- className="split-button-caret"
201
- onClick={() => setMenuOpen((o) => !o)}
202
- disabled={!method || !canCopy}
203
- aria-haspopup="menu"
204
- aria-expanded={menuOpen}
205
- title="选择复制格式"
206
- >
207
- ▾
208
- </button>
209
- {menuOpen && (
210
- <ul className="split-button-menu" role="menu">
211
- {COPY_OPTIONS.map((opt) => (
212
- <li
213
- key={opt.value}
214
- role="menuitem"
215
- onClick={() => {
216
- setCopyKind(opt.value);
217
- setMenuOpen(false);
218
- void copyAs(opt.value);
219
- }}
220
- >
221
- {opt.label}
222
- {copyKind === opt.value && (
223
- <span className="check">✓</span>
224
- )}
225
- </li>
226
- ))}
227
- </ul>
228
- )}
229
- </div>
230
-
231
- {copyError && <span className="error small">{copyError}</span>}
232
-
233
- <button
234
- onClick={() => setValues(paramSchemas.map(sampleFromSchema))}
235
- disabled={!method}
236
- >
237
- 重置为示例
238
- </button>
239
- <button
240
- onClick={() => {
241
- setOutput('');
242
- setElapsed(null);
243
- }}
244
- disabled={!output && !elapsed}
245
- >
246
- 清空输出
247
- </button>
248
- </div>
249
-
250
- <h3>
251
- 结果{isStreamReturn && <span className="hint">(流式)</span>}
252
- {elapsed && (
253
- <span className="elapsed-badge">
254
- {' '}⏱ {formatElapsed(elapsed.total)}
255
- {elapsed.firstFrame !== undefined && (
256
- <span className="hint">
257
- {' '}· 首帧 {formatElapsed(elapsed.firstFrame)}
258
- </span>
259
- )}
260
- </span>
261
- )}
262
- </h3>
263
- <pre className="output" ref={outRef}>
264
- {output || '(尚未执行)'}
265
- </pre>
266
- </section>
267
- );
268
- }
269
-
270
- // ─────────────────────────────────────────────────────────────
271
- // 代码片段生成
272
- // ─────────────────────────────────────────────────────────────
273
-
274
- interface RpcBody {
275
- jsonrpc: '2.0';
276
- method: string;
277
- params: unknown[];
278
- id: number;
279
- }
280
-
281
- function buildCurl(url: string, body: RpcBody): string {
282
- const bodyText = JSON.stringify(body, null, 2);
283
- const quotedBody = `'${bodyText.replace(/'/g, `'\\''`)}'`;
284
- return [
285
- `curl -X POST '${url}'`,
286
- ` -H 'Content-Type: application/json'`,
287
- ` -d ${quotedBody}`,
288
- ].join(' \\\n');
289
- }
290
-
291
- function buildFetch(url: string, body: RpcBody): string {
292
- const bodyText = JSON.stringify(body, null, 2);
293
- const indented = bodyText
294
- .split('\n')
295
- .map((line, i) => (i === 0 ? line : ' ' + line))
296
- .join('\n');
297
- return [
298
- 'await fetch(' + JSON.stringify(url) + ', {',
299
- ` method: 'POST',`,
300
- ` headers: { 'Content-Type': 'application/json' },`,
301
- ' body: JSON.stringify(',
302
- ' ' + indented.split('\n').join('\n '),
303
- ' ),',
304
- '});',
305
- ].join('\n');
306
- }
307
-
308
- function formatError(e: unknown): string {
309
- if (e instanceof RPCError) {
310
- const { code, message, data } = e.payload;
311
- const lines = [`Error ${code}: ${message}`];
312
- if (data !== undefined) {
313
- lines.push('');
314
- lines.push('data:');
315
- lines.push(
316
- typeof data === 'string' ? data : JSON.stringify(data, null, 2),
317
- );
318
- }
319
- return lines.join('\n');
320
- }
321
- return `Error: ${e instanceof Error ? e.message : String(e)}`;
322
- }
@@ -1,82 +0,0 @@
1
- import type { TransportKind } from '../api/rpc';
2
- import { useTheme, type ThemeMode } from '../theme/ThemeProvider';
3
-
4
- interface ToolbarProps {
5
- transport: TransportKind;
6
- onTransportChange: (t: TransportKind) => void;
7
- onRefresh: () => void;
8
- loading: boolean;
9
- error: string | null;
10
- /** 新增:生成文档 */
11
- onGenerateDocs: () => void;
12
- generatingDocs: boolean;
13
- }
14
-
15
- const THEME_OPTIONS: { value: ThemeMode; label: string }[] = [
16
- { value: 'system', label: '跟随系统' },
17
- { value: 'light', label: '亮' },
18
- { value: 'dark', label: '暗' },
19
- ];
20
-
21
- export function Toolbar({
22
- transport,
23
- onTransportChange,
24
- onRefresh,
25
- loading,
26
- error,
27
- onGenerateDocs,
28
- generatingDocs,
29
- }: ToolbarProps) {
30
- const { mode, setMode } = useTheme();
31
-
32
- return (
33
- <header className="toolbar">
34
- <h1>JSON-RPC 2.0</h1>
35
-
36
- <div className="segmented" role="group" aria-label="传输方式">
37
- <button
38
- type="button"
39
- className={transport === 'http' ? 'active' : ''}
40
- onClick={() => onTransportChange('http')}
41
- >
42
- HTTP
43
- </button>
44
- <button
45
- type="button"
46
- className={transport === 'ws' ? 'active' : ''}
47
- onClick={() => onTransportChange('ws')}
48
- >
49
- WebSocket
50
- </button>
51
- </div>
52
-
53
- <button onClick={onRefresh} disabled={loading}>
54
- {loading ? '刷新中…' : '刷新方法列表'}
55
- </button>
56
-
57
- <button
58
- onClick={onGenerateDocs}
59
- disabled={generatingDocs || loading || loading /* methods 空时由 App 拦截 */}
60
- title="拉取所有方法的帮助与签名,生成 Markdown 文档并下载"
61
- >
62
- {generatingDocs ? '生成中…' : '生成文档 ⬇'}
63
- </button>
64
-
65
- {error && <span className="error">{error}</span>}
66
-
67
- <label className="theme-select" title="主题">
68
- <span className="theme-select-label">主题</span>
69
- <select
70
- value={mode}
71
- onChange={(e) => setMode(e.target.value as ThemeMode)}
72
- >
73
- {THEME_OPTIONS.map((opt) => (
74
- <option key={opt.value} value={opt.value}>
75
- {opt.label}
76
- </option>
77
- ))}
78
- </select>
79
- </label>
80
- </header>
81
- );
82
- }
@@ -1,61 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- import type { RpcClient } from '../api/rpc';
3
- import type { SchemaDescription } from '../api/schema';
4
-
5
- export interface MethodDetail {
6
- help: string;
7
- /** system.methodSignature 返回的 Schema 列表(每项对应一个参数) */
8
- signature: SchemaDescription[];
9
- error: string | null;
10
- loading: boolean;
11
- }
12
-
13
- const EMPTY: MethodDetail = { help: '', signature: [], error: null, loading: false };
14
-
15
- function isSchemaDescription(v: unknown): v is SchemaDescription {
16
- return !!v && typeof v === 'object' && typeof (v as SchemaDescription).kind === 'string';
17
- }
18
-
19
- export function useMethodDetail(rpc: RpcClient, method: string): MethodDetail {
20
- const [detail, setDetail] = useState<MethodDetail>(EMPTY);
21
-
22
- useEffect(() => {
23
- if (!method) {
24
- setDetail(EMPTY);
25
- return;
26
- }
27
- let cancelled = false;
28
- setDetail({ help: '', signature: [], error: null, loading: true });
29
-
30
- (async () => {
31
- try {
32
- const [help, signature] = await Promise.all([
33
- rpc.call<string>('system.methodHelp', method).catch(() => ''),
34
- rpc.call<unknown[]>('system.methodSignature', method).catch(() => []),
35
- ]);
36
- if (cancelled) return;
37
- const sigArr = Array.isArray(signature) ? signature.filter(isSchemaDescription) : [];
38
- setDetail({
39
- help: typeof help === 'string' ? help : '',
40
- signature: sigArr,
41
- error: null,
42
- loading: false,
43
- });
44
- } catch (e) {
45
- if (cancelled) return;
46
- setDetail({
47
- help: '',
48
- signature: [],
49
- error: e instanceof Error ? e.message : String(e),
50
- loading: false,
51
- });
52
- }
53
- })();
54
-
55
- return () => {
56
- cancelled = true;
57
- };
58
- }, [rpc, method]);
59
-
60
- return detail;
61
- }
@@ -1,27 +0,0 @@
1
- import { useCallback, useEffect, useState } from 'react';
2
- import type { RpcClient } from '../api/rpc';
3
-
4
- export function useMethods(rpc: RpcClient) {
5
- const [methods, setMethods] = useState<string[]>([]);
6
- const [loading, setLoading] = useState(false);
7
- const [error, setError] = useState<string | null>(null);
8
-
9
- const refresh = useCallback(async () => {
10
- setLoading(true);
11
- setError(null);
12
- try {
13
- const list = await rpc.call<string[]>('system.listMethods');
14
- setMethods(Array.isArray(list) ? [...list].sort() : []);
15
- } catch (e) {
16
- setError(e instanceof Error ? e.message : String(e));
17
- } finally {
18
- setLoading(false);
19
- }
20
- }, [rpc]);
21
-
22
- useEffect(() => {
23
- void refresh();
24
- }, [refresh]);
25
-
26
- return { methods, loading, error, refresh };
27
- }