@zhin.js/client 1.0.4 → 1.0.6

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 (99) hide show
  1. package/README.md +23 -27
  2. package/{src → client}/index.ts +0 -1
  3. package/{src → client}/store/reducers/route.ts +1 -1
  4. package/{src → client}/store/reducers/script.ts +1 -1
  5. package/{src → client}/store/reducers/ui.ts +1 -1
  6. package/dist/index.d.ts +7 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +0 -1
  9. package/dist/index.js.map +1 -0
  10. package/dist/router/index.d.ts +25 -0
  11. package/dist/router/index.d.ts.map +1 -0
  12. package/dist/router/index.js +49 -0
  13. package/dist/router/index.js.map +1 -0
  14. package/dist/store/index.d.ts +19 -0
  15. package/dist/store/index.d.ts.map +1 -0
  16. package/dist/store/index.js +67 -0
  17. package/dist/store/index.js.map +1 -0
  18. package/dist/store/reducers/config.d.ts +54 -0
  19. package/dist/store/reducers/config.d.ts.map +1 -0
  20. package/dist/store/reducers/config.js +78 -0
  21. package/dist/store/reducers/config.js.map +1 -0
  22. package/dist/store/reducers/index.d.ts +13 -0
  23. package/dist/store/reducers/index.d.ts.map +1 -0
  24. package/dist/store/reducers/index.js +11 -0
  25. package/dist/store/reducers/index.js.map +1 -0
  26. package/dist/store/reducers/route.d.ts +26 -0
  27. package/dist/store/reducers/route.d.ts.map +1 -0
  28. package/dist/store/reducers/route.js +85 -0
  29. package/dist/store/reducers/route.js.map +1 -0
  30. package/dist/store/reducers/script.d.ts +11 -0
  31. package/dist/store/reducers/script.d.ts.map +1 -0
  32. package/dist/store/reducers/script.js +74 -0
  33. package/dist/store/reducers/script.js.map +1 -0
  34. package/dist/store/reducers/ui.d.ts +8 -0
  35. package/dist/store/reducers/ui.d.ts.map +1 -0
  36. package/dist/store/reducers/ui.js +23 -0
  37. package/dist/store/reducers/ui.js.map +1 -0
  38. package/dist/types.d.ts +7 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +2 -0
  41. package/dist/types.js.map +1 -0
  42. package/dist/websocket/hooks.d.ts +55 -0
  43. package/dist/websocket/hooks.d.ts.map +1 -0
  44. package/dist/websocket/hooks.js +225 -0
  45. package/dist/websocket/hooks.js.map +1 -0
  46. package/dist/websocket/index.d.ts +13 -0
  47. package/dist/websocket/index.d.ts.map +1 -0
  48. package/dist/websocket/index.js +31 -0
  49. package/dist/websocket/index.js.map +1 -0
  50. package/dist/websocket/instance.d.ts +18 -0
  51. package/dist/websocket/instance.d.ts.map +1 -0
  52. package/dist/websocket/instance.js +39 -0
  53. package/dist/websocket/instance.js.map +1 -0
  54. package/dist/websocket/manager.d.ts +110 -0
  55. package/dist/websocket/manager.d.ts.map +1 -0
  56. package/dist/websocket/manager.js +341 -0
  57. package/dist/websocket/manager.js.map +1 -0
  58. package/dist/websocket/messageHandler.d.ts +48 -0
  59. package/dist/websocket/messageHandler.d.ts.map +1 -0
  60. package/dist/websocket/messageHandler.js +140 -0
  61. package/dist/websocket/messageHandler.js.map +1 -0
  62. package/dist/websocket/types.d.ts +125 -0
  63. package/dist/websocket/types.d.ts.map +1 -0
  64. package/dist/websocket/types.js +43 -0
  65. package/dist/websocket/types.js.map +1 -0
  66. package/package.json +12 -18
  67. package/app/index.html +0 -13
  68. package/app/postcss.config.js +0 -5
  69. package/app/src/components/PluginConfigForm/BasicFieldRenderers.tsx +0 -253
  70. package/app/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +0 -261
  71. package/app/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +0 -105
  72. package/app/src/components/PluginConfigForm/FieldRenderer.tsx +0 -110
  73. package/app/src/components/PluginConfigForm/NestedFieldRenderer.tsx +0 -95
  74. package/app/src/components/PluginConfigForm/index.tsx +0 -237
  75. package/app/src/components/PluginConfigForm/types.ts +0 -46
  76. package/app/src/components/ThemeToggle.tsx +0 -21
  77. package/app/src/hooks/useTheme.ts +0 -17
  78. package/app/src/layouts/dashboard.tsx +0 -259
  79. package/app/src/main.tsx +0 -121
  80. package/app/src/pages/dashboard-bots.tsx +0 -198
  81. package/app/src/pages/dashboard-home.tsx +0 -301
  82. package/app/src/pages/dashboard-logs.tsx +0 -298
  83. package/app/src/pages/dashboard-plugin-detail.tsx +0 -360
  84. package/app/src/pages/dashboard-plugins.tsx +0 -166
  85. package/app/src/style.css +0 -1105
  86. package/app/src/theme/index.ts +0 -92
  87. package/app/tailwind.config.js +0 -70
  88. package/app/tsconfig.json +0 -16
  89. /package/{src → client}/router/index.tsx +0 -0
  90. /package/{src → client}/store/index.ts +0 -0
  91. /package/{src → client}/store/reducers/config.ts +0 -0
  92. /package/{src → client}/store/reducers/index.ts +0 -0
  93. /package/{src → client}/types.ts +0 -0
  94. /package/{src → client}/websocket/hooks.ts +0 -0
  95. /package/{src → client}/websocket/index.ts +0 -0
  96. /package/{src → client}/websocket/instance.ts +0 -0
  97. /package/{src → client}/websocket/manager.ts +0 -0
  98. /package/{src → client}/websocket/messageHandler.ts +0 -0
  99. /package/{src → client}/websocket/types.ts +0 -0
