码桶
发现社区成员的开源项目
McpPage.tsx4.2 KB
import { useState, useEffect, useCallback } from 'react'
import { useAuth } from '@/lib/auth'
import { api } from '@/lib/api'
import McpConfig from '@/components/McpConfig'
import { toast } from 'sonner'
import { Bot, Loader2, Puzzle } from 'lucide-react'
export default function McpPage() {
const { bots, loading: authLoading } = useAuth()
const [selBotId, setSelBotId] = useState<number | ''>('')
const [settings, setSettings] = useState<Record<string, string>>({})
const [saving, setSaving] = useState(false)
const [loading, setLoading] = useState(false)
// auto-select first bot
useEffect(() => {
if (!authLoading && bots.length > 0 && !selBotId) {
setSelBotId(bots[0].id)
}
}, [authLoading, bots, selBotId])
const loadSettings = useCallback(async (botId: number) => {
if (!botId) return
setLoading(true)
try {
const d = await api.post('/api/market/plugin_settings', { bot_id: botId, plugin_id: 'mcp' })
if (!d.ok) throw new Error(d.msg)
setSettings(d.settings || {})
} catch (e: any) {
toast.error('加载 MCP 配置失败:' + e.message)
setSettings({})
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
if (selBotId) loadSettings(selBotId)
}, [selBotId, loadSettings])
const saveSetting = useCallback(async (key: string, value: string): Promise<boolean> => {
if (!selBotId) return false
setSaving(true)
try {
const merged = { ...settings, [key]: value }
const d = await api.post('/api/market/plugin_settings_save', {
bot_id: selBotId, plugin_id: 'mcp', data: merged
})
if (!d.ok) throw new Error(d.msg)
setSettings(merged)
toast.success('MCP 配置已保存')
return true
} catch (e: any) {
toast.error(e.message)
return false
} finally {
setSaving(false)
}
}, [selBotId, settings])
if (authLoading) {
return (
<div className="flex items-center justify-center py-20">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
)
}
if (bots.length === 0) {
return (
<div className="flex flex-col items-center justify-center py-20 text-muted-foreground">
<Bot className="h-12 w-12 mb-4 text-gray-300" />
<p className="text-sm">还没有机器人,请先在个人中心添加</p>
</div>
)
}
return (
<div className="w-full px-4 py-6 sm:px-6 space-y-6">
{/* header */}
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-purple-100 dark:bg-purple-900/30">
<Puzzle className="h-5 w-5 text-purple-600 dark:text-purple-400" />
</div>
<div>
<h1 className="text-xl font-bold">MCP 服务器管理</h1>
<p className="text-sm text-muted-foreground">连接外部 MCP 服务器,赋予机器人更强的 AI 工具能力</p>
</div>
</div>
{/* bot selector */}
<div className="flex items-center gap-2 flex-wrap">
<span className="text-sm text-muted-foreground">选择机器人:</span>
{bots.map(b => (
<button
key={b.id}
onClick={() => setSelBotId(b.id)}
className={`px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${
selBotId === b.id
? 'bg-purple-100 dark:bg-purple-900/40 text-purple-700 dark:text-purple-300'
: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700'
}`}
>
<Bot className="inline h-3 w-3 mr-1" />
{b.name || b.id}
</button>
))}
</div>
{/* config panel */}
{loading ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
</div>
) : selBotId !== '' ? (
<McpConfig
bot={{ id: selBotId }}
settings={settings}
onSave={saveSetting}
saving={saving}
/>
) : null}
</div>
)
}