@zhin.js/client 1.0.4 → 1.0.5

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 (97) hide show
  1. package/dist/index.d.ts +8 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/router/index.d.ts +25 -0
  5. package/dist/router/index.d.ts.map +1 -0
  6. package/dist/router/index.js +49 -0
  7. package/dist/router/index.js.map +1 -0
  8. package/dist/store/index.d.ts +19 -0
  9. package/dist/store/index.d.ts.map +1 -0
  10. package/dist/store/index.js +67 -0
  11. package/dist/store/index.js.map +1 -0
  12. package/dist/store/reducers/config.d.ts +54 -0
  13. package/dist/store/reducers/config.d.ts.map +1 -0
  14. package/dist/store/reducers/config.js +78 -0
  15. package/dist/store/reducers/config.js.map +1 -0
  16. package/dist/store/reducers/index.d.ts +13 -0
  17. package/dist/store/reducers/index.d.ts.map +1 -0
  18. package/dist/store/reducers/index.js +11 -0
  19. package/dist/store/reducers/index.js.map +1 -0
  20. package/dist/store/reducers/route.d.ts +37 -0
  21. package/dist/store/reducers/route.d.ts.map +1 -0
  22. package/dist/store/reducers/route.js +85 -0
  23. package/dist/store/reducers/route.js.map +1 -0
  24. package/dist/store/reducers/script.d.ts +17 -0
  25. package/dist/store/reducers/script.d.ts.map +1 -0
  26. package/dist/store/reducers/script.js +74 -0
  27. package/dist/store/reducers/script.js.map +1 -0
  28. package/dist/store/reducers/ui.d.ts +14 -0
  29. package/dist/store/reducers/ui.d.ts.map +1 -0
  30. package/dist/store/reducers/ui.js +23 -0
  31. package/dist/store/reducers/ui.js.map +1 -0
  32. package/dist/types.d.ts +7 -0
  33. package/dist/types.d.ts.map +1 -0
  34. package/dist/types.js +2 -0
  35. package/dist/types.js.map +1 -0
  36. package/dist/websocket/hooks.d.ts +55 -0
  37. package/dist/websocket/hooks.d.ts.map +1 -0
  38. package/dist/websocket/hooks.js +225 -0
  39. package/dist/websocket/hooks.js.map +1 -0
  40. package/dist/websocket/index.d.ts +13 -0
  41. package/dist/websocket/index.d.ts.map +1 -0
  42. package/dist/websocket/index.js +31 -0
  43. package/dist/websocket/index.js.map +1 -0
  44. package/dist/websocket/instance.d.ts +18 -0
  45. package/dist/websocket/instance.d.ts.map +1 -0
  46. package/dist/websocket/instance.js +39 -0
  47. package/dist/websocket/instance.js.map +1 -0
  48. package/dist/websocket/manager.d.ts +110 -0
  49. package/dist/websocket/manager.d.ts.map +1 -0
  50. package/dist/websocket/manager.js +341 -0
  51. package/dist/websocket/manager.js.map +1 -0
  52. package/dist/websocket/messageHandler.d.ts +48 -0
  53. package/dist/websocket/messageHandler.d.ts.map +1 -0
  54. package/dist/websocket/messageHandler.js +140 -0
  55. package/dist/websocket/messageHandler.js.map +1 -0
  56. package/dist/websocket/types.d.ts +125 -0
  57. package/dist/websocket/types.d.ts.map +1 -0
  58. package/dist/websocket/types.js +43 -0
  59. package/dist/websocket/types.js.map +1 -0
  60. package/package.json +8 -18
  61. package/app/index.html +0 -13
  62. package/app/postcss.config.js +0 -5
  63. package/app/src/components/PluginConfigForm/BasicFieldRenderers.tsx +0 -253
  64. package/app/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +0 -261
  65. package/app/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +0 -105
  66. package/app/src/components/PluginConfigForm/FieldRenderer.tsx +0 -110
  67. package/app/src/components/PluginConfigForm/NestedFieldRenderer.tsx +0 -95
  68. package/app/src/components/PluginConfigForm/index.tsx +0 -237
  69. package/app/src/components/PluginConfigForm/types.ts +0 -46
  70. package/app/src/components/ThemeToggle.tsx +0 -21
  71. package/app/src/hooks/useTheme.ts +0 -17
  72. package/app/src/layouts/dashboard.tsx +0 -259
  73. package/app/src/main.tsx +0 -121
  74. package/app/src/pages/dashboard-bots.tsx +0 -198
  75. package/app/src/pages/dashboard-home.tsx +0 -301
  76. package/app/src/pages/dashboard-logs.tsx +0 -298
  77. package/app/src/pages/dashboard-plugin-detail.tsx +0 -360
  78. package/app/src/pages/dashboard-plugins.tsx +0 -166
  79. package/app/src/style.css +0 -1105
  80. package/app/src/theme/index.ts +0 -92
  81. package/app/tailwind.config.js +0 -70
  82. package/app/tsconfig.json +0 -16
  83. /package/{src → client}/index.ts +0 -0
  84. /package/{src → client}/router/index.tsx +0 -0
  85. /package/{src → client}/store/index.ts +0 -0
  86. /package/{src → client}/store/reducers/config.ts +0 -0
  87. /package/{src → client}/store/reducers/index.ts +0 -0
  88. /package/{src → client}/store/reducers/route.ts +0 -0
  89. /package/{src → client}/store/reducers/script.ts +0 -0
  90. /package/{src → client}/store/reducers/ui.ts +0 -0
  91. /package/{src → client}/types.ts +0 -0
  92. /package/{src → client}/websocket/hooks.ts +0 -0
  93. /package/{src → client}/websocket/index.ts +0 -0
  94. /package/{src → client}/websocket/instance.ts +0 -0
  95. /package/{src → client}/websocket/manager.ts +0 -0
  96. /package/{src → client}/websocket/messageHandler.ts +0 -0
  97. /package/{src → client}/websocket/types.ts +0 -0
