diff --git a/domain/ai/api.py b/domain/ai/api.py index ee4c846..7da7231 100644 --- a/domain/ai/api.py +++ b/domain/ai/api.py @@ -250,7 +250,7 @@ async def get_vector_db_stats(request: Request, user: User = Depends(get_current @audit(action=AuditAction.READ, description="获取向量数据库提供者列表") @router_vector_db.get("/providers", summary="列出可用向量数据库提供者") -async def list_vector_providers(request: Request, user: User = Depends(get_current_active_user)): +async def list_vector_providers(request: Request): return success(list_providers()) diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index be954a6..062165f 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -674,6 +674,7 @@ "Coming soon v2": "Coming soon v2", "Initialization succeeded! Logging you in...": "Initialization succeeded! Logging you in...", "Initialization failed, please try later": "Initialization failed, please try later", + "Vector DB setup failed, you can configure it later in System Settings": "Vector DB setup failed, you can configure it later in System Settings", "Database Setup": "Database Setup", "Choose database driver": "Choose database driver", "Select database and vector database for system data": "Select database and vector database for system data", diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index 28db569..38be588 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -674,6 +674,7 @@ "Coming soon v2": "敬请期待 v2", "Initialization succeeded! Logging you in...": "初始化成功!正在为您登录,请不要刷新。", "Initialization failed, please try later": "初始化失败,请稍后重试", + "Vector DB setup failed, you can configure it later in System Settings": "向量数据库配置失败,可稍后在系统设置中配置", "Database Setup": "数据库设置", "Choose database driver": "选择数据库驱动", "Select database and vector database for system data": "选择用于存储系统数据的数据库和向量数据库。", diff --git a/web/src/pages/SetupPage.tsx b/web/src/pages/SetupPage.tsx index 4e8b746..a8d8af8 100644 --- a/web/src/pages/SetupPage.tsx +++ b/web/src/pages/SetupPage.tsx @@ -1,20 +1,48 @@ import { useEffect, useState } from 'react'; -import { Form, Input, Button, Card, message, Steps, Select, Space, Typography } from 'antd'; +import { Form, Input, Button, Card, message, Steps, Select, Space, Typography, Alert, Grid } from 'antd'; import { UserOutlined, LockOutlined, HddOutlined } from '@ant-design/icons'; import { adaptersApi } from '../api/adapters'; import { setConfig } from '../api/config'; +import { vectorDBApi, type VectorDBProviderMeta } from '../api/vectorDB'; import { useAuth } from '../contexts/AuthContext'; import { useI18n } from '../i18n'; import LanguageSwitcher from '../components/LanguageSwitcher'; const { Title, Text } = Typography; +const buildProviderConfigValues = ( + provider: VectorDBProviderMeta | undefined, + existing?: Record, +) => { + if (!provider) return {}; + const values: Record = {}; + const schema = provider.config_schema || []; + schema.forEach((field) => { + const current = existing && existing[field.key] !== undefined && existing[field.key] !== null + ? String(existing[field.key]) + : undefined; + if (current !== undefined) { + values[field.key] = current; + } else if (field.default !== undefined && field.default !== null) { + values[field.key] = String(field.default); + } else { + values[field.key] = ''; + } + }); + return values; +}; + const SetupPage = () => { + const screens = Grid.useBreakpoint(); + const isMobile = !screens.md; const [loading, setLoading] = useState(false); const [currentStep, setCurrentStep] = useState(0); const [form] = Form.useForm(); const { login, register } = useAuth(); const { t } = useI18n(); + const [vectorProviders, setVectorProviders] = useState([]); + const [vectorProvidersLoading, setVectorProvidersLoading] = useState(false); + const [selectedVectorProviderType, setSelectedVectorProviderType] = useState(null); useEffect(() => { const origin = window.location.origin; @@ -23,6 +51,48 @@ const SetupPage = () => { file_domain: origin, }); }, [form]); + + useEffect(() => { + let mounted = true; + async function loadVectorProviders() { + setVectorProvidersLoading(true); + try { + const providers = await vectorDBApi.getProviders(); + if (!mounted) return; + setVectorProviders(providers); + + const enabled = providers.filter((item) => item.enabled); + const currentType = form.getFieldValue('vector_db_type') as string | undefined; + let nextType = currentType; + if (!nextType || !providers.some((item) => item.type === nextType && item.enabled)) { + nextType = enabled.find((item) => item.type === 'milvus_lite')?.type + ?? enabled[0]?.type + ?? providers[0]?.type + ?? 'milvus_lite'; + } + + setSelectedVectorProviderType(nextType); + const provider = providers.find((item) => item.type === nextType); + const existingConfig = nextType === currentType ? (form.getFieldValue('vector_db_config') as Record | undefined) : undefined; + const configValues = buildProviderConfigValues(provider, existingConfig); + form.setFieldsValue({ vector_db_type: nextType, vector_db_config: configValues }); + } catch (e: any) { + console.error(e); + message.error(e?.message || t('Load failed')); + if (mounted) { + form.setFieldsValue({ vector_db_type: 'milvus_lite' }); + setSelectedVectorProviderType('milvus_lite'); + } + } finally { + if (mounted) setVectorProvidersLoading(false); + } + } + loadVectorProviders(); + return () => { + mounted = false; + }; + }, [form, t]); + const onFinish = async (values: any) => { setLoading(true); try { @@ -43,6 +113,19 @@ const SetupPage = () => { if (tasks.length) { await Promise.all(tasks); } + if (values.vector_db_type) { + const configPayload = Object.fromEntries( + Object.entries(values.vector_db_config || {}) + .filter(([, val]) => val !== undefined && val !== null && String(val).trim() !== '') + .map(([key, val]) => [key, String(val)]), + ); + try { + await vectorDBApi.updateConfig({ type: values.vector_db_type, config: configPayload }); + } catch (e: any) { + console.error(e); + message.warning(e?.message || t('Vector DB setup failed, you can configure it later in System Settings')); + } + } await adaptersApi.create({ name: values.adapter_name, type: values.adapter_type, @@ -67,12 +150,24 @@ const SetupPage = () => { } }; - const stepFields = [ - ['db_driver', 'vector_db_driver'], + const selectedVectorProvider = vectorProviders.find((item) => item.type === selectedVectorProviderType) || vectorProviders.find((item) => item.enabled) || vectorProviders[0]; + const requiredVectorConfigFields = (selectedVectorProvider?.config_schema || []) + .filter((field) => field.required) + .map((field) => ['vector_db_config', field.key]); + + const stepFields: any[] = [ + ['db_driver', 'vector_db_type', ...requiredVectorConfigFields], ['adapter_name', 'adapter_type', 'path', 'root_dir'], ['username', 'full_name', 'email', 'password', 'confirm'], ] + const handleVectorProviderChange = (value: string) => { + setSelectedVectorProviderType(value); + const provider = vectorProviders.find((item) => item.type === value); + const configValues = buildProviderConfigValues(provider); + form.setFieldsValue({ vector_db_type: value, vector_db_config: configValues }); + }; + const next = () => { form.validateFields(stepFields[currentStep]).then(() => { setCurrentStep(currentStep + 1); @@ -100,12 +195,44 @@ const SetupPage = () => { - ({ + value: provider.type, + label: provider.label, + disabled: !provider.enabled, + }))} + onChange={handleVectorProviderChange} + /> + {selectedVectorProvider?.description ? ( + + ) : null} + {selectedVectorProvider?.config_schema?.map((field) => ( + + {field.type === 'password' ? ( + + ) : ( + + )} + + ))} ) }, @@ -228,23 +355,30 @@ const SetupPage = () => { return (
- -
+ +
Foxel Logo {t('System Initialization')}
({ title: item.title }))} /> @@ -256,20 +390,20 @@ const SetupPage = () => { ))} -
- +
+ {currentStep > 0 && ( - )} {currentStep < steps.length - 1 && ( - )} {currentStep === steps.length - 1 && ( - )}