@zhin.js/client 1.0.2 → 1.0.4

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.
@@ -2,8 +2,9 @@ import { useEffect, useState } from 'react'
2
2
  import { useParams, useNavigate } from 'react-router'
3
3
  import * as Themes from '@radix-ui/themes'
4
4
  import { Icons } from '@zhin.js/client'
5
+ import {PluginConfigForm} from '../components/PluginConfigForm'
5
6
 
6
- const { Flex, Box, Spinner, Text, Callout, Heading, Badge, Grid, Card, Button, Code, Separator, ScrollArea } = Themes
7
+ const { Flex, Box, Spinner, Text, Callout, Heading, Badge, Grid, Card, Button, Code, Separator, ScrollArea, Dialog } = Themes
7
8
 
8
9
  interface PluginDetail {
9
10
  name: string
@@ -12,10 +13,6 @@ interface PluginDetail {
12
13
  description: string
13
14
  commands: Array<{
14
15
  name: string
15
- pattern: string
16
- description: string
17
- alias: string[]
18
- examples: string[]
19
16
  }>
20
17
  components: Array<{
21
18
  name: string
@@ -35,7 +32,7 @@ interface PluginDetail {
35
32
  pattern: string
36
33
  running: boolean
37
34
  }>
38
- schemas: Array<{
35
+ definitions: Array<{
39
36
  name: string
40
37
  fields: string[]
41
38
  }>
@@ -45,7 +42,7 @@ interface PluginDetail {
45
42
  middlewareCount: number
46
43
  contextCount: number
47
44
  cronCount: number
48
- schemaCount: number
45
+ definitionCount: number
49
46
  }
50
47
  }
51
48
 
@@ -81,6 +78,8 @@ export default function DashboardPluginDetail() {
81
78
  }
82
79
  }
83
80
 
81
+
82
+
84
83
  if (loading) {
85
84
  return (
86
85
  <Flex align="center" justify="center" className="h-full">
@@ -115,7 +114,7 @@ export default function DashboardPluginDetail() {
115
114
  <Box>
116
115
  {/* 头部 */}
117
116
  <Flex direction="column" gap="3" mb="4">
118
- <Button variant="ghost" onClick={() => navigate('/plugins')} size="2" className="self-start">
117
+ <Button variant="ghost" onClick={() => navigate('/plugins')} size="2">
119
118
  <Icons.ArrowLeft className="w-4 h-4" />
120
119
  返回
121
120
  </Button>
@@ -136,7 +135,15 @@ export default function DashboardPluginDetail() {
136
135
  </Flex>
137
136
  </Flex>
138
137
 
139
- <Separator size="4" mb="4" />
138
+ {/* 插件配置折叠面板 */}
139
+ <PluginConfigForm
140
+ pluginName={plugin.name}
141
+ onSuccess={() => {
142
+ // 配置更新会通过 WebSocket 自动同步
143
+ }}
144
+ />
145
+
146
+ <Separator size="4" my="4" />
140
147
 
141
148
  {/* 统计概览 - 紧凑卡片 */}
142
149
  <Grid columns={{ initial: '2', sm: '3', md: '6' }} gap="2" mb="4">
@@ -183,7 +190,7 @@ export default function DashboardPluginDetail() {
183
190
  <Card size="1">
184
191
  <Flex direction="column" align="center" gap="1" p="2">
185
192
  <Icons.FileText className="w-4 h-4 text-cyan-600 dark:text-cyan-400" />
186
- <Text size="4" weight="bold">{plugin.statistics.schemaCount}</Text>
193
+ <Text size="4" weight="bold">{plugin.statistics.definitionCount}</Text>
187
194
  <Text size="1" color="gray">数据模型</Text>
188
195
  </Flex>
189
196
  </Card>
@@ -207,14 +214,7 @@ export default function DashboardPluginDetail() {
207
214
  <Flex direction="column" gap="1">
208
215
  <Flex align="center" gap="2">
209
216
  <Code size="2">{cmd.name}</Code>
210
- {cmd.alias.length > 0 && (
211
- <Text size="1" color="gray">别名: {cmd.alias.join(', ')}</Text>
212
- )}
213
217
  </Flex>
214
- <Text size="1" color="gray">{cmd.description}</Text>
215
- {cmd.examples.length > 0 && (
216
- <Text size="1" color="gray" className="italic">示例: {cmd.examples[0]}</Text>
217
- )}
218
218
  </Flex>
219
219
  </Box>
220
220
  ))}
@@ -330,22 +330,22 @@ export default function DashboardPluginDetail() {
330
330
  )}
331
331
 
332
332
  {/* 数据模型列表 */}
333
- {plugin.schemas.length > 0 && (
333
+ {plugin.definitions.length > 0 && (
334
334
  <Card size="2">
335
335
  <Flex direction="column" gap="2" p="3">
336
336
  <Flex align="center" gap="2">
337
337
  <Icons.FileText className="w-4 h-4 text-cyan-600 dark:text-cyan-400" />
338
338
  <Heading size="3">数据模型</Heading>
339
- <Badge size="1">{plugin.schemas.length}</Badge>
339
+ <Badge size="1">{plugin.definitions.length}</Badge>
340
340
  </Flex>
341
341
  <Separator size="4" />
342
342
  <Flex direction="column" gap="2" className="max-h-60 overflow-y-auto">
343
- {plugin.schemas.map((schema, index) => (
343
+ {plugin.definitions.map((definition, index) => (
344
344
  <Box key={index} className="rounded-lg bg-gray-50 dark:bg-gray-900 p-2">
345
345
  <Flex direction="column" gap="1">
346
- <Code size="2">{schema.name}</Code>
346
+ <Code size="2">{definition.name}</Code>
347
347
  <Text size="1" color="gray">
348
- 字段: {schema.fields.join(', ')}
348
+ 字段: {definition.fields.join(', ')}
349
349
  </Text>
350
350
  </Flex>
351
351
  </Box>
package/dist/index.js CHANGED
@@ -9,7 +9,6 @@ export * from './router';
9
9
  export * as Icons from 'lucide-react';
10
10
  // WebSocket
11
11
  export * from './websocket';
12
- export { useWebSocket } from './websocket/useWebSocket';
13
12
  export function cn(...inputs) {
14
13
  return twMerge(clsx(inputs));
15
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/client",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Zhin 客户端",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,13 +29,15 @@
29
29
  "app"
30
30
  ],
31
31
  "devDependencies": {
32
- "radix-ui": "^1.4.3",
33
32
  "@radix-ui/themes": "^3.2.1",
34
- "lucide-react": "^0.469.0",
35
33
  "@types/events": "^3.0.3",
36
34
  "@types/node": "^24.7.1",
37
35
  "@types/react": "^19.2.2",
38
- "@types/react-dom": "^19.2.1"
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"
39
41
  },
40
42
  "scripts": {
41
43
  "build": "tsc",
package/src/index.ts CHANGED
@@ -12,7 +12,6 @@ export * as Icons from 'lucide-react'
12
12
 
13
13
  // WebSocket
14
14
  export * from './websocket'
15
- export { useWebSocket } from './websocket/useWebSocket'
16
15
 
17
16
  export function cn(...inputs: ClassValue[]) {
18
17
  return twMerge(clsx(inputs))
@@ -108,4 +108,25 @@ export {
108
108
  unloadScript
109
109
  } from './reducers/script'
110
110
 
111
- export type { RouteMenuItem } from './reducers/route'
111
+ // 导出 Config actions selectors
112
+ export {
113
+ setConnected,
114
+ setLoading,
115
+ setError,
116
+ updateConfig,
117
+ updateSchema,
118
+ updateConfigs,
119
+ updateSchemas,
120
+ removeConfig,
121
+ clearConfigs,
122
+ selectConfig,
123
+ selectSchema,
124
+ selectConfigLoading,
125
+ selectConfigError,
126
+ selectConfigConnected,
127
+ selectAllConfigs,
128
+ selectAllSchemas
129
+ } from './reducers/config'
130
+
131
+ export type { RouteMenuItem } from './reducers/route'
132
+ export type { ConfigMessage } from './reducers/config'
@@ -0,0 +1,135 @@
1
+ /**
2
+ * 配置管理 Redux Slice
3
+ * 通过 WebSocket 管理插件配置
4
+ */
5
+
6
+ import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
7
+
8
+ export interface ConfigState {
9
+ // 所有插件的配置,按插件名分发
10
+ configs: Record<string, any>
11
+ // 所有插件的 Schema,按插件名分发
12
+ schemas: Record<string, any>
13
+ // 加载状态
14
+ loading: Record<string, boolean>
15
+ // 错误信息
16
+ errors: Record<string, string | null>
17
+ // WebSocket 连接状态
18
+ connected: boolean
19
+ }
20
+
21
+ const initialState: ConfigState = {
22
+ configs: {},
23
+ schemas: {},
24
+ loading: {},
25
+ errors: {},
26
+ connected: false
27
+ }
28
+
29
+ // WebSocket 消息类型
30
+ export interface ConfigMessage {
31
+ type: 'config:get' | 'config:set' | 'config:updated' | 'schema:get' | 'schema:updated'
32
+ pluginName: string
33
+ data?: any
34
+ error?: string
35
+ }
36
+
37
+ const configSlice = createSlice({
38
+ name: 'config',
39
+ initialState,
40
+ reducers: {
41
+ // WebSocket 连接状态
42
+ setConnected: (state, action: PayloadAction<boolean>) => {
43
+ state.connected = action.payload
44
+ },
45
+
46
+ // 设置配置加载状态
47
+ setLoading: (state, action: PayloadAction<{ pluginName: string; loading: boolean }>) => {
48
+ const { pluginName, loading } = action.payload
49
+ state.loading[pluginName] = loading
50
+ },
51
+
52
+ // 设置错误信息
53
+ setError: (state, action: PayloadAction<{ pluginName: string; error: string | null }>) => {
54
+ const { pluginName, error } = action.payload
55
+ state.errors[pluginName] = error
56
+ },
57
+
58
+ // 更新插件配置
59
+ updateConfig: (state, action: PayloadAction<{ pluginName: string; config: any }>) => {
60
+ const { pluginName, config } = action.payload
61
+ state.configs[pluginName] = config
62
+ state.loading[pluginName] = false
63
+ state.errors[pluginName] = null
64
+ },
65
+
66
+ // 更新插件 Schema
67
+ updateSchema: (state, action: PayloadAction<{ pluginName: string; schema: any }>) => {
68
+ const { pluginName, schema } = action.payload
69
+ state.schemas[pluginName] = schema
70
+ },
71
+
72
+ // 批量更新配置
73
+ updateConfigs: (state, action: PayloadAction<Record<string, any>>) => {
74
+ state.configs = { ...state.configs, ...action.payload }
75
+ },
76
+
77
+ // 批量更新 Schema
78
+ updateSchemas: (state, action: PayloadAction<Record<string, any>>) => {
79
+ state.schemas = { ...state.schemas, ...action.payload }
80
+ },
81
+
82
+ // 清除插件配置
83
+ removeConfig: (state, action: PayloadAction<string>) => {
84
+ const pluginName = action.payload
85
+ delete state.configs[pluginName]
86
+ delete state.schemas[pluginName]
87
+ delete state.loading[pluginName]
88
+ delete state.errors[pluginName]
89
+ },
90
+
91
+ // 清除所有配置
92
+ clearConfigs: (state) => {
93
+ state.configs = {}
94
+ state.schemas = {}
95
+ state.loading = {}
96
+ state.errors = {}
97
+ }
98
+ }
99
+ })
100
+
101
+ export const {
102
+ setConnected,
103
+ setLoading,
104
+ setError,
105
+ updateConfig,
106
+ updateSchema,
107
+ updateConfigs,
108
+ updateSchemas,
109
+ removeConfig,
110
+ clearConfigs
111
+ } = configSlice.actions
112
+
113
+ export default configSlice.reducer
114
+
115
+ // Selectors
116
+ export const selectConfig = (state: { config: ConfigState }, pluginName: string) =>
117
+ state.config.configs[pluginName]
118
+
119
+ export const selectSchema = (state: { config: ConfigState }, pluginName: string) =>
120
+ state.config.schemas[pluginName]
121
+
122
+ export const selectConfigLoading = (state: { config: ConfigState }, pluginName: string) =>
123
+ state.config.loading[pluginName] || false
124
+
125
+ export const selectConfigError = (state: { config: ConfigState }, pluginName: string) =>
126
+ state.config.errors[pluginName] || null
127
+
128
+ export const selectConfigConnected = (state: { config: ConfigState }) =>
129
+ state.config.connected
130
+
131
+ export const selectAllConfigs = (state: { config: ConfigState }) =>
132
+ state.config.configs
133
+
134
+ export const selectAllSchemas = (state: { config: ConfigState }) =>
135
+ state.config.schemas
@@ -2,15 +2,18 @@ import { Reducer } from "@reduxjs/toolkit"
2
2
  import ui from "./ui"
3
3
  import route from "./route"
4
4
  import script from "./script"
5
+ import config from "./config"
5
6
 
6
7
  export interface Reducers {
7
8
  ui: ReturnType<typeof ui>
8
9
  route: ReturnType<typeof route>
9
10
  script: ReturnType<typeof script>
11
+ config: ReturnType<typeof config>
10
12
  }
11
13
 
12
14
  export const reducers: Record<keyof Reducers, Reducer<any, any>> = {
13
15
  ui,
14
16
  route,
15
- script
17
+ script,
18
+ config
16
19
  }
@@ -0,0 +1,280 @@
1
+ /**
2
+ * WebSocket React Hooks
3
+ * 提供在 React 组件中使用 WebSocket 功能的便捷接口
4
+ */
5
+
6
+ import { useCallback, useEffect, useMemo, useState } from 'react'
7
+ import {
8
+ useSelector,
9
+ useDispatch,
10
+ selectConfig,
11
+ selectSchema,
12
+ selectConfigLoading,
13
+ selectConfigError,
14
+ selectConfigConnected,
15
+ selectAllConfigs,
16
+ selectAllSchemas,
17
+ setLoading,
18
+ setError
19
+ } from '../store'
20
+ import { getWebSocketManager } from './instance'
21
+ import type { UseConfigOptions, UseWebSocketOptions, ConnectionState } from './types'
22
+
23
+ // ============================================================================
24
+ // WebSocket 连接 Hook
25
+ // ============================================================================
26
+
27
+ /**
28
+ * WebSocket 连接状态和基础功能 Hook
29
+ */
30
+ export function useWebSocket(options: UseWebSocketOptions = {}) {
31
+ const { autoConnect = true } = options
32
+
33
+ const wsManager = getWebSocketManager()
34
+
35
+ // 从 Redux 获取状态
36
+ const connected = useSelector(selectConfigConnected)
37
+ const entries = useSelector((state: any) => state.script?.entries || [])
38
+ const loadedScripts = useSelector((state: any) => state.script?.loadedScripts || [])
39
+
40
+ // 连接控制
41
+ const connect = useCallback(() => {
42
+ wsManager.connect()
43
+ }, [wsManager])
44
+
45
+ const disconnect = useCallback(() => {
46
+ wsManager.disconnect()
47
+ }, [wsManager])
48
+
49
+ // 消息发送
50
+ const send = useCallback((message: any) => {
51
+ wsManager.send(message)
52
+ }, [wsManager])
53
+
54
+ const sendRequest = useCallback(async <T = any>(message: any): Promise<T> => {
55
+ // 临时使用旧方法,后续完善
56
+ return (wsManager as any).sendRequest(message)
57
+ }, [wsManager])
58
+
59
+ // 状态查询
60
+ const isConnected = useCallback(() => {
61
+ return wsManager.isConnected()
62
+ }, [wsManager])
63
+
64
+ const getState = useCallback(() => {
65
+ // 临时返回连接状态,后续完善
66
+ return wsManager.isConnected() ? 'connected' : 'disconnected'
67
+ }, [wsManager])
68
+
69
+ // 自动连接
70
+ useEffect(() => {
71
+ if (autoConnect && !connected) {
72
+ connect()
73
+ }
74
+ }, [autoConnect, connected, connect])
75
+
76
+ return {
77
+ // 状态
78
+ connected,
79
+ entries,
80
+ loadedScripts,
81
+
82
+ // 方法
83
+ connect,
84
+ disconnect,
85
+ send,
86
+ sendRequest,
87
+ isConnected,
88
+ getState,
89
+
90
+ // WebSocket 实例(高级用法)
91
+ manager: wsManager
92
+ }
93
+ }
94
+
95
+ // ============================================================================
96
+ // 配置管理 Hook
97
+ // ============================================================================
98
+
99
+ /**
100
+ * 插件配置管理 Hook
101
+ */
102
+ export function useConfig(pluginName: string, options: UseConfigOptions = {}) {
103
+ const { autoLoad = true, autoLoadSchema = true } = options
104
+
105
+ const dispatch = useDispatch()
106
+ const wsManager = getWebSocketManager()
107
+
108
+ // 状态选择器
109
+ const config = useSelector((state: any) => selectConfig(state, pluginName))
110
+ const schema = useSelector((state: any) => selectSchema(state, pluginName))
111
+ const loading = useSelector((state: any) => selectConfigLoading(state, pluginName))
112
+ const error = useSelector((state: any) => selectConfigError(state, pluginName))
113
+ const connected = useSelector(selectConfigConnected)
114
+
115
+ // 配置操作
116
+ const getConfig = useCallback(async () => {
117
+ dispatch(setLoading({ pluginName, loading: true }))
118
+ dispatch(setError({ pluginName, error: null }))
119
+
120
+ try {
121
+ const result = await wsManager.getConfig(pluginName)
122
+ return result
123
+ } catch (error) {
124
+ console.log('getConfig',error)
125
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
126
+ dispatch(setError({ pluginName, error: errorMessage }))
127
+ throw error
128
+ } finally {
129
+ dispatch(setLoading({ pluginName, loading: false }))
130
+ }
131
+ }, [pluginName, wsManager, dispatch])
132
+
133
+ const setConfig = useCallback(async (newConfig: any) => {
134
+ dispatch(setLoading({ pluginName, loading: true }))
135
+ dispatch(setError({ pluginName, error: null }))
136
+
137
+ try {
138
+ await wsManager.setConfig(pluginName, newConfig)
139
+ } catch (error) {
140
+ console.log('setConfig',error)
141
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
142
+ dispatch(setError({ pluginName, error: errorMessage }))
143
+ throw error
144
+ } finally {
145
+ dispatch(setLoading({ pluginName, loading: false }))
146
+ }
147
+ }, [pluginName, wsManager, dispatch])
148
+
149
+ const getSchema = useCallback(async () => {
150
+ try {
151
+ return await wsManager.getSchema(pluginName)
152
+ } catch (error) {
153
+ console.error(`Failed to get schema for plugin ${pluginName}:`, error)
154
+ throw error
155
+ }
156
+ }, [pluginName, wsManager])
157
+
158
+ // 重新加载配置和 Schema
159
+ const reload = useCallback(async () => {
160
+ const promises = []
161
+
162
+ if (autoLoad) {
163
+ promises.push(getConfig())
164
+ }
165
+
166
+ if (autoLoadSchema) {
167
+ promises.push(getSchema())
168
+ }
169
+
170
+ await Promise.all(promises)
171
+ }, [autoLoad, autoLoadSchema, getConfig, getSchema])
172
+
173
+ // 自动加载
174
+ useEffect(() => {
175
+ if (connected && autoLoad && !config && !loading) {
176
+ getConfig().catch(console.error)
177
+ }
178
+ }, [connected, autoLoad, config, loading, getConfig])
179
+
180
+ useEffect(() => {
181
+ if (connected && autoLoadSchema && !schema) {
182
+ getSchema().catch(console.error)
183
+ }
184
+ }, [connected, autoLoadSchema, schema, getSchema])
185
+
186
+ return useMemo(() => ({
187
+ // 状态
188
+ config,
189
+ schema,
190
+ loading,
191
+ error,
192
+ connected,
193
+
194
+ // 方法
195
+ getConfig,
196
+ setConfig,
197
+ getSchema,
198
+ reload
199
+ }), [config, schema, loading, error, connected, getConfig, setConfig, getSchema, reload])
200
+ }
201
+
202
+ // ============================================================================
203
+ // 批量配置管理 Hook
204
+ // ============================================================================
205
+
206
+ /**
207
+ * 所有配置管理 Hook
208
+ */
209
+ export function useAllConfigs() {
210
+ const wsManager = getWebSocketManager()
211
+
212
+ const allConfigs = useSelector(selectAllConfigs)
213
+ const allSchemas = useSelector(selectAllSchemas)
214
+ const connected = useSelector(selectConfigConnected)
215
+
216
+ const refreshAll = useCallback(async () => {
217
+ try {
218
+ const [configs, schemas] = await Promise.all([
219
+ (wsManager as any).getAllConfigs(),
220
+ (wsManager as any).getAllSchemas()
221
+ ])
222
+
223
+ return { configs, schemas }
224
+ } catch (error) {
225
+ console.error('Failed to refresh all configs:', error)
226
+ throw error
227
+ }
228
+ }, [wsManager])
229
+
230
+ return useMemo(() => ({
231
+ configs: allConfigs,
232
+ schemas: allSchemas,
233
+ connected,
234
+ refreshAll
235
+ }), [allConfigs, allSchemas, connected, refreshAll])
236
+ }
237
+
238
+ // ============================================================================
239
+ // 高级 Hook
240
+ // ============================================================================
241
+
242
+ /**
243
+ * WebSocket 连接状态监听 Hook
244
+ */
245
+ export function useWebSocketState() {
246
+ const wsManager = getWebSocketManager()
247
+ const [state, setState] = useState(wsManager.getState())
248
+
249
+ useEffect(() => {
250
+ const checkState = () => {
251
+ const newState = wsManager.getState()
252
+ if (newState !== state) {
253
+ setState(newState)
254
+ }
255
+ }
256
+
257
+ const interval = setInterval(checkState, 1000)
258
+ return () => clearInterval(interval)
259
+ }, [wsManager, state])
260
+
261
+ return state
262
+ }
263
+
264
+ /**
265
+ * WebSocket 消息监听 Hook
266
+ */
267
+ export function useWebSocketMessages<T = any>(
268
+ filter?: (message: any) => boolean,
269
+ handler?: (message: T) => void
270
+ ) {
271
+ const [messages, setMessages] = useState<T[]>([])
272
+
273
+ useEffect(() => {
274
+ const wsManager = getWebSocketManager()
275
+ // 这里需要扩展 WebSocketManager 来支持消息监听
276
+ // 暂时留空,后续可以扩展
277
+ }, [filter, handler])
278
+
279
+ return messages
280
+ }