@@ -0,0 +1,43 @@
1
+ /**
2
+ * WebSocket 相关类型定义
3
+ */
4
+ // ============================================================================
5
+ // 连接状态枚举
6
+ // ============================================================================
7
+ export var ConnectionState;
8
+ (function (ConnectionState) {
9
+ ConnectionState["DISCONNECTED"] = "disconnected";
10
+ ConnectionState["CONNECTING"] = "connecting";
11
+ ConnectionState["CONNECTED"] = "connected";
12
+ ConnectionState["RECONNECTING"] = "reconnecting";
13
+ ConnectionState["ERROR"] = "error";
14
+ })(ConnectionState || (ConnectionState = {}));
15
+ // ============================================================================
16
+ // 错误类型
17
+ // ============================================================================
18
+ export class WebSocketError extends Error {
19
+ code;
20
+ originalError;
21
+ constructor(message, code, originalError) {
22
+ super(message);
23
+ this.code = code;
24
+ this.originalError = originalError;
25
+ this.name = 'WebSocketError';
26
+ }
27
+ }
28
+ export class RequestTimeoutError extends WebSocketError {
29
+ constructor(requestId) {
30
+ super(`Request ${requestId} timed out`, 'REQUEST_TIMEOUT');
31
+ }
32
+ }
33
+ export class ConnectionError extends WebSocketError {
34
+ constructor(message, originalError) {
35
+ super(message, 'CONNECTION_ERROR', originalError);
36
+ }
37
+ }
38
+ export class MessageError extends WebSocketError {
39
+ constructor(message, originalError) {
40
+ super(message, 'MESSAGE_ERROR', originalError);
41
+ }
42
+ }
43
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../client/websocket/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAkKH,+EAA+E;AAC/E,SAAS;AACT,+EAA+E;AAE/E,MAAM,CAAN,IAAY,eAMX;AAND,WAAY,eAAe;IACzB,gDAA6B,CAAA;IAC7B,4CAAyB,CAAA;IACzB,0CAAuB,CAAA;IACvB,gDAA6B,CAAA;IAC7B,kCAAe,CAAA;AACjB,CAAC,EANW,eAAe,KAAf,eAAe,QAM1B;AAED,+EAA+E;AAC/E,OAAO;AACP,+EAA+E;AAE/E,MAAM,OAAO,cAAe,SAAQ,KAAK;IAG9B;IACA;IAHT,YACE,OAAe,EACR,IAAY,EACZ,aAAqB;QAE5B,KAAK,CAAC,OAAO,CAAC,CAAA;QAHP,SAAI,GAAJ,IAAI,CAAQ;QACZ,kBAAa,GAAb,aAAa,CAAQ;QAG5B,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,cAAc;IACrD,YAAY,SAAiB;QAC3B,KAAK,CAAC,WAAW,SAAS,YAAY,EAAE,iBAAiB,CAAC,CAAA;IAC5D,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,cAAc;IACjD,YAAY,OAAe,EAAE,aAAqB;QAChD,KAAK,CAAC,OAAO,EAAE,kBAAkB,EAAE,aAAa,CAAC,CAAA;IACnD,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,cAAc;IAC9C,YAAY,OAAe,EAAE,aAAqB;QAChD,KAAK,CAAC,OAAO,EAAE,eAAe,EAAE,aAAa,CAAC,CAAA;IAChD,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/client",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Zhin 客户端",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,33 +11,23 @@
11
11
  "import": "./dist/index.js"
