@zhin.js/console 1.0.21 → 1.0.22

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 (56) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +4 -4
  3. package/client/components.json +17 -0
  4. package/client/index.html +1 -1
  5. package/client/src/components/PluginConfigForm/BasicFieldRenderers.tsx +89 -180
  6. package/client/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +97 -200
  7. package/client/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +31 -70
  8. package/client/src/components/PluginConfigForm/FieldRenderer.tsx +27 -77
  9. package/client/src/components/PluginConfigForm/NestedFieldRenderer.tsx +33 -53
  10. package/client/src/components/PluginConfigForm/index.tsx +71 -173
  11. package/client/src/components/ui/accordion.tsx +54 -0
  12. package/client/src/components/ui/alert.tsx +62 -0
  13. package/client/src/components/ui/avatar.tsx +41 -0
  14. package/client/src/components/ui/badge.tsx +32 -0
  15. package/client/src/components/ui/button.tsx +50 -0
  16. package/client/src/components/ui/card.tsx +50 -0
  17. package/client/src/components/ui/checkbox.tsx +25 -0
  18. package/client/src/components/ui/dialog.tsx +87 -0
  19. package/client/src/components/ui/dropdown-menu.tsx +97 -0
  20. package/client/src/components/ui/input.tsx +21 -0
  21. package/client/src/components/ui/scroll-area.tsx +43 -0
  22. package/client/src/components/ui/select.tsx +127 -0
  23. package/client/src/components/ui/separator.tsx +23 -0
  24. package/client/src/components/ui/skeleton.tsx +12 -0
  25. package/client/src/components/ui/switch.tsx +26 -0
  26. package/client/src/components/ui/tabs.tsx +52 -0
  27. package/client/src/components/ui/textarea.tsx +20 -0
  28. package/client/src/components/ui/tooltip.tsx +27 -0
  29. package/client/src/layouts/dashboard.tsx +91 -221
  30. package/client/src/main.tsx +38 -42
  31. package/client/src/pages/dashboard-bots.tsx +91 -137
  32. package/client/src/pages/dashboard-home.tsx +133 -204
  33. package/client/src/pages/dashboard-logs.tsx +125 -196
  34. package/client/src/pages/dashboard-plugin-detail.tsx +261 -329
  35. package/client/src/pages/dashboard-plugins.tsx +108 -105
  36. package/client/src/style.css +156 -865
  37. package/client/src/theme/index.ts +60 -35
  38. package/client/tailwind.config.js +78 -69
  39. package/dist/client.js +1 -1
  40. package/dist/cva.js +47 -0
  41. package/dist/index.html +1 -1
  42. package/dist/index.js +6 -6
  43. package/dist/react-router.js +7121 -5585
  44. package/dist/react.js +192 -149
  45. package/dist/style.css +2 -2
  46. package/lib/bin.js +2 -2
  47. package/lib/build.js +2 -2
  48. package/lib/index.d.ts +0 -3
  49. package/lib/index.js +160 -205
  50. package/lib/transform.d.ts +26 -0
  51. package/lib/transform.js +78 -0
  52. package/lib/websocket.d.ts +0 -1
  53. package/package.json +8 -7
  54. package/dist/radix-ui-themes.js +0 -9305
  55. package/lib/dev.d.ts +0 -18
  56. package/lib/dev.js +0 -87
@@ -1,176 +1,108 @@
1
1
  /**
2
- * 集合类型字段渲染器
3
- * 处理: list, tuple, object, dict
2
+ * Collection type field renderers
4
3
  */
5
4
 
6
- import { Flex, Box, Text, TextArea, Button, Badge, Card, Separator } from '@radix-ui/themes'
7
5
  import { List, Trash2, Plus, Package, Code, Info } from 'lucide-react'
8
6
  import type { FieldRendererProps, SchemaField } from './types.js'
7
+ import { Textarea } from '../ui/textarea'
8
+ import { Badge } from '../ui/badge'
9
+ import { Button } from '../ui/button'
10
+ import { Card } from '../ui/card'
11
+ import { Separator } from '../ui/separator'
9
12
 
