@zhin.js/console 1.0.20 → 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.
- package/CHANGELOG.md +24 -0
- package/README.md +4 -4
- package/client/components.json +17 -0
- package/client/index.html +1 -1
- package/client/src/components/PluginConfigForm/BasicFieldRenderers.tsx +89 -180
- package/client/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +97 -200
- package/client/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +31 -70
- package/client/src/components/PluginConfigForm/FieldRenderer.tsx +27 -77
- package/client/src/components/PluginConfigForm/NestedFieldRenderer.tsx +33 -53
- package/client/src/components/PluginConfigForm/index.tsx +71 -173
- package/client/src/components/ui/accordion.tsx +54 -0
- package/client/src/components/ui/alert.tsx +62 -0
- package/client/src/components/ui/avatar.tsx +41 -0
- package/client/src/components/ui/badge.tsx +32 -0
- package/client/src/components/ui/button.tsx +50 -0
- package/client/src/components/ui/card.tsx +50 -0
- package/client/src/components/ui/checkbox.tsx +25 -0
- package/client/src/components/ui/dialog.tsx +87 -0
- package/client/src/components/ui/dropdown-menu.tsx +97 -0
- package/client/src/components/ui/input.tsx +21 -0
- package/client/src/components/ui/scroll-area.tsx +43 -0
- package/client/src/components/ui/select.tsx +127 -0
- package/client/src/components/ui/separator.tsx +23 -0
- package/client/src/components/ui/skeleton.tsx +12 -0
- package/client/src/components/ui/switch.tsx +26 -0
- package/client/src/components/ui/tabs.tsx +52 -0
- package/client/src/components/ui/textarea.tsx +20 -0
- package/client/src/components/ui/tooltip.tsx +27 -0
- package/client/src/layouts/dashboard.tsx +91 -221
- package/client/src/main.tsx +38 -42
- package/client/src/pages/dashboard-bots.tsx +91 -137
- package/client/src/pages/dashboard-home.tsx +133 -204
- package/client/src/pages/dashboard-logs.tsx +125 -196
- package/client/src/pages/dashboard-plugin-detail.tsx +261 -329
- package/client/src/pages/dashboard-plugins.tsx +108 -105
- package/client/src/style.css +156 -865
- package/client/src/theme/index.ts +60 -35
- package/client/tailwind.config.js +78 -69
- package/dist/client.js +1 -1
- package/dist/cva.js +47 -0
- package/dist/index.html +1 -1
- package/dist/index.js +6 -6
- package/dist/react-router.js +7121 -5585
- package/dist/react.js +192 -149
- package/dist/style.css +2 -2
- package/lib/bin.js +2 -2
- package/lib/build.js +2 -2
- package/lib/index.d.ts +0 -3
- package/lib/index.js +160 -205
- package/lib/transform.d.ts +26 -0
- package/lib/transform.js +78 -0
- package/lib/websocket.d.ts +0 -1
- package/package.json +8 -7
- package/dist/radix-ui-themes.js +0 -9305
- package/lib/dev.d.ts +0 -18
- package/lib/dev.js +0 -87
|
@@ -1,53 +1,50 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
2
|
import { useParams, useNavigate } from 'react-router'
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { ArrowLeft, AlertCircle, Package, Terminal, Box as IconBox, Layers, Clock, Database, Brain, Wrench, Shield, Settings, Plug, Server, type LucideIcon } from 'lucide-react'
|
|
4
|
+
import { PluginConfigForm } from '../components/PluginConfigForm'
|
|
5
|
+
import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/card'
|
|
6
|
+
import { Badge } from '../components/ui/badge'
|
|
7
|
+
import { Button } from '../components/ui/button'
|
|
8
|
+
import { Alert, AlertDescription } from '../components/ui/alert'
|
|
9
|
+
import { Skeleton } from '../components/ui/skeleton'
|
|
10
|
+
import { Separator } from '../components/ui/separator'
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
|
|
13
|
+
/** Feature 序列化格式(与后端 FeatureJSON 一致) */
|
|
14
|
+
interface FeatureJSON {
|
|
15
|
+
name: string
|
|
16
|
+
icon: string
|
|
17
|
+
desc: string
|
|
18
|
+
count: number
|
|
19
|
+
items: any[]
|
|
20
|
+
}
|
|
8
21
|
|
|
9
22
|
interface PluginDetail {
|
|
10
23
|
name: string
|
|
11
24
|
filename: string
|
|
12
25
|
status: 'active' | 'inactive'
|
|
13
26
|
description: string
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
id: string
|
|
36
|
-
pattern: string
|
|
37
|
-
running: boolean
|
|
38
|
-
}>
|
|
39
|
-
definitions: Array<{
|
|
40
|
-
name: string
|
|
41
|
-
fields: string[]
|
|
42
|
-
}>
|
|
43
|
-
statistics: {
|
|
44
|
-
commandCount: number
|
|
45
|
-
componentCount: number
|
|
46
|
-
middlewareCount: number
|
|
47
|
-
contextCount: number
|
|
48
|
-
cronCount: number
|
|
49
|
-
definitionCount: number
|
|
50
|
-
}
|
|
27
|
+
features: FeatureJSON[]
|
|
28
|
+
contexts: Array<{ name: string }>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 根据后端返回的 icon 名称映射到 lucide-react 图标组件 */
|
|
32
|
+
const iconMap: Record<string, LucideIcon> = {
|
|
33
|
+
Terminal,
|
|
34
|
+
Box: IconBox,
|
|
35
|
+
Layers,
|
|
36
|
+
Clock,
|
|
37
|
+
Brain,
|
|
38
|
+
Wrench,
|
|
39
|
+
Database,
|
|
40
|
+
Shield,
|
|
41
|
+
Settings,
|
|
42
|
+
Plug,
|
|
43
|
+
Server,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getIcon(iconName: string): LucideIcon {
|
|
47
|
+
return iconMap[iconName] || Package
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
export default function DashboardPluginDetail() {
|
|
@@ -58,23 +55,16 @@ export default function DashboardPluginDetail() {
|
|
|
58
55
|
const [error, setError] = useState<string | null>(null)
|
|
59
56
|
|
|
60
57
|
useEffect(() => {
|
|
61
|
-
if (name)
|
|
62
|
-
fetchPluginDetail(name)
|
|
63
|
-
}
|
|
58
|
+
if (name) fetchPluginDetail(name)
|
|
64
59
|
}, [name])
|
|
65
60
|
|
|
66
61
|
const fetchPluginDetail = async (pluginName: string) => {
|
|
67
62
|
try {
|
|
68
63
|
const res = await fetch(`/api/plugins/${encodeURIComponent(pluginName)}`, { credentials: 'include' })
|
|
69
64
|
if (!res.ok) throw new Error('API 请求失败')
|
|
70
|
-
|
|
71
65
|
const data = await res.json()
|
|
72
|
-
if (data.success) {
|
|
73
|
-
|
|
74
|
-
setError(null)
|
|
75
|
-
} else {
|
|
76
|
-
throw new Error('数据格式错误')
|
|
77
|
-
}
|
|
66
|
+
if (data.success) { setPlugin(data.data); setError(null) }
|
|
67
|
+
else throw new Error('数据格式错误')
|
|
78
68
|
} catch (err) {
|
|
79
69
|
setError((err as Error).message)
|
|
80
70
|
} finally {
|
|
@@ -82,307 +72,249 @@ export default function DashboardPluginDetail() {
|
|
|
82
72
|
}
|
|
83
73
|
}
|
|
84
74
|
|
|
85
|
-
|
|
86
|
-
|
|
87
75
|
if (loading) {
|
|
88
76
|
return (
|
|
89
|
-
<
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
</Flex>
|
|
77
|
+
<div className="space-y-4">
|
|
78
|
+
<Skeleton className="h-8 w-24" />
|
|
79
|
+
<div className="flex items-center gap-3"><Skeleton className="h-12 w-12 rounded-xl" /><div><Skeleton className="h-6 w-48" /><Skeleton className="h-4 w-64 mt-1" /></div></div>
|
|
80
|
+
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-2">{[...Array(6)].map((_, i) => <Skeleton key={i} className="h-20" />)}</div>
|
|
81
|
+
</div>
|
|
95
82
|
)
|
|
96
83
|
}
|
|
97
84
|
|
|
98
85
|
if (error || !plugin) {
|
|
99
86
|
return (
|
|
100
|
-
<
|
|
101
|
-
<Button variant="ghost" onClick={() => navigate('/plugins')}
|
|
102
|
-
<ArrowLeft className="w-4 h-4" />
|
|
103
|
-
返回
|
|
87
|
+
<div>
|
|
88
|
+
<Button variant="ghost" onClick={() => navigate('/plugins')} className="mb-4">
|
|
89
|
+
<ArrowLeft className="w-4 h-4 mr-1" /> 返回
|
|
104
90
|
</Button>
|
|
105
|
-
<
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
加载失败: {error || '插件不存在'}
|
|
111
|
-
</Callout.Text>
|
|
112
|
-
</Callout.Root>
|
|
113
|
-
</Box>
|
|
91
|
+
<Alert variant="destructive">
|
|
92
|
+
<AlertCircle className="h-4 w-4" />
|
|
93
|
+
<AlertDescription>加载失败: {error || '插件不存在'}</AlertDescription>
|
|
94
|
+
</Alert>
|
|
95
|
+
</div>
|
|
114
96
|
)
|
|
115
97
|
}
|
|
116
98
|
|
|
117
99
|
return (
|
|
118
|
-
<
|
|
119
|
-
{/*
|
|
120
|
-
<
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
返回
|
|
124
|
-
</Button>
|
|
125
|
-
|
|
126
|
-
<Flex align="center" gap="3">
|
|
127
|
-
<Flex align="center" justify="center" className="w-12 h-12 rounded-xl bg-blue-500/10 dark:bg-blue-400/10">
|
|
128
|
-
<Package className="w-6 h-6 text-blue-600 dark:text-blue-400" />
|
|
129
|
-
</Flex>
|
|
130
|
-
<Flex direction="column" gap="1">
|
|
131
|
-
<Flex align="center" gap="2">
|
|
132
|
-
<Heading size="5">{plugin.name}</Heading>
|
|
133
|
-
<Badge color={plugin.status === 'active' ? 'green' : 'gray'} size="1">
|
|
134
|
-
{plugin.status === 'active' ? '运行中' : '已停止'}
|
|
135
|
-
</Badge>
|
|
136
|
-
</Flex>
|
|
137
|
-
<Text size="2" color="gray">{plugin.description || '暂无描述'}</Text>
|
|
138
|
-
</Flex>
|
|
139
|
-
</Flex>
|
|
140
|
-
</Flex>
|
|
141
|
-
|
|
142
|
-
{/* 插件配置折叠面板 */}
|
|
143
|
-
<PluginConfigForm
|
|
144
|
-
pluginName={plugin.name}
|
|
145
|
-
onSuccess={() => {
|
|
146
|
-
// 配置更新会通过 WebSocket 自动同步
|
|
147
|
-
}}
|
|
148
|
-
/>
|
|
100
|
+
<div className="space-y-4">
|
|
101
|
+
{/* Header */}
|
|
102
|
+
<Button variant="ghost" size="sm" onClick={() => navigate('/plugins')}>
|
|
103
|
+
<ArrowLeft className="w-4 h-4 mr-1" /> 返回
|
|
104
|
+
</Button>
|
|
149
105
|
|
|
150
|
-
<
|
|
106
|
+
<div className="flex items-center gap-3">
|
|
107
|
+
<div className="flex items-center justify-center w-12 h-12 rounded-xl bg-secondary">
|
|
108
|
+
<Package className="w-6 h-6" />
|
|
109
|
+
</div>
|
|
110
|
+
<div>
|
|
111
|
+
<div className="flex items-center gap-2">
|
|
112
|
+
<h1 className="text-xl font-bold">{plugin.name}</h1>
|
|
113
|
+
<Badge variant={plugin.status === 'active' ? 'success' : 'secondary'}>
|
|
114
|
+
{plugin.status === 'active' ? '运行中' : '已停止'}
|
|
115
|
+
</Badge>
|
|
116
|
+
</div>
|
|
117
|
+
<p className="text-sm text-muted-foreground">{plugin.description || '暂无描述'}</p>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
151
120
|
|
|
152
|
-
{/*
|
|
153
|
-
<
|
|
154
|
-
<Card size="1">
|
|
155
|
-
<Flex direction="column" align="center" gap="1" p="2">
|
|
156
|
-
<Terminal className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
|
157
|
-
<Text size="4" weight="bold">{plugin.statistics.commandCount}</Text>
|
|
158
|
-
<Text size="1" color="gray">命令</Text>
|
|
159
|
-
</Flex>
|
|
160
|
-
</Card>
|
|
121
|
+
{/* Config form */}
|
|
122
|
+
<PluginConfigForm pluginName={plugin.name} onSuccess={() => {}} />
|
|
161
123
|
|
|
162
|
-
|
|
163
|
-
<Flex direction="column" align="center" gap="1" p="2">
|
|
164
|
-
<IconBox className="w-4 h-4 text-green-600 dark:text-green-400" />
|
|
165
|
-
<Text size="4" weight="bold">{plugin.statistics.componentCount}</Text>
|
|
166
|
-
<Text size="1" color="gray">组件</Text>
|
|
167
|
-
</Flex>
|
|
168
|
-
</Card>
|
|
124
|
+
<Separator />
|
|
169
125
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
126
|
+
{/* Stats grid - 动态渲染 features 摘要 */}
|
|
127
|
+
{plugin.features.length > 0 && (
|
|
128
|
+
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-2">
|
|
129
|
+
{plugin.features.map((feature) => {
|
|
130
|
+
const Icon = getIcon(feature.icon)
|
|
131
|
+
return (
|
|
132
|
+
<Card key={feature.name}>
|
|
133
|
+
<CardContent className="flex flex-col items-center gap-1 p-3">
|
|
134
|
+
<Icon className="w-4 h-4 text-muted-foreground" />
|
|
135
|
+
<span className="text-2xl font-bold">{feature.count}</span>
|
|
136
|
+
<span className="text-xs text-muted-foreground">{feature.desc}</span>
|
|
137
|
+
</CardContent>
|
|
138
|
+
</Card>
|
|
139
|
+
)
|
|
140
|
+
})}
|
|
141
|
+
{/* Contexts card */}
|
|
142
|
+
{plugin.contexts.length > 0 && (
|
|
143
|
+
<Card>
|
|
144
|
+
<CardContent className="flex flex-col items-center gap-1 p-3">
|
|
145
|
+
<Database className="w-4 h-4 text-muted-foreground" />
|
|
146
|
+
<span className="text-2xl font-bold">{plugin.contexts.length}</span>
|
|
147
|
+
<span className="text-xs text-muted-foreground">上下文</span>
|
|
148
|
+
</CardContent>
|
|
149
|
+
</Card>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
177
153
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
</Flex>
|
|
184
|
-
</Card>
|
|
154
|
+
{/* Detail sections - 动态渲染每个 Feature 的 items */}
|
|
155
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
156
|
+
{plugin.features.map((feature) => {
|
|
157
|
+
if (feature.items.length === 0) return null
|
|
158
|
+
const Icon = getIcon(feature.icon)
|
|
185
159
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
{/* 命令列表 */}
|
|
206
|
-
{plugin.commands.length > 0 && (
|
|
207
|
-
<Card size="2">
|
|
208
|
-
<Flex direction="column" gap="2" p="3">
|
|
209
|
-
<Flex align="center" gap="2">
|
|
210
|
-
<Terminal className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
|
211
|
-
<Heading size="3">命令</Heading>
|
|
212
|
-
<Badge size="1">{plugin.commands.length}</Badge>
|
|
213
|
-
</Flex>
|
|
214
|
-
<Separator size="4" />
|
|
215
|
-
<Flex direction="column" gap="2" className="max-h-60 overflow-y-auto">
|
|
216
|
-
{plugin.commands.map((cmd, index) => (
|
|
217
|
-
<Box key={index} className="rounded-lg bg-gray-50 dark:bg-gray-900 p-3">
|
|
218
|
-
<Flex direction="column" gap="2">
|
|
219
|
-
<Code size="2" weight="bold">{cmd.name}</Code>
|
|
220
|
-
|
|
221
|
-
{cmd.desc && cmd.desc.length > 0 && (
|
|
222
|
-
<Flex direction="column" gap="1">
|
|
223
|
-
{cmd.desc.map((desc, i) => (
|
|
224
|
-
<Text key={i} size="1" color="gray">{desc}</Text>
|
|
225
|
-
))}
|
|
226
|
-
</Flex>
|
|
227
|
-
)}
|
|
228
|
-
|
|
229
|
-
{cmd.usage && cmd.usage.length > 0 && (
|
|
230
|
-
<Flex direction="column" gap="1">
|
|
231
|
-
<Text size="1" weight="bold" color="blue">用法:</Text>
|
|
232
|
-
{cmd.usage.map((usage, i) => (
|
|
233
|
-
<Code key={i} size="1" variant="soft">{usage}</Code>
|
|
234
|
-
))}
|
|
235
|
-
</Flex>
|
|
236
|
-
)}
|
|
237
|
-
|
|
238
|
-
{cmd.examples && cmd.examples.length > 0 && (
|
|
239
|
-
<Flex direction="column" gap="1">
|
|
240
|
-
<Text size="1" weight="bold" color="green">示例:</Text>
|
|
241
|
-
{cmd.examples.map((example, i) => (
|
|
242
|
-
<Code key={i} size="1" variant="soft" color="green">{example}</Code>
|
|
243
|
-
))}
|
|
244
|
-
</Flex>
|
|
245
|
-
)}
|
|
246
|
-
</Flex>
|
|
247
|
-
</Box>
|
|
248
|
-
))}
|
|
249
|
-
</Flex>
|
|
250
|
-
</Flex>
|
|
251
|
-
</Card>
|
|
252
|
-
)}
|
|
160
|
+
return (
|
|
161
|
+
<Card key={feature.name}>
|
|
162
|
+
<CardHeader className="pb-2">
|
|
163
|
+
<div className="flex items-center gap-2">
|
|
164
|
+
<Icon className="w-4 h-4 text-muted-foreground" />
|
|
165
|
+
<CardTitle className="text-base">{feature.desc}</CardTitle>
|
|
166
|
+
<Badge variant="secondary">{feature.count}</Badge>
|
|
167
|
+
</div>
|
|
168
|
+
</CardHeader>
|
|
169
|
+
<CardContent>
|
|
170
|
+
<div className="max-h-60 overflow-y-auto pr-1 space-y-2">
|
|
171
|
+
{feature.items.map((item, index) => (
|
|
172
|
+
<FeatureItemCard key={index} featureName={feature.name} item={item} />
|
|
173
|
+
))}
|
|
174
|
+
</div>
|
|
175
|
+
</CardContent>
|
|
176
|
+
</Card>
|
|
177
|
+
)
|
|
178
|
+
})}
|
|
253
179
|
|
|
254
|
-
{/*
|
|
255
|
-
{plugin.components.length > 0 && (
|
|
256
|
-
<Card size="2">
|
|
257
|
-
<Flex direction="column" gap="2" p="3">
|
|
258
|
-
<Flex align="center" gap="2">
|
|
259
|
-
<Box className="w-4 h-4 text-green-600 dark:text-green-400" />
|
|
260
|
-
<Heading size="3">组件</Heading>
|
|
261
|
-
<Badge size="1">{plugin.components.length}</Badge>
|
|
262
|
-
</Flex>
|
|
263
|
-
<Separator size="4" />
|
|
264
|
-
<Flex direction="column" gap="2" className="max-h-60 overflow-y-auto">
|
|
265
|
-
{plugin.components.map((comp, index) => (
|
|
266
|
-
<Box key={index} className="rounded-lg bg-gray-50 dark:bg-gray-900 p-2">
|
|
267
|
-
<Flex direction="column" gap="1">
|
|
268
|
-
<Flex align="center" gap="2">
|
|
269
|
-
<Code size="2">{comp.name}</Code>
|
|
270
|
-
<Badge size="1" variant="soft">{comp.type}</Badge>
|
|
271
|
-
</Flex>
|
|
272
|
-
<Text size="1" color="gray">
|
|
273
|
-
属性数: {Object.keys(comp.props).length}
|
|
274
|
-
</Text>
|
|
275
|
-
</Flex>
|
|
276
|
-
</Box>
|
|
277
|
-
))}
|
|
278
|
-
</Flex>
|
|
279
|
-
</Flex>
|
|
280
|
-
</Card>
|
|
281
|
-
)}
|
|
282
|
-
|
|
283
|
-
{/* 中间件列表 */}
|
|
284
|
-
{plugin.middlewares.length > 0 && (
|
|
285
|
-
<Card size="2">
|
|
286
|
-
<Flex direction="column" gap="2" p="3">
|
|
287
|
-
<Flex align="center" gap="2">
|
|
288
|
-
<Layers className="w-4 h-4 text-purple-600 dark:text-purple-400" />
|
|
289
|
-
<Heading size="3">中间件</Heading>
|
|
290
|
-
<Badge size="1">{plugin.middlewares.length}</Badge>
|
|
291
|
-
</Flex>
|
|
292
|
-
<Separator size="4" />
|
|
293
|
-
<Flex direction="column" gap="2" className="max-h-60 overflow-y-auto">
|
|
294
|
-
{plugin.middlewares.map((mw, index) => (
|
|
295
|
-
<Box key={index} className="rounded-lg bg-gray-50 dark:bg-gray-900 p-2">
|
|
296
|
-
<Flex align="center" gap="2">
|
|
297
|
-
<Code size="2">{mw.id}</Code>
|
|
298
|
-
<Badge size="1" variant="soft">{mw.type}</Badge>
|
|
299
|
-
</Flex>
|
|
300
|
-
</Box>
|
|
301
|
-
))}
|
|
302
|
-
</Flex>
|
|
303
|
-
</Flex>
|
|
304
|
-
</Card>
|
|
305
|
-
)}
|
|
306
|
-
|
|
307
|
-
{/* 上下文列表 */}
|
|
180
|
+
{/* Contexts section */}
|
|
308
181
|
{plugin.contexts.length > 0 && (
|
|
309
|
-
<Card
|
|
310
|
-
<
|
|
311
|
-
<
|
|
312
|
-
<Database className="w-4 h-4 text-
|
|
313
|
-
<
|
|
314
|
-
<Badge
|
|
315
|
-
</
|
|
316
|
-
|
|
317
|
-
|
|
182
|
+
<Card>
|
|
183
|
+
<CardHeader className="pb-2">
|
|
184
|
+
<div className="flex items-center gap-2">
|
|
185
|
+
<Database className="w-4 h-4 text-muted-foreground" />
|
|
186
|
+
<CardTitle className="text-base">上下文</CardTitle>
|
|
187
|
+
<Badge variant="secondary">{plugin.contexts.length}</Badge>
|
|
188
|
+
</div>
|
|
189
|
+
</CardHeader>
|
|
190
|
+
<CardContent>
|
|
191
|
+
<div className="max-h-60 overflow-y-auto pr-1 space-y-2">
|
|
318
192
|
{plugin.contexts.map((ctx, index) => (
|
|
319
|
-
<
|
|
320
|
-
<
|
|
321
|
-
|
|
322
|
-
<Text size="1" color="gray">{ctx.description}</Text>
|
|
323
|
-
</Flex>
|
|
324
|
-
</Box>
|
|
193
|
+
<div key={index} className="rounded-md bg-muted/50 p-2">
|
|
194
|
+
<code className="text-sm">{ctx.name}</code>
|
|
195
|
+
</div>
|
|
325
196
|
))}
|
|
326
|
-
</
|
|
327
|
-
</
|
|
197
|
+
</div>
|
|
198
|
+
</CardContent>
|
|
328
199
|
</Card>
|
|
329
200
|
)}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
{plugin.crons.length > 0 && (
|
|
333
|
-
<Card size="2">
|
|
334
|
-
<Flex direction="column" gap="2" p="3">
|
|
335
|
-
<Flex align="center" gap="2">
|
|
336
|
-
<Clock className="w-4 h-4 text-amber-600 dark:text-amber-400" />
|
|
337
|
-
<Heading size="3">定时任务</Heading>
|
|
338
|
-
<Badge size="1">{plugin.crons.length}</Badge>
|
|
339
|
-
</Flex>
|
|
340
|
-
<Separator size="4" />
|
|
341
|
-
<Flex direction="column" gap="2" className="max-h-60 overflow-y-auto">
|
|
342
|
-
{plugin.crons.map((cron, index) => (
|
|
343
|
-
<Box key={index} className="rounded-lg bg-gray-50 dark:bg-gray-900 p-2">
|
|
344
|
-
<Flex justify="between" align="center">
|
|
345
|
-
<Flex direction="column" gap="1">
|
|
346
|
-
<Code size="2">{cron.id}</Code>
|
|
347
|
-
<Text size="1" color="gray">{cron.pattern}</Text>
|
|
348
|
-
</Flex>
|
|
349
|
-
<Badge color={cron.running ? 'green' : 'gray'} size="1">
|
|
350
|
-
{cron.running ? '运行中' : '已停止'}
|
|
351
|
-
</Badge>
|
|
352
|
-
</Flex>
|
|
353
|
-
</Box>
|
|
354
|
-
))}
|
|
355
|
-
</Flex>
|
|
356
|
-
</Flex>
|
|
357
|
-
</Card>
|
|
358
|
-
)}
|
|
359
|
-
|
|
360
|
-
{/* 数据模型列表 */}
|
|
361
|
-
{plugin.definitions.length > 0 && (
|
|
362
|
-
<Card size="2">
|
|
363
|
-
<Flex direction="column" gap="2" p="3">
|
|
364
|
-
<Flex align="center" gap="2">
|
|
365
|
-
<FileText className="w-4 h-4 text-cyan-600 dark:text-cyan-400" />
|
|
366
|
-
<Heading size="3">数据模型</Heading>
|
|
367
|
-
<Badge size="1">{plugin.definitions.length}</Badge>
|
|
368
|
-
</Flex>
|
|
369
|
-
<Separator size="4" />
|
|
370
|
-
<Flex direction="column" gap="2" className="max-h-60 overflow-y-auto">
|
|
371
|
-
{plugin.definitions.map((definition, index) => (
|
|
372
|
-
<Box key={index} className="rounded-lg bg-gray-50 dark:bg-gray-900 p-2">
|
|
373
|
-
<Flex direction="column" gap="1">
|
|
374
|
-
<Code size="2">{definition.name}</Code>
|
|
375
|
-
<Text size="1" color="gray">
|
|
376
|
-
字段: {definition.fields.join(', ')}
|
|
377
|
-
</Text>
|
|
378
|
-
</Flex>
|
|
379
|
-
</Box>
|
|
380
|
-
))}
|
|
381
|
-
</Flex>
|
|
382
|
-
</Flex>
|
|
383
|
-
</Card>
|
|
384
|
-
)}
|
|
385
|
-
</Grid>
|
|
386
|
-
</Box>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
387
203
|
)
|
|
388
204
|
}
|
|
205
|
+
|
|
206
|
+
/** 根据 Feature 类型渲染不同样式的 item 卡片 */
|
|
207
|
+
function FeatureItemCard({ featureName, item }: { featureName: string; item: any }) {
|
|
208
|
+
switch (featureName) {
|
|
209
|
+
case 'command':
|
|
210
|
+
return (
|
|
211
|
+
<div className="rounded-md bg-muted/50 p-3 space-y-1">
|
|
212
|
+
<code className="text-sm font-semibold">{item.name}</code>
|
|
213
|
+
{item.desc?.map((desc: string, i: number) => (
|
|
214
|
+
<p key={i} className="text-xs text-muted-foreground">{desc}</p>
|
|
215
|
+
))}
|
|
216
|
+
{item.usage && item.usage.length > 0 && (
|
|
217
|
+
<div>
|
|
218
|
+
<span className="text-xs font-semibold text-blue-600 dark:text-blue-400">用法:</span>
|
|
219
|
+
{item.usage.map((u: string, i: number) => (
|
|
220
|
+
<code key={i} className="block text-xs bg-muted rounded px-1 py-0.5 mt-0.5">{u}</code>
|
|
221
|
+
))}
|
|
222
|
+
</div>
|
|
223
|
+
)}
|
|
224
|
+
{item.examples && item.examples.length > 0 && (
|
|
225
|
+
<div>
|
|
226
|
+
<span className="text-xs font-semibold text-emerald-600 dark:text-emerald-400">示例:</span>
|
|
227
|
+
{item.examples.map((e: string, i: number) => (
|
|
228
|
+
<code key={i} className="block text-xs bg-muted rounded px-1 py-0.5 mt-0.5">{e}</code>
|
|
229
|
+
))}
|
|
230
|
+
</div>
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
)
|
|
234
|
+
case 'cron':
|
|
235
|
+
return (
|
|
236
|
+
<div className="flex justify-between items-center rounded-md bg-muted/50 p-2">
|
|
237
|
+
<code className="text-sm">{item.expression}</code>
|
|
238
|
+
<Badge variant={item.running ? 'success' : 'secondary'}>
|
|
239
|
+
{item.running ? '运行中' : '已停止'}
|
|
240
|
+
</Badge>
|
|
241
|
+
</div>
|
|
242
|
+
)
|
|
243
|
+
case 'tool':
|
|
244
|
+
return (
|
|
245
|
+
<div className="rounded-md bg-muted/50 p-3 space-y-1">
|
|
246
|
+
<code className="text-sm font-semibold">{item.name}</code>
|
|
247
|
+
{item.desc && <p className="text-xs text-muted-foreground">{item.desc}</p>}
|
|
248
|
+
{item.platforms && item.platforms.length > 0 && (
|
|
249
|
+
<div className="flex gap-1 flex-wrap">
|
|
250
|
+
{item.platforms.map((p: string, i: number) => (
|
|
251
|
+
<Badge key={i} variant="outline" className="text-[10px]">{p}</Badge>
|
|
252
|
+
))}
|
|
253
|
+
</div>
|
|
254
|
+
)}
|
|
255
|
+
</div>
|
|
256
|
+
)
|
|
257
|
+
case 'skill':
|
|
258
|
+
return (
|
|
259
|
+
<div className="rounded-md bg-muted/50 p-3 space-y-1">
|
|
260
|
+
<code className="text-sm font-semibold">{item.name}</code>
|
|
261
|
+
{item.desc && <p className="text-xs text-muted-foreground">{item.desc}</p>}
|
|
262
|
+
{item.toolCount != null && (
|
|
263
|
+
<span className="text-xs text-muted-foreground">工具: {item.toolCount}</span>
|
|
264
|
+
)}
|
|
265
|
+
</div>
|
|
266
|
+
)
|
|
267
|
+
case 'config':
|
|
268
|
+
return (
|
|
269
|
+
<div className="flex justify-between items-center rounded-md bg-muted/50 p-2">
|
|
270
|
+
<code className="text-sm">{item.name}</code>
|
|
271
|
+
{item.defaultValue !== undefined && (
|
|
272
|
+
<span className="text-xs text-muted-foreground">默认: {JSON.stringify(item.defaultValue)}</span>
|
|
273
|
+
)}
|
|
274
|
+
</div>
|
|
275
|
+
)
|
|
276
|
+
case 'permission':
|
|
277
|
+
return (
|
|
278
|
+
<div className="rounded-md bg-muted/50 p-2">
|
|
279
|
+
<code className="text-sm">{item.name}</code>
|
|
280
|
+
</div>
|
|
281
|
+
)
|
|
282
|
+
case 'database':
|
|
283
|
+
return (
|
|
284
|
+
<div className="rounded-md bg-muted/50 p-2">
|
|
285
|
+
<code className="text-sm">{item.name}</code>
|
|
286
|
+
</div>
|
|
287
|
+
)
|
|
288
|
+
case 'adapter':
|
|
289
|
+
return (
|
|
290
|
+
<div className="rounded-md bg-muted/50 p-3 space-y-1">
|
|
291
|
+
<div className="flex items-center gap-2">
|
|
292
|
+
<Plug className="w-3.5 h-3.5 text-muted-foreground" />
|
|
293
|
+
<code className="text-sm font-semibold">{item.name}</code>
|
|
294
|
+
</div>
|
|
295
|
+
<div className="flex gap-3 text-xs text-muted-foreground">
|
|
296
|
+
<span>Bot: {item.bots ?? 0}</span>
|
|
297
|
+
<span>在线: {item.online ?? 0}</span>
|
|
298
|
+
{item.tools > 0 && <span>工具: {item.tools}</span>}
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
)
|
|
302
|
+
case 'service':
|
|
303
|
+
return (
|
|
304
|
+
<div className="rounded-md bg-muted/50 p-2 flex items-center gap-2">
|
|
305
|
+
<Server className="w-3.5 h-3.5 text-muted-foreground" />
|
|
306
|
+
<code className="text-sm">{item.name}</code>
|
|
307
|
+
{item.desc && item.desc !== item.name && (
|
|
308
|
+
<span className="text-xs text-muted-foreground">- {item.desc}</span>
|
|
309
|
+
)}
|
|
310
|
+
</div>
|
|
311
|
+
)
|
|
312
|
+
default:
|
|
313
|
+
// 通用渲染:显示 item.name 或 JSON
|
|
314
|
+
return (
|
|
315
|
+
<div className="rounded-md bg-muted/50 p-2">
|
|
316
|
+
<code className="text-sm">{item.name || JSON.stringify(item)}</code>
|
|
317
|
+
</div>
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
}
|