12
12
  }
13
13
  },
14
- "dependencies": {
14
+ "dependencies": {},
15
+ "peerDependencies": {
15
16
  "@reduxjs/toolkit": "^2.9.0",
16
- "@tailwindcss/postcss": "^4.1.11",
17
+ "lucide-react": "^0.469.0",
17
18
  "clsx": "^2.1.1",
18
- "react": "^19.2.0",
19
- "react-dom": "^19.2.0",
20
19
  "react-redux": "9.2.0",
21
20
  "react-router": "7.0.0",
22
21
  "redux-persist": "6.0.0",
23
- "tailwind-merge": "^3.3.1",
24
- "tailwindcss": "latest"
22
+ "tailwind-merge": "^3.3.1"
25
23
  },
26
24
  "files": [
27
- "lib",
28
- "src",
29
- "app"
25
+ "dist",
26
+ "client"
30
27
  ],
31
28
  "devDependencies": {
32
- "@radix-ui/themes": "^3.2.1",
33
- "@types/events": "^3.0.3",
34
- "@types/node": "^24.7.1",
35
29
  "@types/react": "^19.2.2",
36
- "@types/react-dom": "^19.2.1",
37
- "@vitejs/plugin-react": "^4.3.4",
38
- "lucide-react": "^0.469.0",
39
- "radix-ui": "^1.4.3",
40
- "vite": "^7.0.6"
30
+ "@types/react-dom": "^19.2.1"
41
31
  },
42
32
  "scripts": {
43
33
  "build": "tsc",
package/app/index.html DELETED
@@ -1,13 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang='en'>
3
- <head>
4
- <meta charset='UTF-8'>
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Zhin</title>
7
- </head>
8
- <body>
9
- <noscript>You need to enable JavaScript to run this app.</noscript>
10
- <div id="root"></div>
11
- <script src='/src/main.tsx' type='module'></script>
12
- </body>
13
- </html>
@@ -1,5 +0,0 @@
1
- export default {
2
- plugins: {
3
- "@tailwindcss/postcss": {},
4
- },
5
- };
@@ -1,253 +0,0 @@
1
- /**
2
- * 基础类型字段渲染器
3
- * 处理: string, number, boolean, percent, date, regexp, const
4
- */
5
-
6
- import { Flex, Box, Text, TextField, TextArea, Switch, Select, Badge, Callout } from '@radix-ui/themes'
7
- import { Icons } from '@zhin.js/client'
8
- import type { FieldRendererProps } from './types.js'
9
-
10
- export function StringFieldRenderer({ field, value, onChange }: FieldRendererProps) {
11
- // 枚举类型 - 下拉选择 - 优化样式
12
- if (field.enum) {
13
- return (
14
- <Select.Root
15
- size="2"
16
- value={value?.toString() || ''}
17
- onValueChange={onChange}
18
- >
19
- <Select.Trigger className="w-full hover:border-blue-500 dark:hover:border-blue-400 transition-colors" />
20
- <Select.Content className="shadow-lg">
21
- {field.enum.map((option) => (
22
- <Select.Item
23
- key={option}
24
- value={option.toString()}
25
- className="hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors"
26
- >
27
- {option.toString()}
28
- </Select.Item>
29
- ))}
30
- </Select.Content>
31
- </Select.Root>
32
- )
33
- }
34
-
35
- // 多行文本 - 优化样式
36
- if (field.description?.includes('多行') || field.key?.includes('description')) {
37
- return (
38
- <TextArea
39
- size="2"
40
- value={value || ''}
41
- onChange={(e) => onChange(e.target.value)}
42
- placeholder={field.description || `请输入`}
43
- rows={3}
44
- className="font-sans resize-y hover:border-blue-500 dark:hover:border-blue-400 transition-colors focus:ring-2 focus:ring-blue-500/20"
45
- />
46
- )
47
- }
48
-
49
- // 单行文本 - 优化样式
50
- return (
51
- <TextField.Root
52
- size="2"
53
- value={value || ''}
54
- onChange={(e) => onChange(e.target.value)}
55
- placeholder={field.description || `请输入`}
56
- className="hover:border-blue-500 dark:hover:border-blue-400 transition-colors focus-within:ring-2 focus-within:ring-blue-500/20"
57
- />
58
- )
59
- }
60
-
61
- export function NumberFieldRenderer({ field, value, onChange }: FieldRendererProps) {
62
- return (
63
- <TextField.Root
64
- size="2"
65
- type="number"
66
- value={value?.toString() || ''}
67
- onChange={(e) => onChange(parseFloat(e.target.value) || 0)}
68
- placeholder={field.description || `请输入数字`}
69
- min={field.min}
70
- max={field.max}
71
- className="hover:border-blue-500 dark:hover:border-blue-400 transition-colors focus-within:ring-2 focus-within:ring-blue-500/20"
72
- />
73
- )
74
- }
75
-
76
- export function BooleanFieldRenderer({ value, onChange }: FieldRendererProps) {
77
- return (
78
- <Flex align="center" gap="3" className="p-3 rounded-lg bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-800">
79
- <Switch
80
- size="2"
81
- checked={value === true}
82
- onCheckedChange={onChange}
83
- className="cursor-pointer"
84
- />
85
- <Flex direction="column" gap="1">
86
- <Text size="2" weight="bold" color={value ? 'green' : 'gray'}>
87
- {value ? '已启用' : '已禁用'}
88
- </Text>
89
- <Text size="1" color="gray">
90
- {value ? '功能当前处于开启状态' : '功能当前处于关闭状态'}
91
- </Text>
92
- </Flex>
93
- </Flex>
94
- )
95
- }
96
-
97
- export function PercentFieldRenderer({ field, value, onChange }: FieldRendererProps) {
98
- const percentValue = typeof value === 'number' ? value : (field.default || 0)
99
-
100
- return (
101
- <div className="p-4 rounded-lg bg-gradient-to-br from-blue-50 to-cyan-50 dark:from-blue-900/20 dark:to-cyan-900/20 border border-blue-200 dark:border-blue-800">
102
- <Flex direction="column" gap="3">
103
- <Flex align="center" gap="3">
104
- <input
105
- type="range"
106
- min={field.min || 0}
107
- max={field.max || 1}
108
- step={field.step || 0.01}
109
- value={percentValue}
110
- onChange={(e) => onChange(parseFloat(e.target.value))}
111
- className="flex-1 h-2 rounded-lg appearance-none cursor-pointer bg-blue-200 dark:bg-blue-800 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-blue-600 dark:[&::-webkit-slider-thumb]:bg-blue-400 [&::-webkit-slider-thumb]:cursor-pointer hover:[&::-webkit-slider-thumb]:bg-blue-700 dark:hover:[&::-webkit-slider-thumb]:bg-blue-300 transition-all"
112
- />
113
- <TextField.Root
114
- size="2"
115
- type="number"
116
- value={(percentValue * 100).toFixed(0)}
117
- onChange={(e) => onChange(parseFloat(e.target.value) / 100)}
118
- min={(field.min || 0) * 100}
119
- max={(field.max || 1) * 100}
120
- className="w-24"
121
- >
122
- <TextField.Slot side="right">
123
- <Text size="1" weight="bold" className="text-blue-600 dark:text-blue-400">%</Text>
124
- </TextField.Slot>
125
- </TextField.Root>
126
- </Flex>
127
- <Flex align="center" justify="between">
128
- <Text size="1" color="gray">
129
- 当前值
130
- </Text>
131
- <Badge color="blue" size="2" variant="soft" className="font-mono">
132
- {(percentValue * 100).toFixed(1)}%
133
- </Badge>
134
- </Flex>
135
- </Flex>
136
- </div>
137
- )
138
- }
139
-
140
- export function DateFieldRenderer({ field, value, onChange }: FieldRendererProps) {
141
- const dateValue = value instanceof Date
142
- ? value.toISOString().split('T')[0]
143
- : value || ''
144
-
145
- return (
146
- <div className="relative">
147
- <TextField.Root
148
- size="2"
149
- type="date"
150
- value={dateValue}
151
- onChange={(e) => onChange(new Date(e.target.value))}
152
- placeholder={field.description || '选择日期'}
153
- className="hover:border-blue-500 dark:hover:border-blue-400 transition-colors focus-within:ring-2 focus-within:ring-blue-500/20"
154
- >
155
- <TextField.Slot side="left">
156
- <Icons.Calendar className="w-4 h-4 text-gray-500" />
157
- </TextField.Slot>
158
- </TextField.Root>
159
- </div>
160
- )
161
- }
162
-
163
- export function RegexpFieldRenderer({ field, value, onChange }: FieldRendererProps) {
164
- const regexpValue = value instanceof RegExp
165
- ? value.source
166
- : (typeof value === 'string' ? value : '')
167
-
168
- return (
169
- <div className="p-3 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800">
170
- <Flex direction="column" gap="2">
171
- <TextField.Root
172
- size="2"
173
- value={regexpValue}
174
- onChange={(e) => {
175
- try {
176
- onChange(new RegExp(e.target.value))
177
- } catch {
178
- onChange(e.target.value)
179
- }
180
- }}
181
- placeholder={field.description || '请输入正则表达式'}
182
- className="font-mono text-sm hover:border-amber-500 dark:hover:border-amber-400 transition-colors"
183
- >
184
- <TextField.Slot side="left">
185
- <Text size="1" className="text-amber-600 dark:text-amber-400 font-bold">/</Text>
186
- </TextField.Slot>
187
- <TextField.Slot side="right">
188
- <Text size="1" className="text-amber-600 dark:text-amber-400 font-bold">/</Text>
189
- </TextField.Slot>
190
- </TextField.Root>
191
- <Flex align="center" gap="2">
192
- <Icons.Info className="w-3 h-3 text-amber-600 dark:text-amber-400" />
193
- <Text size="1" className="text-amber-700 dark:text-amber-300">
194
- 输入正则表达式模式 (省略斜杠)
195
- </Text>
196
- </Flex>
197
- </Flex>
198
- </div>
199
- )
200
- }
201
-
202
- export function ConstFieldRenderer({ field, value }: FieldRendererProps) {
203
- const constValue = field.default || value
204
-
205
- return (
206
- <div className="p-3 rounded-lg bg-gray-100 dark:bg-gray-900 border border-gray-300 dark:border-gray-700">
207
- <Flex align="center" gap="3">
208
- <Icons.Lock className="w-4 h-4 text-gray-500 dark:text-gray-400" />
209
- <Badge variant="soft" size="2" className="font-mono">
210
- {String(constValue)}
211
- </Badge>
212
- <Text size="1" color="gray" className="ml-auto">
213
- (常量,不可修改)
214
- </Text>
215
- </Flex>
216
- </div>
217
- )
218
- }
219
-
220
- export function AnyFieldRenderer({ field, value, onChange }: FieldRendererProps) {
221
- return (
222
- <div className="p-3 rounded-lg bg-purple-50 dark:bg-purple-900/20 border border-purple-200 dark:border-purple-800">
223
- <Flex direction="column" gap="3">
224
- <Flex align="center" gap="2">
225
- <Icons.Code className="w-4 h-4 text-purple-600 dark:text-purple-400" />
226
- <Text size="1" weight="bold" className="text-purple-700 dark:text-purple-300">
227
- JSON 格式输入
228
- </Text>
229
- </Flex>
230
- <TextArea
231
- size="2"
232
- value={typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value || '')}
233
- onChange={(e) => {
234
- try {
235
- onChange(JSON.parse(e.target.value))
236
- } catch {
237
- onChange(e.target.value)
238
- }
239
- }}
240
- placeholder={field.description || '支持任意类型 (JSON 格式)'}
241
- rows={4}
242
- className="font-mono text-sm bg-white dark:bg-gray-950 hover:border-purple-500 dark:hover:border-purple-400 transition-colors"
243
- />
244
- <Flex align="center" gap="2">
245
- <Icons.Info className="w-3 h-3 text-purple-600 dark:text-purple-400" />
246
- <Text size="1" className="text-purple-700 dark:text-purple-300">
247
- 支持: 字符串、数字、布尔值、对象、数组
248
- </Text>
249
- </Flex>
250
- </Flex>
251
- </div>
252
- )
253
- }
@@ -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
- }