10
13
  interface CollectionFieldProps extends FieldRendererProps {
11
- renderNestedField: (
12
- fieldName: string,
13
- field: SchemaField,
14
- value: any,
15
- onChange: (val: any) => void
16
- ) => React.ReactElement
14
+ renderNestedField: (fieldName: string, field: SchemaField, value: any, onChange: (val: any) => void) => React.ReactElement
17
15
  }
18
16
 
19
- export function ListFieldRenderer({
20
- fieldName,
21
- field,
22
- value,
23
- onChange,
24
- onArrayItemChange,
25
- renderNestedField
17
+ export function ListFieldRenderer({
18
+ fieldName, field, value, onChange, onArrayItemChange, renderNestedField
26
19
  }: CollectionFieldProps) {
27
20
  const arrayValue = Array.isArray(value) ? value : []
28
21
  const innerField = field.inner || field.items
29
-
30
- // 简单类型 - 使用多行文本 - 优化样式
22
+
31
23
  if (innerField && ['string', 'number'].includes(innerField.type)) {
32
24
  return (
33
- <div className="p-3 rounded-lg bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800">
34
- <Flex direction="column" gap="2">
35
- <Flex align="center" gap="2">
36
- <List className="w-4 h-4 text-green-600 dark:text-green-400" />
37
- <Text size="1" weight="bold" className="text-green-700 dark:text-green-300">
38
- 列表输入 (每行一个值)
39
- </Text>
40
- </Flex>
41
- <TextArea
42
- size="2"
43
- value={arrayValue.join('\n')}
44
- onChange={(e) => {
45
- const lines = e.target.value.split('\n').filter(Boolean)
46
- const parsed = innerField.type === 'number'
47
- ? lines.map(l => parseFloat(l) || 0)
48
- : lines
49
- onChange(parsed)
50
- }}
51
- placeholder={`每行一个值\n${field.description || ''}`}
52
- rows={4}
53
- className="font-mono text-sm bg-white dark:bg-gray-950 hover:border-green-500 dark:hover:border-green-400 transition-colors"
54
- />
55
- </Flex>
25
+ <div className="p-3 rounded-lg bg-muted/50 border space-y-2">
26
+ <p className="flex items-center gap-1 text-xs font-semibold text-muted-foreground">
27
+ <List className="w-3 h-3" /> 列表输入 (每行一个值)
28
+ </p>
29
+ <Textarea
30
+ value={arrayValue.join('\n')}
31
+ onChange={(e) => {
32
+ const lines = e.target.value.split('\n').filter(Boolean)
33
+ onChange(innerField.type === 'number' ? lines.map(l => parseFloat(l) || 0) : lines)
34
+ }}
35
+ placeholder={`每行一个值\n${field.description || ''}`}
36
+ rows={4} className="font-mono text-sm"
37
+ />
56
38
  </div>
57
39
  )
58
40
  }
59
-
60
- // 复杂类型 - Card 列表 - 优化样式
41
+
61
42
  return (
62
- <Flex direction="column" gap="2">
63
- <div className="space-y-2">
64
- {arrayValue.map((item, index) => (
65
- <Card
66
- key={index}
67
- size="1"
68
- variant="surface"
69
- className="border border-gray-200 dark:border-gray-800 hover:border-blue-300 dark:hover:border-blue-700 transition-colors group"
70
- >
71
- <Flex direction="column" gap="2" className="p-3">
72
- <Flex justify="between" align="center">
73
- <Flex align="center" gap="2">
74
- <Badge size="1" variant="soft" color="blue" className="font-mono">
75
- {index + 1}
76
- </Badge>
77
- </Flex>
78
- <Button
79
- size="1"
80
- variant="ghost"
81
- color="red"
82
- onClick={() => {
83
- const newArr = arrayValue.filter((_, i) => i !== index)
84
- onChange(newArr)
85
- }}
86
- className="opacity-0 group-hover:opacity-100 transition-opacity"
87
- >
88
- <Trash2 className="w-3 h-3" />
89
- </Button>
90
- </Flex>
91
- <div className="pl-2 border-l-2 border-blue-200 dark:border-blue-800">
92
- {innerField && renderNestedField(`${fieldName}[${index}]`, innerField, item, (val) => {
93
- onArrayItemChange?.(fieldName, index, val)
94
- })}
95
- </div>
96
- </Flex>
97
- </Card>
98
- ))}
99
- </div>
100
- <Button
101
- size="2"
102
- variant="soft"
103
- onClick={() => {
104
- const newItem = innerField?.default || (innerField?.type === 'object' ? {} : '')
105
- onChange([...arrayValue, newItem])
106
- }}
107
- className="w-full hover:bg-blue-100 dark:hover:bg-blue-900/30 border-2 border-dashed border-blue-300 dark:border-blue-700 hover:border-blue-500 dark:hover:border-blue-500 transition-colors"
43
+ <div className="space-y-2">
44
+ {arrayValue.map((item, index) => (
45
+ <Card key={index} className="group">
46
+ <div className="p-3 space-y-2">
47
+ <div className="flex justify-between items-center">
48
+ <Badge variant="secondary" className="font-mono">{index + 1}</Badge>
49
+ <Button variant="ghost" size="sm"
50
+ className="opacity-0 group-hover:opacity-100 transition-opacity text-destructive"
51
+ onClick={() => onChange(arrayValue.filter((_, i) => i !== index))}
52
+ >
53
+ <Trash2 className="w-3 h-3" />
54
+ </Button>
55
+ </div>
56
+ <div className="pl-2 border-l-2">
57
+ {innerField && renderNestedField(`${fieldName}[${index}]`, innerField, item, (val) => {
58
+ onArrayItemChange?.(fieldName, index, val)
59
+ })}
60
+ </div>
61
+ </div>
62
+ </Card>
63
+ ))}
64
+ <Button variant="outline" size="sm" className="w-full border-dashed"
65
+ onClick={() => onChange([...arrayValue, innerField?.default || (innerField?.type === 'object' ? {} : '')])}
108
66
  >
109
- <Plus className="w-4 h-4" />
110
- 添加项
67
+ <Plus className="w-4 h-4 mr-1" /> 添加项
111
68
  </Button>
112
- </Flex>
69
+ </div>
113
70
  )
114
71
  }
115
72
 
116
73
  export function ArrayFieldRenderer({ field, value, onChange }: FieldRendererProps) {
117
- // 兼容旧格式 - 优化样式
118
74
  return (
119
- <div className="p-3 rounded-lg bg-cyan-50 dark:bg-cyan-900/20 border border-cyan-200 dark:border-cyan-800">
120
- <Flex direction="column" gap="2">
121
- <Flex align="center" gap="2">
122
- <List className="w-4 h-4 text-cyan-600 dark:text-cyan-400" />
123
- <Text size="1" weight="bold" className="text-cyan-700 dark:text-cyan-300">
124
- 数组输入 (每行一个值)
125
- </Text>
126
- </Flex>
127
- <TextArea
128
- size="2"
129
- value={Array.isArray(value) ? value.join('\n') : ''}
130
- onChange={(e) => onChange(e.target.value.split('\n').filter(Boolean))}
131
- placeholder={`每行一个值\n${field.description || ''}`}
132
- rows={3}
133
- className="font-mono text-sm bg-white dark:bg-gray-950 hover:border-cyan-500 dark:hover:border-cyan-400 transition-colors"
134
- />
135
- </Flex>
75
+ <div className="p-3 rounded-lg bg-muted/50 border space-y-2">
76
+ <p className="flex items-center gap-1 text-xs font-semibold text-muted-foreground">
77
+ <List className="w-3 h-3" /> 数组输入 (每行一个值)
78
+ </p>
79
+ <Textarea
80
+ value={Array.isArray(value) ? value.join('\n') : ''}
81
+ onChange={(e) => onChange(e.target.value.split('\n').filter(Boolean))}
82
+ placeholder={`每行一个值\n${field.description || ''}`}
83
+ rows={3} className="font-mono text-sm"
84
+ />
136
85
  </div>
137
86
  )
138
87
  }
139
88
 
140
89
  export function TupleFieldRenderer({
141
- fieldName,
142
- field,
143
- value,
144
- onArrayItemChange,
145
- renderNestedField
90
+ fieldName, field, value, onArrayItemChange, renderNestedField
146
91
  }: CollectionFieldProps) {
147
92
  const tupleValue = Array.isArray(value) ? value : []
148
93
  const tupleFields = field.list || []
149
-
94
+
150
95
  return (
151
- <div className="space-y-3">
96
+ <div className="space-y-2">
152
97
  {tupleFields.map((tupleField, index) => (
153
- <div
154
- key={index}
155
- className="p-3 rounded-lg bg-indigo-50 dark:bg-indigo-900/20 border border-indigo-200 dark:border-indigo-800 hover:border-indigo-300 dark:hover:border-indigo-700 transition-colors"
156
- >
157
- <Flex direction="column" gap="2">
158
- <Flex align="center" gap="2">
159
- <Badge size="1" variant="soft" color="indigo" className="font-mono">
160
- #{index + 1}
161
- </Badge>
162
- </Flex>
163
- {tupleField.description && (
164
- <Text size="1" className="text-indigo-700 dark:text-indigo-300">
165
- {tupleField.description}
166
- </Text>
167
- )}
168
- <div className="mt-1">
169
- {renderNestedField(`${fieldName}[${index}]`, tupleField, tupleValue[index], (val) => {
170
- onArrayItemChange?.(fieldName, index, val)
171
- })}
172
- </div>
173
- </Flex>
98
+ <div key={index} className="p-3 rounded-lg bg-muted/50 border space-y-2">
99
+ <Badge variant="secondary" className="font-mono">#{index + 1}</Badge>
100
+ {tupleField.description && <p className="text-xs text-muted-foreground">{tupleField.description}</p>}
101
+ <div className="mt-1">
102
+ {renderNestedField(`${fieldName}[${index}]`, tupleField, tupleValue[index], (val) => {
103
+ onArrayItemChange?.(fieldName, index, val)
104
+ })}
105
+ </div>
174
106
  </div>
175
107
  ))}
176
108
  </div>
@@ -178,48 +110,28 @@ export function TupleFieldRenderer({
178
110
  }
179
111
 
180
112
  export function ObjectFieldRenderer({
181
- fieldName,
182
- field,
183
- value,
184
- renderField
113
+ fieldName, field, renderField
185
114
  }: CollectionFieldProps & { renderField: (fieldName: string, field: SchemaField, parentPath?: string) => React.ReactElement }) {
186
115
  const objectFields = field.dict || field.properties || {}
187
-
116
+
188
117
  return (
189
- <div className="rounded-lg border-2 border-blue-200 dark:border-blue-800 bg-gradient-to-br from-blue-50/50 to-cyan-50/50 dark:from-blue-900/10 dark:to-cyan-900/10 overflow-hidden">
190
- <div className="px-4 py-2 bg-blue-100 dark:bg-blue-900/30 border-b border-blue-200 dark:border-blue-800">
191
- <Flex align="center" gap="2">
192
- <Package className="w-4 h-4 text-blue-600 dark:text-blue-400" />
193
- </Flex>
118
+ <div className="rounded-lg border-2 bg-muted/30 overflow-hidden">
119
+ <div className="px-4 py-2 bg-muted border-b flex items-center gap-2">
120
+ <Package className="w-4 h-4 text-muted-foreground" />
121
+ <span className="text-xs font-semibold text-muted-foreground">对象</span>
194
122
  </div>
195
- <div className="p-4 space-y-3">
123
+ <div className="p-3 space-y-2">
196
124
  {Object.entries(objectFields).map(([key, nestedField]: [string, any], index) => (
197
125
  <div key={key}>
198
- <div className="p-3 rounded-md bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800">
199
- <Flex direction="column" gap="2">
200
- <Flex align="center" gap="1">
201
- <Text size="2" weight="bold" className="text-gray-900 dark:text-gray-100">
202
- {nestedField.key || key}
203
- </Text>
204
- {nestedField.required && (
205
- <Text size="2" weight="bold" color="red" className="leading-none">
206
- *
207
- </Text>
208
- )}
209
- </Flex>
210
- {nestedField.description && (
211
- <Text size="1" color="gray">
212
- {nestedField.description}
213
- </Text>
214
- )}
215
- <div className="mt-1">
216
- {renderField(key, nestedField, fieldName)}
217
- </div>
218
- </Flex>
126
+ <div className="p-3 rounded-md bg-background border space-y-1">
127
+ <div className="flex items-center gap-1">
128
+ <span className="text-sm font-semibold">{nestedField.key || key}</span>
129
+ {nestedField.required && <span className="text-destructive font-bold">*</span>}
130
+ </div>
131
+ {nestedField.description && <p className="text-xs text-muted-foreground">{nestedField.description}</p>}
132
+ <div className="mt-1">{renderField(key, nestedField, fieldName)}</div>
219
133
  </div>
220
- {index < Object.entries(objectFields).length - 1 && (
221
- <Separator size="4" className="my-2" />
222
- )}
134
+ {index < Object.entries(objectFields).length - 1 && <Separator className="my-2" />}
223
135
  </div>
224
136
  ))}
225
137
  </div>
@@ -229,33 +141,18 @@ export function ObjectFieldRenderer({
229
141
 
230
142
  export function DictFieldRenderer({ field, value, onChange }: FieldRendererProps) {
231
143
  return (
232
- <div className="p-3 rounded-lg bg-violet-50 dark:bg-violet-900/20 border border-violet-200 dark:border-violet-800">
233
- <Flex direction="column" gap="3">
234
- <Flex align="center" gap="2">
235
- <Code className="w-4 h-4 text-violet-600 dark:text-violet-400" />
236
- </Flex>
237
- <TextArea
238
- size="2"
239
- value={typeof value === 'object' ? JSON.stringify(value, null, 2) : value || '{}'}
240
- onChange={(e) => {
241
- try {
242
- const parsed = JSON.parse(e.target.value)
243
- onChange(parsed)
244
- } catch {
245
- // 忽略解析错误,继续编辑
246
- }
247
- }}
248
- placeholder={field.description || '请输入 JSON 格式'}
249
- rows={6}
250
- className="font-mono text-sm bg-white dark:bg-gray-950 hover:border-violet-500 dark:hover:border-violet-400 transition-colors"
251
- />
252
- <Flex align="center" gap="2">
253
- <Info className="w-3 h-3 text-violet-600 dark:text-violet-400" />
254
- <Text size="1" className="text-violet-700 dark:text-violet-300">
255
- 键值对格式: &#123;"key": "value"&#125;
256
- </Text>
257
- </Flex>
258
- </Flex>
144
+ <div className="p-3 rounded-lg bg-muted/50 border space-y-2">
145
+ <p className="flex items-center gap-1 text-xs font-semibold text-muted-foreground">
146
+ <Code className="w-3 h-3" /> 键值对编辑
147
+ </p>
148
+ <Textarea
149
+ value={typeof value === 'object' ? JSON.stringify(value, null, 2) : value || '{}'}
150
+ onChange={(e) => { try { onChange(JSON.parse(e.target.value)) } catch {} }}
151
+ placeholder={field.description || '请输入 JSON 格式'} rows={6} className="font-mono text-sm"
152
+ />
153
+ <p className="flex items-center gap-1 text-xs text-muted-foreground">
154
+ <Info className="w-3 h-3" /> 键值对格式: {"{"}"key": "value"{"}"}
155
+ </p>
259
156
  </div>
260
157
  )
261
158
  }
@@ -1,102 +1,63 @@
1
1
  /**
2
- * 组合类型字段渲染器
3
- * 处理: union, intersect
2
+ * Composite type field renderers
4
3
  */
5
4
 
6
- import { Flex, Box, Text, TextField, Select, Card, Badge } from '@radix-ui/themes'
7
5
  import { GitBranch, Layers } from 'lucide-react'
8
6
  import type { FieldRendererProps, SchemaField } from './types.js'
7
+ import { Input } from '../ui/input'
8
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
9
9
 
10
10
  interface CompositeFieldProps extends FieldRendererProps {
11
- renderNestedField: (
12
- fieldName: string,
13
- field: SchemaField,
14
- value: any,
15
- onChange: (val: any) => void
16
- ) => React.ReactElement
11
+ renderNestedField: (fieldName: string, field: SchemaField, value: any, onChange: (val: any) => void) => React.ReactElement
17
12
  }
18
13
 
19
14
  export function UnionFieldRenderer({ field, value, onChange }: FieldRendererProps) {
20
15
  const unionFields = field.list || []
21
-
16
+
22
17
  if (unionFields.length === 0) {
23
18
  return (
24
- <TextField.Root
25
- size="2"
26
- value={value || ''}
27
- onChange={(e) => onChange(e.target.value)}
19
+ <Input
20
+ value={value || ''} onChange={(e) => onChange(e.target.value)}
28
21
  placeholder={field.description || '请输入值'}
29
- className="hover:border-blue-500 dark:hover:border-blue-400 transition-colors focus-within:ring-2 focus-within:ring-blue-500/20"
30
22
  />
31
23
  )
32
24
  }
33
-
34
- // 如果所有选项都是简单类型,使用下拉选择 - 优化样式
25
+
35
26
  const options = unionFields.map((uf: any) => uf.default || uf.type)
36
-
27
+
37
28
  return (
38
- <div className="p-3 rounded-lg bg-gradient-to-br from-pink-50 to-rose-50 dark:from-pink-900/20 dark:to-rose-900/20 border border-pink-200 dark:border-pink-800">
39
- <Flex direction="column" gap="2">
40
- <Flex align="center" gap="2">
41
- <GitBranch className="w-4 h-4 text-pink-600 dark:text-pink-400" />
42
- </Flex>
43
- <Select.Root
44
- size="2"
45
- value={value?.toString() || ''}
46
- onValueChange={onChange}
47
- >
48
- <Select.Trigger className="w-full hover:border-pink-500 dark:hover:border-pink-400 transition-colors" />
49
- <Select.Content className="shadow-lg">
50
- {options.map((option: any, index: number) => (
51
- <Select.Item
52
- key={index}
53
- value={String(option)}
54
- className="hover:bg-pink-50 dark:hover:bg-pink-900/20 transition-colors"
55
- >
56
- <Flex align="center" gap="2">
57
- <Text>{String(option)}</Text>
58
- </Flex>
59
- </Select.Item>
60
- ))}
61
- </Select.Content>
62
- </Select.Root>
63
- </Flex>
29
+ <div className="p-3 rounded-lg bg-muted/50 border space-y-2">
30
+ <p className="flex items-center gap-1 text-xs font-semibold text-muted-foreground">
31
+ <GitBranch className="w-3 h-3" /> 联合类型
32
+ </p>
33
+ <Select value={value?.toString() || ''} onValueChange={onChange}>
34
+ <SelectTrigger><SelectValue placeholder="请选择" /></SelectTrigger>
35
+ <SelectContent>
36
+ {options.map((option: any, index: number) => (
37
+ <SelectItem key={index} value={String(option)}>{String(option)}</SelectItem>
38
+ ))}
39
+ </SelectContent>
40
+ </Select>
64
41
  </div>
65
42
  )
66
43
  }
67
44
 
68
45
  export function IntersectFieldRenderer({
69
- fieldName,
70
- field,
71
- value,
72
- onChange,
73
- renderNestedField
46
+ fieldName, field, value, onChange, renderNestedField
74
47
  }: CompositeFieldProps) {
75
48
  const intersectFields = field.list || []
76
-
49
+
77
50
  return (
78
- <div className="rounded-lg border-2 border-teal-200 dark:border-teal-800 bg-gradient-to-br from-teal-50/50 to-emerald-50/50 dark:from-teal-900/10 dark:to-emerald-900/10 overflow-hidden">
79
- <div className="px-4 py-2 bg-teal-100 dark:bg-teal-900/30 border-b border-teal-200 dark:border-teal-800">
80
- <Flex align="center" gap="2">
81
- <Layers className="w-4 h-4 text-teal-600 dark:text-teal-400" />
82
- </Flex>
51
+ <div className="rounded-lg border-2 bg-muted/30 overflow-hidden">
52
+ <div className="px-4 py-2 bg-muted border-b flex items-center gap-2">
53
+ <Layers className="w-4 h-4 text-muted-foreground" />
54
+ <span className="text-xs font-semibold text-muted-foreground">交叉类型</span>
83
55
  </div>
84
- <div className="p-4 space-y-3">
56
+ <div className="p-3 space-y-2">
85
57
  {intersectFields.map((iField: any, index: number) => (
86
- <div
87
- key={index}
88
- className="p-3 rounded-md bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800"
89
- >
90
- <Flex direction="column" gap="2">
91
- {iField.description && (
92
- <Text size="1" color="gray">
93
- {iField.description}
94
- </Text>
95
- )}
96
- <div className="mt-1">
97
- {renderNestedField(`${fieldName}[${index}]`, iField, value, onChange)}
98
- </div>
99
- </Flex>
58
+ <div key={index} className="p-3 rounded-md bg-background border space-y-1">
59
+ {iField.description && <p className="text-xs text-muted-foreground">{iField.description}</p>}
60
+ <div className="mt-1">{renderNestedField(`${fieldName}[${index}]`, iField, value, onChange)}</div>
100
61
  </div>
101
62
  ))}
102
63
  </div>
@@ -1,31 +1,19 @@
1
1
  /**
2
- * 字段渲染器主入口
3
- * 根据字段类型分发到对应的渲染器
2
+ * Field renderer - dispatches to type-specific renderers
4
3
  */
5
4
 
6
- import { TextArea } from '@radix-ui/themes'
7
5
  import type { FieldRendererProps, SchemaField } from './types.js'
6
+ import { Textarea } from '../ui/textarea'
8
7
  import {
9
- StringFieldRenderer,
10
- NumberFieldRenderer,
11
- BooleanFieldRenderer,
12
- PercentFieldRenderer,
13
- DateFieldRenderer,
14
- RegexpFieldRenderer,
15
- ConstFieldRenderer,
16
- AnyFieldRenderer
8
+ StringFieldRenderer, NumberFieldRenderer, BooleanFieldRenderer,
9
+ PercentFieldRenderer, DateFieldRenderer, RegexpFieldRenderer,
10
+ ConstFieldRenderer, AnyFieldRenderer
17
11
  } from './BasicFieldRenderers.js'
18
12
  import {
19
- ListFieldRenderer,
20
- ArrayFieldRenderer,
21
- TupleFieldRenderer,
22
- ObjectFieldRenderer,
23
- DictFieldRenderer
13
+ ListFieldRenderer, ArrayFieldRenderer, TupleFieldRenderer,
14
+ ObjectFieldRenderer, DictFieldRenderer
24
15
  } from './CollectionFieldRenderers.js'
25
- import {
26
- UnionFieldRenderer,
27
- IntersectFieldRenderer
28
- } from './CompositeFieldRenderers.js'
16
+ import { UnionFieldRenderer, IntersectFieldRenderer } from './CompositeFieldRenderers.js'
29
17
 
30
18
  interface FieldRendererConfig extends FieldRendererProps {
31
19
  renderField: (fieldName: string, field: SchemaField, parentPath?: string) => React.ReactElement
@@ -36,66 +24,29 @@ export function FieldRenderer(props: FieldRendererConfig) {
36
24
  const { field } = props
37
25
 
38
26
  switch (field.type) {
39
- case 'string':
40
- return <StringFieldRenderer {...props} />
41
-
42
- case 'number':
43
- case 'integer':
44
- return <NumberFieldRenderer {...props} />
45
-
46
- case 'boolean':
47
- return <BooleanFieldRenderer {...props} />
48
-
49
- case 'percent':
50
- return <PercentFieldRenderer {...props} />
51
-
52
- case 'date':
53
- return <DateFieldRenderer {...props} />
54
-
55
- case 'regexp':
56
- return <RegexpFieldRenderer {...props} />
57
-
58
- case 'const':
59
- return <ConstFieldRenderer {...props} />
60
- case 'any':
61
- return <AnyFieldRenderer {...props} />
62
-
63
- case 'list':
64
- return <ListFieldRenderer {...props} />
65
-
66
- case 'array':
67
- return <ArrayFieldRenderer {...props} />
68
-
69
- case 'tuple':
70
- return <TupleFieldRenderer {...props} />
71
-
72
- case 'object':
73
- return <ObjectFieldRenderer {...props} />
74
-
75
- case 'dict':
76
- return <DictFieldRenderer {...props} />
77
-
78
- case 'union':
79
- return <UnionFieldRenderer {...props} />
80
-
81
- case 'intersect':
82
- return <IntersectFieldRenderer {...props} />
83
-
27
+ case 'string': return <StringFieldRenderer {...props} />
28
+ case 'number': case 'integer': return <NumberFieldRenderer {...props} />
29
+ case 'boolean': return <BooleanFieldRenderer {...props} />
30
+ case 'percent': return <PercentFieldRenderer {...props} />
31
+ case 'date': return <DateFieldRenderer {...props} />
32
+ case 'regexp': return <RegexpFieldRenderer {...props} />
33
+ case 'const': return <ConstFieldRenderer {...props} />
34
+ case 'any': return <AnyFieldRenderer {...props} />
35
+ case 'list': return <ListFieldRenderer {...props} />
36
+ case 'array': return <ArrayFieldRenderer {...props} />
37
+ case 'tuple': return <TupleFieldRenderer {...props} />
38
+ case 'object': return <ObjectFieldRenderer {...props} />
39
+ case 'dict': return <DictFieldRenderer {...props} />
40
+ case 'union': return <UnionFieldRenderer {...props} />
41
+ case 'intersect': return <IntersectFieldRenderer {...props} />
84
42
  default:
85
- // 默认 JSON 编辑器
86
43
  return (
87
- <TextArea
88
- size="1"
44
+ <Textarea
89
45
  value={typeof props.value === 'object' ? JSON.stringify(props.value, null, 2) : props.value || ''}
90
46
  onChange={(e) => {
91
- try {
92
- const parsed = JSON.parse(e.target.value)
93
- props.onChange(parsed)
94
- } catch {
95
- props.onChange(e.target.value)
96
- }
47
+ try { props.onChange(JSON.parse(e.target.value)) } catch { props.onChange(e.target.value) }
97
48
  }}
98
- placeholder={field.description || `请输入 JSON 格式`}
49
+ placeholder={field.description || '请输入 JSON 格式'}
99
50
  rows={4}
100
51
  className="font-mono text-xs"
101
52
  />
@@ -103,8 +54,7 @@ export function FieldRenderer(props: FieldRendererConfig) {
103
54
  }
104
55
  }
105
56
 
106
- // 辅助函数:判断字段是否为复杂类型(需要折叠)
107
57
  export function isComplexField(field: SchemaField): boolean {
108
- return ['object', 'list', 'tuple', 'union', 'intersect', 'any'].includes(field.type)
58
+ return ['object', 'list', 'tuple', 'union', 'intersect', 'any'].includes(field.type)
109
59
  || (field.type === 'dict' && !!field.dict)
110
60
  }