@yuhere/js-jsonrpc2 2.1.12 → 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 +0 -1
  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 +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 +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/htdoc/assets/{index-Cr0Jnrgb.js → index-CqI7vRAS.js} +1191 -327
  81. package/htdoc/assets/index-CqI7vRAS.js.map +1 -0
  82. package/{webui/src/styles/geek.css → htdoc/assets/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-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
@@ -1,697 +0,0 @@
1
- import { useState } from 'react';
2
- import type { SchemaDescription } from '../api/schema';
3
- import { isSchema, sampleFromSchema } from '../api/paramForm';
4
-
5
- // ─────────────────────────────────────────────────────────────
6
- // 视觉常量
7
- // ─────────────────────────────────────────────────────────────
8
-
9
- const KIND_COLORS: Record<string, string> = {
10
- string: 'var(--syn-string)',
11
- number: 'var(--syn-number)',
12
- boolean: 'var(--syn-boolean)',
13
- literal: 'var(--syn-literal)',
14
- array: 'var(--syn-container)',
15
- tuple: 'var(--syn-container)',
16
- object: 'var(--syn-object)',
17
- record: 'var(--syn-object)',
18
- union: 'var(--syn-union)',
19
- enum: 'var(--syn-union)',
20
- unknown: 'var(--syn-muted)',
21
- null: 'var(--syn-muted)',
22
- };
23
-
24
- function colorOf(kind: string): string {
25
- return KIND_COLORS[kind] ?? 'var(--fg)';
26
- }
27
-
28
- function formatConstraints(c?: Record<string, unknown>): string {
29
- if (!c) return '';
30
- return Object.entries(c)
31
- .filter(([, v]) => v !== undefined)
32
- .map(([k, v]) => `${k}=${JSON.stringify(v)}`)
33
- .join(', ');
34
- }
35
-
36
- // ─────────────────────────────────────────────────────────────
37
- // 顶层:一组参数
38
- // ─────────────────────────────────────────────────────────────
39
-
40
- export interface SchemaFormProps {
41
- /** signature[1..],每个元素对应一个客户端参数 */
42
- params: SchemaDescription[];
43
- /** 与 params 等长的值数组 */
44
- values: unknown[];
45
- onChange: (index: number, value: unknown) => void;
46
- disabled?: boolean;
47
- }
48
-
49
- export function SchemaForm({ params, values, onChange, disabled }: SchemaFormProps) {
50
- if (params.length === 0) {
51
- return <p className="hint">此方法无需参数。</p>;
52
- }
53
- return (
54
- <div className="schema-form">
55
- {params.map((schema, i) => (
56
- <SchemaField
57
- key={i}
58
- schema={schema}
59
- value={values[i]}
60
- onChange={(v) => onChange(i, v)}
61
- label={`参数 [${i}]`}
62
- depth={0}
63
- disabled={disabled}
64
- />
65
- ))}
66
- </div>
67
- );
68
- }
69
-
70
- // ─────────────────────────────────────────────────────────────
71
- // 通用节点
72
- // ─────────────────────────────────────────────────────────────
73
-
74
- interface SchemaFieldProps {
75
- schema: SchemaDescription;
76
- value: unknown;
77
- onChange: (v: unknown) => void;
78
- label?: string;
79
- depth?: number;
80
- defaultOpen?: boolean;
81
- disabled?: boolean;
82
- }
83
-
84
- function SchemaField({
85
- schema,
86
- value,
87
- onChange,
88
- label,
89
- depth = 0,
90
- defaultOpen = true,
91
- disabled,
92
- }: SchemaFieldProps) {
93
- const [open, setOpen] = useState(defaultOpen);
94
- const kind = schema.kind;
95
-
96
- // ── nullable 标记:标题 + 控制按钮同行,下面递归真实 kind ──
97
- if (schema.nullable) {
98
- const isNull = value === null || value === undefined;
99
- return (
100
- <div className="sf-node sf-row" style={{ paddingLeft: depth * 14 }}>
101
- <div className="sf-head sf-head-inline">
102
- {label !== undefined && <span className="sf-label">{label}</span>}
103
- <span className="sf-kind" style={{ color: colorOf(kind) }}>
104
- {kind}
105
- </span>
106
- <span className="sf-nullable-badge">nullable</span>
107
- {schema.description && (
108
- <span className="sf-desc">— {schema.description}</span>
109
- )}
110
- {!isNull && (
111
- <button
112
- type="button"
113
- className="tiny"
114
- disabled={disabled}
115
- onClick={() => onChange(null)}
116
- >
117
- 置 null
118
- </button>
119
- )}
120
- {isNull && (
121
- <button
122
- type="button"
123
- className="tiny"
124
- disabled={disabled}
125
- onClick={() =>
126
- onChange(
127
- sampleFromSchema({
128
- ...schema,
129
- nullable: false,
130
- }),
131
- )
132
- }
133
- >
134
- 填值
135
- </button>
136
- )}
137
- </div>
138
- <div className="sf-input-cell">
139
- {isNull ? (
140
- <span className="sf-null-indicator">null</span>
141
- ) : (
142
- <SchemaField
143
- schema={{ ...schema, nullable: false }}
144
- value={value}
145
- onChange={onChange}
146
- depth={0}
147
- disabled={disabled}
148
- defaultOpen
149
- />
150
- )}
151
- </div>
152
- </div>
153
- );
154
- }
155
-
156
- // ── 标量叶子:标题与输入框同一行 ──
157
- if (isScalarKind(kind)) {
158
- return (
159
- <div className="sf-node sf-row" style={{ paddingLeft: depth * 14 }}>
160
- <div className="sf-head sf-head-inline">
161
- {label !== undefined && <span className="sf-label">{label}</span>}
162
- <span className="sf-kind" style={{ color: colorOf(kind) }}>
163
- {kind}
164
- </span>
165
- {schema.description && (
166
- <span className="sf-desc">— {schema.description}</span>
167
- )}
168
- <Constraints schema={schema} />
169
- </div>
170
- <div className="sf-input-cell">
171
- <ScalarInput
172
- schema={schema}
173
- value={value}
174
- onChange={onChange}
175
- disabled={disabled}
176
- />
177
- </div>
178
- </div>
179
- );
180
- }
181
-
182
- // ── union:标题 + 分支选择同行 ──
183
- if (kind === 'union') {
184
- const options = Array.isArray(schema.children?.options)
185
- ? (schema.children!.options as unknown[]).filter(isSchema)
186
- : [];
187
- return (
188
- <div className="sf-node sf-row" style={{ paddingLeft: depth * 14 }}>
189
- <div className="sf-head sf-head-inline">
190
- {label !== undefined && <span className="sf-label">{label}</span>}
191
- <span className="sf-kind" style={{ color: colorOf('union') }}>
192
- union
193
- </span>
194
- {schema.description && (
195
- <span className="sf-desc">— {schema.description}</span>
196
- )}
197
- </div>
198
- <div className="sf-input-cell">
199
- <UnionField
200
- options={options}
201
- value={value}
202
- onChange={onChange}
203
- depth={0}
204
- disabled={disabled}
205
- />
206
- </div>
207
- </div>
208
- );
209
- }
210
-
211
- // ── 容器:标题行(展开/收起)+ 展开的子内容 ──
212
- if (
213
- kind === 'array' ||
214
- kind === 'tuple' ||
215
- kind === 'object' ||
216
- kind === 'record'
217
- ) {
218
- return (
219
- <div className="sf-node" style={{ paddingLeft: depth * 14 }}>
220
- <div
221
- className="sf-head sf-head-clickable"
222
- onClick={() => setOpen((o) => !o)}
223
- >
224
- <span className="sf-expander">{open ? '▾' : '▸'}</span>
225
- {label !== undefined && <span className="sf-label">{label}</span>}
226
- <span className="sf-kind" style={{ color: colorOf(kind) }}>
227
- {kind}
228
- </span>
229
- {schema.description && (
230
- <span className="sf-desc">— {schema.description}</span>
231
- )}
232
- <Constraints schema={schema} />
233
- </div>
234
- {open && (
235
- <div className="sf-children">
236
- <ContainerChildren
237
- schema={schema}
238
- value={value}
239
- onChange={onChange}
240
- depth={depth + 1}
241
- disabled={disabled}
242
- />
243
- </div>
244
- )}
245
- </div>
246
- );
247
- }
248
-
249
- // ── unknown / null / 其他兜底 ──
250
- return (
251
- <UnknownLeaf
252
- schema={schema}
253
- value={value}
254
- onChange={onChange}
255
- label={label}
256
- depth={depth}
257
- disabled={disabled}
258
- />
259
- );
260
- }
261
-
262
- // ─────────────────────────────────────────────────────────────
263
- // 标量控件
264
- // ─────────────────────────────────────────────────────────────
265
-
266
- function isScalarKind(kind: string): boolean {
267
- return (
268
- kind === 'string' ||
269
- kind === 'number' ||
270
- kind === 'boolean' ||
271
- kind === 'enum' ||
272
- kind === 'literal' ||
273
- kind === 'null'
274
- );
275
- }
276
-
277
- function ScalarInput({
278
- schema,
279
- value,
280
- onChange,
281
- disabled,
282
- }: {
283
- schema: SchemaDescription;
284
- value: unknown;
285
- onChange: (v: unknown) => void;
286
- disabled?: boolean;
287
- }) {
288
- switch (schema.kind) {
289
- case 'boolean':
290
- return (
291
- <label className="sf-scalar">
292
- <input
293
- type="checkbox"
294
- checked={Boolean(value)}
295
- disabled={disabled}
296
- onChange={(e) => onChange(e.target.checked)}
297
- />
298
- <span>{value ? 'true' : 'false'}</span>
299
- </label>
300
- );
301
- case 'number':
302
- return (
303
- <input
304
- className="sf-input"
305
- type="number"
306
- disabled={disabled}
307
- value={value == null ? '' : String(value)}
308
- onChange={(e) =>
309
- onChange(e.target.value === '' ? null : Number(e.target.value))
310
- }
311
- />
312
- );
313
- case 'enum': {
314
- const values = Array.isArray(schema.constraints?.values)
315
- ? (schema.constraints!.values as string[])
316
- : [];
317
- return (
318
- <select
319
- className="sf-input"
320
- disabled={disabled}
321
- value={value == null ? '' : String(value)}
322
- onChange={(e) => onChange(e.target.value)}
323
- >
324
- {values.map((v) => (
325
- <option key={v} value={v}>
326
- {v}
327
- </option>
328
- ))}
329
- </select>
330
- );
331
- }
332
- case 'literal':
333
- return (
334
- <div className="sf-scalar">
335
- <code className="literal-value">
336
- {JSON.stringify(schema.constraints?.value ?? null)}
337
- </code>
338
- <span className="hint">(固定值)</span>
339
- </div>
340
- );
341
- case 'null':
342
- return (
343
- <div className="sf-scalar">
344
- <code className="literal-value">null</code>
345
- <span className="hint">(固定值)</span>
346
- </div>
347
- );
348
- default:
349
- // string
350
- return (
351
- <input
352
- className="sf-input"
353
- type="text"
354
- disabled={disabled}
355
- value={value == null ? '' : String(value)}
356
- onChange={(e) => onChange(e.target.value)}
357
- />
358
- );
359
- }
360
- }
361
-
362
- // ─────────────────────────────────────────────────────────────
363
- // union 分支选择
364
- // ─────────────────────────────────────────────────────────────
365
-
366
- function UnionField({
367
- options,
368
- value,
369
- onChange,
370
- depth,
371
- disabled,
372
- }: {
373
- options: SchemaDescription[];
374
- value: unknown;
375
- onChange: (v: unknown) => void;
376
- depth: number;
377
- disabled?: boolean;
378
- }) {
379
- const [selected, setSelected] = useState(0);
380
- const active = options[selected];
381
- if (!active) {
382
- return <p className="hint">(union 无分支)</p>;
383
- }
384
- return (
385
- <div style={{ paddingLeft: depth * 14 }}>
386
- <div className="sf-union-picker">
387
- <select
388
- className="sf-input"
389
- disabled={disabled}
390
- value={selected}
391
- onChange={(e) => {
392
- const i = Number(e.target.value);
393
- setSelected(i);
394
- const next = options[i];
395
- if (next) onChange(sampleFromSchema(next));
396
- }}
397
- >
398
- {options.map((opt, i) => (
399
- <option key={i} value={i}>
400
- option[{i}] — {opt.kind}
401
- </option>
402
- ))}
403
- </select>
404
- </div>
405
- <SchemaField
406
- schema={active}
407
- value={value}
408
- onChange={onChange}
409
- depth={depth + 1}
410
- disabled={disabled}
411
- defaultOpen
412
- />
413
- </div>
414
- );
415
- }
416
-
417
- // ─────────────────────────────────────────────────────────────
418
- // 容器子节点
419
- // ─────────────────────────────────────────────────────────────
420
-
421
- function ContainerChildren({
422
- schema,
423
- value,
424
- onChange,
425
- depth,
426
- disabled,
427
- }: {
428
- schema: SchemaDescription;
429
- value: unknown;
430
- onChange: (v: unknown) => void;
431
- depth: number;
432
- disabled?: boolean;
433
- }) {
434
- const c = schema.children ?? {};
435
-
436
- // array
437
- if (schema.kind === 'array') {
438
- const itemSchema = isSchema(c.items) ? c.items : undefined;
439
- if (!itemSchema) return <p className="hint">(数组无 item schema)</p>;
440
- const arr = Array.isArray(value) ? value : [];
441
- return (
442
- <div>
443
- {arr.map((item, i) => (
444
- <div key={i} className="sf-array-item">
445
- <SchemaField
446
- schema={itemSchema}
447
- value={item}
448
- onChange={(v) => {
449
- const next = arr.slice();
450
- next[i] = v;
451
- onChange(next);
452
- }}
453
- label={`[${i}]`}
454
- depth={depth}
455
- disabled={disabled}
456
- defaultOpen={false}
457
- />
458
- <button
459
- type="button"
460
- className="tiny"
461
- disabled={disabled}
462
- onClick={() => onChange(arr.filter((_, j) => j !== i))}
463
- >
464
- 删除
465
- </button>
466
- </div>
467
- ))}
468
- <button
469
- type="button"
470
- className="tiny"
471
- disabled={disabled}
472
- onClick={() => onChange([...arr, sampleFromSchema(itemSchema)])}
473
- >
474
- + 添加项
475
- </button>
476
- </div>
477
- );
478
- }
479
-
480
- // tuple
481
- if (schema.kind === 'tuple') {
482
- const items = Array.isArray(c.items)
483
- ? (c.items as unknown[]).filter(isSchema)
484
- : [];
485
- const arr = Array.isArray(value) ? value : [];
486
- return (
487
- <div>
488
- {items.map((itemSchema, i) => (
489
- <SchemaField
490
- key={i}
491
- schema={itemSchema}
492
- value={arr[i]}
493
- onChange={(v) => {
494
- const next = arr.slice();
495
- next[i] = v;
496
- onChange(next);
497
- }}
498
- label={`[${i}]`}
499
- depth={depth}
500
- disabled={disabled}
501
- defaultOpen={false}
502
- />
503
- ))}
504
- </div>
505
- );
506
- }
507
-
508
- // object
509
- if (schema.kind === 'object') {
510
- const props = (c.properties ?? {}) as Record<string, unknown>;
511
- const obj =
512
- value && typeof value === 'object' && !Array.isArray(value)
513
- ? (value as Record<string, unknown>)
514
- : {};
515
- const strict = Boolean(schema.constraints?.strict);
516
- return (
517
- <div>
518
- {Object.entries(props).map(([key, prop]) => {
519
- if (!isSchema(prop)) return null;
520
- return (
521
- <SchemaField
522
- key={key}
523
- schema={prop}
524
- value={obj[key]}
525
- onChange={(v) => {
526
- const next = { ...obj };
527
- if (v === undefined) delete next[key];
528
- else next[key] = v;
529
- onChange(next);
530
- }}
531
- label={key}
532
- depth={depth}
533
- disabled={disabled}
534
- defaultOpen={false}
535
- />
536
- );
537
- })}
538
- {!strict && (
539
- <p
540
- className="hint"
541
- style={{ paddingLeft: depth * 14, margin: '4px 0' }}
542
- >
543
- (非 strict 模式,允许额外字段)
544
- </p>
545
- )}
546
- </div>
547
- );
548
- }
549
-
550
- // record
551
- if (schema.kind === 'record') {
552
- const valueSchema = isSchema(c.values) ? c.values : undefined;
553
- if (!valueSchema) return <p className="hint">(record 无 value schema)</p>;
554
- const obj =
555
- value && typeof value === 'object' && !Array.isArray(value)
556
- ? (value as Record<string, unknown>)
557
- : {};
558
- return (
559
- <div>
560
- {Object.entries(obj).map(([key, v]) => (
561
- <div key={key} className="sf-record-item">
562
- <SchemaField
563
- schema={valueSchema}
564
- value={v}
565
- onChange={(nv) => onChange({ ...obj, [key]: nv })}
566
- label={key}
567
- depth={depth}
568
- disabled={disabled}
569
- defaultOpen={false}
570
- />
571
- <button
572
- type="button"
573
- className="tiny"
574
- disabled={disabled}
575
- onClick={() => {
576
- const next = { ...obj };
577
- delete next[key];
578
- onChange(next);
579
- }}
580
- >
581
- 删除
582
- </button>
583
- </div>
584
- ))}
585
- <AddRecordKey
586
- onAdd={(key, v) => onChange({ ...obj, [key]: v })}
587
- sample={() => sampleFromSchema(valueSchema)}
588
- disabled={disabled}
589
- />
590
- </div>
591
- );
592
- }
593
-
594
- return null;
595
- }
596
-
597
- function AddRecordKey({
598
- onAdd,
599
- sample,
600
- disabled,
601
- }: {
602
- onAdd: (key: string, value: unknown) => void;
603
- sample: () => unknown;
604
- disabled?: boolean;
605
- }) {
606
- const [key, setKey] = useState('');
607
- return (
608
- <div className="sf-record-add">
609
- <input
610
- className="sf-input"
611
- placeholder="新键名"
612
- value={key}
613
- onChange={(e) => setKey(e.target.value)}
614
- disabled={disabled}
615
- />
616
- <button
617
- type="button"
618
- className="tiny"
619
- disabled={disabled || !key}
620
- onClick={() => {
621
- onAdd(key, sample());
622
- setKey('');
623
- }}
624
- >
625
- + 添加键
626
- </button>
627
- </div>
628
- );
629
- }
630
-
631
- // ─────────────────────────────────────────────────────────────
632
- // unknown / null:JSON textarea(标题 + textarea 同一行)
633
- // ─────────────────────────────────────────────────────────────
634
-
635
- function UnknownLeaf({
636
- schema,
637
- value,
638
- onChange,
639
- label,
640
- depth,
641
- disabled,
642
- }: {
643
- schema: SchemaDescription;
644
- value: unknown;
645
- onChange: (v: unknown) => void;
646
- label?: string;
647
- depth: number;
648
- disabled?: boolean;
649
- }) {
650
- const [text, setText] = useState(() => JSON.stringify(value ?? null, null, 2));
651
- const [error, setError] = useState<string | null>(null);
652
-
653
- const commit = (next: string) => {
654
- setText(next);
655
- try {
656
- const parsed = JSON.parse(next);
657
- setError(null);
658
- onChange(parsed);
659
- } catch (e) {
660
- setError(e instanceof Error ? e.message : String(e));
661
- }
662
- };
663
-
664
- return (
665
- <div className="sf-node sf-row" style={{ paddingLeft: depth * 14 }}>
666
- <div className="sf-head sf-head-inline">
667
- {label !== undefined && <span className="sf-label">{label}</span>}
668
- <span className="sf-kind" style={{ color: colorOf(schema.kind) }}>
669
- {schema.kind}
670
- </span>
671
- {schema.description && (
672
- <span className="sf-desc">— {schema.description}</span>
673
- )}
674
- </div>
675
- <div className="sf-input-cell">
676
- <textarea
677
- className="sf-textarea"
678
- rows={3}
679
- spellCheck={false}
680
- value={text}
681
- disabled={disabled}
682
- onChange={(e) => commit(e.target.value)}
683
- />
684
- {error && <span className="error small">JSON 错误:{error}</span>}
685
- </div>
686
- </div>
687
- );
688
- }
689
-
690
- // ─────────────────────────────────────────────────────────────
691
- // 小工具
692
- // ─────────────────────────────────────────────────────────────
693
-
694
- function Constraints({ schema }: { schema: SchemaDescription }) {
695
- const text = formatConstraints(schema.constraints);
696
- return text ? <span className="sf-constraints">{`{ ${text} }`}</span> : null;
697
- }