@@ -1,261 +0,0 @@
1
- /**
2
- * 集合类型字段渲染器
3
- * 处理: list, tuple, object, dict
4
- */
5
-
6
- import { Flex, Box, Text, TextArea, Button, Badge, Card, Separator } from '@radix-ui/themes'
7
- import { Icons } from '@zhin.js/client'
8
- import type { FieldRendererProps, SchemaField } from './types.js'
9
-
10
- interface CollectionFieldProps extends FieldRendererProps {
11
- renderNestedField: (
12
- fieldName: string,
13
- field: SchemaField,
14
- value: any,
15
- onChange: (val: any) => void
16
- ) => React.ReactElement
17
- }
18
-
19
- export function ListFieldRenderer({
20
- fieldName,
21
- field,
22
- value,
23
- onChange,
24
- onArrayItemChange,
25
- renderNestedField
26
- }: CollectionFieldProps) {
27
- const arrayValue = Array.isArray(value) ? value : []
28
- const innerField = field.inner || field.items
29
-
30
- // 简单类型 - 使用多行文本 - 优化样式
31
- if (innerField && ['string', 'number'].includes(innerField.type)) {
32
- 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
- <Icons.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>
56
- </div>
57
- )
58
- }
59
-
60
- // 复杂类型 - Card 列表 - 优化样式
61
- 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
- <Icons.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"
108
- >
109
- <Icons.Plus className="w-4 h-4" />
110
- 添加项
111
- </Button>
112
- </Flex>
113
- )
114
- }
115
-
116
- export function ArrayFieldRenderer({ field, value, onChange }: FieldRendererProps) {
117
- // 兼容旧格式 - 优化样式
118
- 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
- <Icons.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>
136
- </div>
137
- )
138
- }
139
-
140
- export function TupleFieldRenderer({
141
- fieldName,
142
- field,
143
- value,
144
- onArrayItemChange,
145
- renderNestedField
146
- }: CollectionFieldProps) {
147
- const tupleValue = Array.isArray(value) ? value : []
148
- const tupleFields = field.list || []
149
-
150
- return (
151
- <div className="space-y-3">
152
- {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>
174
- </div>
175
- ))}
176
- </div>
177
- )
178
- }
179
-
180
- export function ObjectFieldRenderer({
181
- fieldName,
182
- field,
183
- value,
184
- renderField
185
- }: CollectionFieldProps & { renderField: (fieldName: string, field: SchemaField, parentPath?: string) => React.ReactElement }) {
186
- const objectFields = field.dict || field.properties || {}
187
-
188
- 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
- <Icons.Package className="w-4 h-4 text-blue-600 dark:text-blue-400" />
193
- </Flex>
194
- </div>
195
- <div className="p-4 space-y-3">
196
- {Object.entries(objectFields).map(([key, nestedField]: [string, any], index) => (
197
- <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>
219
- </div>
220
- {index < Object.entries(objectFields).length - 1 && (
221
- <Separator size="4" className="my-2" />
222
- )}
223
- </div>
224
- ))}
225
- </div>
226
- </div>
227
- )
228
- }
229
-
230
- export function DictFieldRenderer({ field, value, onChange }: FieldRendererProps) {
231
- 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
- <Icons.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
- <Icons.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>
259
- </div>
260
- )
261
- }
@@ -1,105 +0,0 @@
1
- /**
2
- * 组合类型字段渲染器
3
- * 处理: union, intersect
4
- */
5
-
6
- import { Flex, Box, Text, TextField, Select, Card, Badge } from '@radix-ui/themes'
7
- import { Icons } from '@zhin.js/client'
8
- import type { FieldRendererProps, SchemaField } from './types.js'
9
-
10
- interface CompositeFieldProps extends FieldRendererProps {
11
- renderNestedField: (
12
- fieldName: string,
13
- field: SchemaField,
14
- value: any,
15
- onChange: (val: any) => void
16
- ) => React.ReactElement
17
- }
18
-
19
- export function UnionFieldRenderer({ field, value, onChange }: FieldRendererProps) {
20
- const unionFields = field.list || []
21
-
22
- if (unionFields.length === 0) {
23
- return (
24
- <TextField.Root
25
- size="2"
26
- value={value || ''}
27
- onChange={(e) => onChange(e.target.value)}
28
- 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
- />
31
- )
32
- }
33
-
34
- // 如果所有选项都是简单类型,使用下拉选择 - 优化样式
35
- const options = unionFields.map((uf: any) => uf.default || uf.type)
36
-
37
- 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
- <Icons.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>
64
- </div>
65
- )
66
- }
67
-
68
- export function IntersectFieldRenderer({
69
- fieldName,
70
- field,
71
- value,
72
- onChange,
73
- renderNestedField
74
- }: CompositeFieldProps) {
75
- const intersectFields = field.list || []
76
-
77
- 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
- <Icons.Layers className="w-4 h-4 text-teal-600 dark:text-teal-400" />
82
- </Flex>
83
- </div>
84
- <div className="p-4 space-y-3">
85
- {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>
100
- </div>
101
- ))}
102
- </div>
103
- </div>
104
- )
105
- }
@@ -1,110 +0,0 @@
1
- /**
2
- * 字段渲染器主入口
3
- * 根据字段类型分发到对应的渲染器
4
- */
5
-
6
- import { TextArea } from '@radix-ui/themes'
7
- import type { FieldRendererProps, SchemaField } from './types.js'
8
- import {
9
- StringFieldRenderer,
10
- NumberFieldRenderer,
11
- BooleanFieldRenderer,
12
- PercentFieldRenderer,
13
- DateFieldRenderer,
14
- RegexpFieldRenderer,
15
- ConstFieldRenderer,
16
- AnyFieldRenderer
17
- } from './BasicFieldRenderers.js'
18
- import {
19
- ListFieldRenderer,
20
- ArrayFieldRenderer,
21
- TupleFieldRenderer,
22
- ObjectFieldRenderer,
23
- DictFieldRenderer
24
- } from './CollectionFieldRenderers.js'
25
- import {
26
- UnionFieldRenderer,
27
- IntersectFieldRenderer
28
- } from './CompositeFieldRenderers.js'
29
-
30
- interface FieldRendererConfig extends FieldRendererProps {
31
- renderField: (fieldName: string, field: SchemaField, parentPath?: string) => React.ReactElement
32
- renderNestedField: (fieldName: string, field: SchemaField, value: any, onChange: (val: any) => void) => React.ReactElement
33
- }
34
-
35
- export function FieldRenderer(props: FieldRendererConfig) {
36
- const { field } = props
37
-
38
- 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
-
84
- default:
85
- // 默认 JSON 编辑器
86
- return (
87
- <TextArea
88
- size="1"
89
- value={typeof props.value === 'object' ? JSON.stringify(props.value, null, 2) : props.value || ''}
90
- 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
- }
97
- }}
98
- placeholder={field.description || `请输入 JSON 格式`}
99
- rows={4}
100
- className="font-mono text-xs"
101
- />
102
- )
103
- }
104
- }
105
-
106
- // 辅助函数:判断字段是否为复杂类型(需要折叠)
107
- export function isComplexField(field: SchemaField): boolean {
108
- return ['object', 'list', 'tuple', 'union', 'intersect', 'any'].includes(field.type)
109
- || (field.type === 'dict' && !!field.dict)
110
- }
@@ -1,95 +0,0 @@
1
- /**
2
- * 嵌套字段渲染器
3
- * 用于渲染数组项、元组项等嵌套字段
4
- */
5
-
6
- import { Flex, Box, Text, TextField, TextArea, Switch, Card } from '@radix-ui/themes'
7
- import type { SchemaField } from './types.js'
8
-
9
- interface NestedFieldRendererProps {
10
- fieldName: string
11
- field: SchemaField
12
- value: any
13
- onChange: (val: any) => void
14
- }
15
-
16
- export function NestedFieldRenderer({ field, value, onChange }: NestedFieldRendererProps): React.ReactElement {
17
- switch (field.type) {
18
- case 'string':
19
- return (
20
- <TextField.Root
21
- size="1"
22
- value={value || ''}
23
- onChange={(e) => onChange(e.target.value)}
24
- placeholder={field.description || '请输入'}
25
- />
26
- )
27
-
28
- case 'number':
29
- case 'integer':
30
- return (
31
- <TextField.Root
32
- size="1"
33
- type="number"
34
- value={value?.toString() || ''}
35
- onChange={(e) => onChange(parseFloat(e.target.value) || 0)}
36
- placeholder={field.description || '请输入数字'}
37
- min={field.min}
38
- max={field.max}
39
- />
40
- )
41
-
42
- case 'boolean':
43
- return (
44
- <Flex align="center" gap="2">
45
- <Switch
46
- checked={value === true}
47
- onCheckedChange={onChange}
48
- />
49
- <Text size="2" color={value ? 'green' : 'gray'}>
50
- {value ? '已启用' : '已禁用'}
51
- </Text>
52
- </Flex>
53
- )
54
-
55
- case 'object': {
56
- const objectFields = field.dict || field.properties || {}
57
- return (
58
- <Card size="1">
59
- <Flex direction="column" gap="2" p="2">
60
- {Object.entries(objectFields).map(([key, nestedField]: [string, any]) => (
61
- <Box key={key}>
62
- <Text size="1" weight="bold">{key}</Text>
63
- <NestedFieldRenderer
64
- fieldName={key}
65
- field={nestedField}
66
- value={value?.[key]}
67
- onChange={(val) => {
68
- onChange({ ...value, [key]: val })
69
- }}
70
- />
71
- </Box>
72
- ))}
73
- </Flex>
74
- </Card>
75
- )
76
- }
77
-
78
- default:
79
- return (
80
- <TextArea
81
- size="1"
82
- value={typeof value === 'object' ? JSON.stringify(value, null, 2) : value || ''}
83
- onChange={(e) => {
84
- try {
85
- onChange(JSON.parse(e.target.value))
86
- } catch {
87
- onChange(e.target.value)
88
- }
89
- }}
90
- rows={3}
91
- className="font-mono text-xs"
92
- />
93
- )
94
- }
95
- }