fix:优化图片识别,优化webui配置和排版,优化聊天流监控,新增mcp显示,新增prompt修改面板,优化插件状态显示,优化长期记忆控制台,

This commit is contained in:
SengokuCola
2026-05-04 16:25:31 +08:00
parent c5cd47adc2
commit 120acb835f
51 changed files with 1764 additions and 493 deletions

View File

@@ -0,0 +1,251 @@
import { useCallback, useEffect, useState } from 'react'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { ScrollArea } from '@/components/ui/scroll-area'
import { DynamicConfigForm } from '@/components/dynamic-form'
import { RestartOverlay } from '@/components/restart-overlay'
import { useToast } from '@/hooks/use-toast'
import { getBotConfig, getBotConfigSchema, updateBotConfigSection } from '@/lib/config-api'
import { fieldHooks } from '@/lib/field-hooks'
import { RestartProvider, useRestart } from '@/lib/restart-context'
import type { ConfigSchema } from '@/types/config-schema'
import { Info, Power, Save } from 'lucide-react'
import { MCPRootItemsHook, MCPServersHook } from './config/bot/hooks'
type ConfigSectionData = Record<string, unknown>
function updateNestedValue(
target: ConfigSectionData | null | undefined,
pathSegments: string[],
value: unknown
): ConfigSectionData {
const currentTarget = target && typeof target === 'object' && !Array.isArray(target) ? target : {}
const [currentPath, ...restPath] = pathSegments
if (!currentPath) {
return currentTarget
}
if (restPath.length === 0) {
return {
...currentTarget,
[currentPath]: value,
}
}
return {
...currentTarget,
[currentPath]: updateNestedValue(currentTarget[currentPath] as ConfigSectionData | undefined, restPath, value),
}
}
export function MCPSettingsPage() {
return (
<RestartProvider>
<MCPSettingsPageContent />
</RestartProvider>
)
}
function MCPSettingsPageContent() {
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false)
const [mcpConfig, setMcpConfig] = useState<ConfigSectionData>({})
const [mcpSchema, setMcpSchema] = useState<ConfigSchema | null>(null)
const { toast } = useToast()
const { triggerRestart, isRestarting } = useRestart()
useEffect(() => {
const hookEntries = [
['mcp.client.roots.items', MCPRootItemsHook],
['mcp.servers', MCPServersHook],
] as const
for (const [fieldPath, hookComponent] of hookEntries) {
fieldHooks.register(fieldPath, hookComponent, 'replace')
}
return () => {
for (const [fieldPath] of hookEntries) {
fieldHooks.unregister(fieldPath)
}
}
}, [])
const loadConfig = useCallback(async () => {
try {
setLoading(true)
const [configResult, schemaResult] = await Promise.all([getBotConfig(), getBotConfigSchema()])
if (!configResult.success) {
toast({
title: '加载失败',
description: configResult.error,
variant: 'destructive',
})
return
}
if (!schemaResult.success) {
toast({
title: '加载失败',
description: schemaResult.error,
variant: 'destructive',
})
return
}
const configPayload = configResult.data as { config?: Record<string, unknown> } & Record<string, unknown>
const fullConfig = (configPayload.config ?? configPayload) as Record<string, unknown>
const schemaPayload = schemaResult.data as { schema?: ConfigSchema } & ConfigSchema
const fullSchema = (schemaPayload.schema ?? schemaPayload) as ConfigSchema
setMcpConfig((fullConfig.mcp ?? {}) as ConfigSectionData)
setMcpSchema(fullSchema.nested?.mcp ?? null)
setHasUnsavedChanges(false)
} catch (error) {
console.error('加载 MCP 设置失败:', error)
toast({
title: '加载失败',
description: (error as Error).message,
variant: 'destructive',
})
} finally {
setLoading(false)
}
}, [toast])
useEffect(() => {
void loadConfig()
}, [loadConfig])
const saveConfig = useCallback(async (): Promise<boolean> => {
try {
setSaving(true)
const result = await updateBotConfigSection('mcp', mcpConfig)
if (!result.success) {
toast({
title: '保存失败',
description: result.error,
variant: 'destructive',
})
return false
}
setHasUnsavedChanges(false)
toast({
title: '保存成功',
description: 'MCP 设置已保存,重启后生效。',
})
return true
} catch (error) {
console.error('保存 MCP 设置失败:', error)
toast({
title: '保存失败',
description: (error as Error).message,
variant: 'destructive',
})
return false
} finally {
setSaving(false)
}
}, [mcpConfig, toast])
const saveAndRestart = useCallback(async () => {
const saved = await saveConfig()
if (!saved) {
return
}
await triggerRestart({ delay: 500 })
}, [saveConfig, triggerRestart])
const formSchema: ConfigSchema | null = mcpSchema
? {
className: 'MCPSettings',
classDoc: 'MCP 设置',
fields: [],
nested: {
mcp: mcpSchema,
},
}
: null
return (
<ScrollArea className="h-full">
<div className="space-y-4 sm:space-y-6 p-4 sm:p-6">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="min-w-0">
<h1 className="text-xl sm:text-2xl md:text-3xl font-bold">MCP </h1>
<p className="text-muted-foreground mt-1 text-xs sm:text-sm">
MCP
</p>
</div>
<div className="flex gap-2">
<Button
onClick={saveConfig}
disabled={loading || saving || !hasUnsavedChanges || isRestarting}
size="sm"
variant="outline"
className="w-24"
>
<Save className="h-4 w-4" strokeWidth={2} fill="none" />
<span className="ml-1 text-xs sm:text-sm">{saving ? '保存中' : hasUnsavedChanges ? '保存' : '已保存'}</span>
</Button>
<Button
onClick={saveAndRestart}
disabled={loading || saving || isRestarting}
size="sm"
className="w-28"
>
<Power className="h-4 w-4" />
<span className="ml-1 text-xs sm:text-sm">{isRestarting ? '重启中' : '保存重启'}</span>
</Button>
</div>
</div>
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
MCP MCP 使
</AlertDescription>
</Alert>
{loading && (
<div className="flex h-64 items-center justify-center text-sm text-muted-foreground">
...
</div>
)}
{!loading && formSchema && (
<DynamicConfigForm
schema={formSchema}
values={{ mcp: mcpConfig }}
onChange={(fieldPath, value) => {
const [, ...restPath] = fieldPath.split('.')
const nextConfig = restPath.length === 0
? (value as ConfigSectionData)
: updateNestedValue(mcpConfig, restPath, value)
setMcpConfig(nextConfig)
setHasUnsavedChanges(true)
}}
hooks={fieldHooks}
/>
)}
{!loading && !formSchema && (
<Alert>
<Info className="h-4 w-4" />
<AlertDescription> schema MCP </AlertDescription>
</Alert>
)}
<RestartOverlay />
</div>
</ScrollArea>
)
}