Skip to content

Commit d9d2ddf

Browse files
committed
feat: update vector DB provider handling and improve setup page configuration
1 parent e6ab01e commit d9d2ddf

File tree

4 files changed

+154
-18
lines changed

4 files changed

+154
-18
lines changed

domain/ai/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ async def get_vector_db_stats(request: Request, user: User = Depends(get_current
250250

251251
@audit(action=AuditAction.READ, description="获取向量数据库提供者列表")
252252
@router_vector_db.get("/providers", summary="列出可用向量数据库提供者")
253-
async def list_vector_providers(request: Request, user: User = Depends(get_current_active_user)):
253+
async def list_vector_providers(request: Request):
254254
return success(list_providers())
255255

256256

web/src/i18n/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@
674674
"Coming soon v2": "Coming soon v2",
675675
"Initialization succeeded! Logging you in...": "Initialization succeeded! Logging you in...",
676676
"Initialization failed, please try later": "Initialization failed, please try later",
677+
"Vector DB setup failed, you can configure it later in System Settings": "Vector DB setup failed, you can configure it later in System Settings",
677678
"Database Setup": "Database Setup",
678679
"Choose database driver": "Choose database driver",
679680
"Select database and vector database for system data": "Select database and vector database for system data",

web/src/i18n/locales/zh.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@
674674
"Coming soon v2": "敬请期待 v2",
675675
"Initialization succeeded! Logging you in...": "初始化成功!正在为您登录,请不要刷新。",
676676
"Initialization failed, please try later": "初始化失败,请稍后重试",
677+
"Vector DB setup failed, you can configure it later in System Settings": "向量数据库配置失败,可稍后在系统设置中配置",
677678
"Database Setup": "数据库设置",
678679
"Choose database driver": "选择数据库驱动",
679680
"Select database and vector database for system data": "选择用于存储系统数据的数据库和向量数据库。",

web/src/pages/SetupPage.tsx

Lines changed: 151 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,48 @@
11
import { useEffect, useState } from 'react';
2-
import { Form, Input, Button, Card, message, Steps, Select, Space, Typography } from 'antd';
2+
import { Form, Input, Button, Card, message, Steps, Select, Space, Typography, Alert, Grid } from 'antd';
33
import { UserOutlined, LockOutlined, HddOutlined } from '@ant-design/icons';
44
import { adaptersApi } from '../api/adapters';
55
import { setConfig } from '../api/config';
6+
import { vectorDBApi, type VectorDBProviderMeta } from '../api/vectorDB';
67
import { useAuth } from '../contexts/AuthContext';
78
import { useI18n } from '../i18n';
89
import LanguageSwitcher from '../components/LanguageSwitcher';
910

1011
const { Title, Text } = Typography;
1112

13+
const buildProviderConfigValues = (
14+
provider: VectorDBProviderMeta | undefined,
15+
existing?: Record<string, string>,
16+
) => {
17+
if (!provider) return {};
18+
const values: Record<string, string> = {};
19+
const schema = provider.config_schema || [];
20+
schema.forEach((field) => {
21+
const current = existing && existing[field.key] !== undefined && existing[field.key] !== null
22+
? String(existing[field.key])
23+
: undefined;
24+
if (current !== undefined) {
25+
values[field.key] = current;
26+
} else if (field.default !== undefined && field.default !== null) {
27+
values[field.key] = String(field.default);
28+
} else {
29+
values[field.key] = '';
30+
}
31+
});
32+
return values;
33+
};
34+
1235
const SetupPage = () => {
36+
const screens = Grid.useBreakpoint();
37+
const isMobile = !screens.md;
1338
const [loading, setLoading] = useState(false);
1439
const [currentStep, setCurrentStep] = useState(0);
1540
const [form] = Form.useForm();
1641
const { login, register } = useAuth();
1742
const { t } = useI18n();
43+
const [vectorProviders, setVectorProviders] = useState<VectorDBProviderMeta[]>([]);
44+
const [vectorProvidersLoading, setVectorProvidersLoading] = useState(false);
45+
const [selectedVectorProviderType, setSelectedVectorProviderType] = useState<string | null>(null);
1846

1947
useEffect(() => {
2048
const origin = window.location.origin;
@@ -23,6 +51,48 @@ const SetupPage = () => {
2351
file_domain: origin,
2452
});
2553
}, [form]);
54+
55+
useEffect(() => {
56+
let mounted = true;
57+
async function loadVectorProviders() {
58+
setVectorProvidersLoading(true);
59+
try {
60+
const providers = await vectorDBApi.getProviders();
61+
if (!mounted) return;
62+
setVectorProviders(providers);
63+
64+
const enabled = providers.filter((item) => item.enabled);
65+
const currentType = form.getFieldValue('vector_db_type') as string | undefined;
66+
let nextType = currentType;
67+
if (!nextType || !providers.some((item) => item.type === nextType && item.enabled)) {
68+
nextType = enabled.find((item) => item.type === 'milvus_lite')?.type
69+
?? enabled[0]?.type
70+
?? providers[0]?.type
71+
?? 'milvus_lite';
72+
}
73+
74+
setSelectedVectorProviderType(nextType);
75+
const provider = providers.find((item) => item.type === nextType);
76+
const existingConfig = nextType === currentType ? (form.getFieldValue('vector_db_config') as Record<string, string> | undefined) : undefined;
77+
const configValues = buildProviderConfigValues(provider, existingConfig);
78+
form.setFieldsValue({ vector_db_type: nextType, vector_db_config: configValues });
79+
} catch (e: any) {
80+
console.error(e);
81+
message.error(e?.message || t('Load failed'));
82+
if (mounted) {
83+
form.setFieldsValue({ vector_db_type: 'milvus_lite' });
84+
setSelectedVectorProviderType('milvus_lite');
85+
}
86+
} finally {
87+
if (mounted) setVectorProvidersLoading(false);
88+
}
89+
}
90+
loadVectorProviders();
91+
return () => {
92+
mounted = false;
93+
};
94+
}, [form, t]);
95+
2696
const onFinish = async (values: any) => {
2797
setLoading(true);
2898
try {
@@ -43,6 +113,19 @@ const SetupPage = () => {
43113
if (tasks.length) {
44114
await Promise.all(tasks);
45115
}
116+
if (values.vector_db_type) {
117+
const configPayload = Object.fromEntries(
118+
Object.entries(values.vector_db_config || {})
119+
.filter(([, val]) => val !== undefined && val !== null && String(val).trim() !== '')
120+
.map(([key, val]) => [key, String(val)]),
121+
);
122+
try {
123+
await vectorDBApi.updateConfig({ type: values.vector_db_type, config: configPayload });
124+
} catch (e: any) {
125+
console.error(e);
126+
message.warning(e?.message || t('Vector DB setup failed, you can configure it later in System Settings'));
127+
}
128+
}
46129
await adaptersApi.create({
47130
name: values.adapter_name,
48131
type: values.adapter_type,
@@ -67,12 +150,24 @@ const SetupPage = () => {
67150
}
68151
};
69152

70-
const stepFields = [
71-
['db_driver', 'vector_db_driver'],
153+
const selectedVectorProvider = vectorProviders.find((item) => item.type === selectedVectorProviderType) || vectorProviders.find((item) => item.enabled) || vectorProviders[0];
154+
const requiredVectorConfigFields = (selectedVectorProvider?.config_schema || [])
155+
.filter((field) => field.required)
156+
.map((field) => ['vector_db_config', field.key]);
157+
158+
const stepFields: any[] = [
159+
['db_driver', 'vector_db_type', ...requiredVectorConfigFields],
72160
['adapter_name', 'adapter_type', 'path', 'root_dir'],
73161
['username', 'full_name', 'email', 'password', 'confirm'],
74162
]
75163

164+
const handleVectorProviderChange = (value: string) => {
165+
setSelectedVectorProviderType(value);
166+
const provider = vectorProviders.find((item) => item.type === value);
167+
const configValues = buildProviderConfigValues(provider);
168+
form.setFieldsValue({ vector_db_type: value, vector_db_config: configValues });
169+
};
170+
76171
const next = () => {
77172
form.validateFields(stepFields[currentStep]).then(() => {
78173
setCurrentStep(currentStep + 1);
@@ -100,12 +195,44 @@ const SetupPage = () => {
100195
</Form.Item>
101196
<Form.Item
102197
label={t('Vector DB Driver')}
103-
name="vector_db_driver"
104-
initialValue="milvus"
198+
name="vector_db_type"
199+
initialValue="milvus_lite"
105200
rules={[{ required: true }]}
106201
>
107-
<Select size="large" disabled options={[{ label: 'Milvus', value: 'milvus' }]} />
202+
<Select
203+
size="large"
204+
loading={vectorProvidersLoading}
205+
disabled={vectorProvidersLoading || !vectorProviders.length}
206+
options={vectorProviders.map((provider) => ({
207+
value: provider.type,
208+
label: provider.label,
209+
disabled: !provider.enabled,
210+
}))}
211+
onChange={handleVectorProviderChange}
212+
/>
108213
</Form.Item>
214+
{selectedVectorProvider?.description ? (
215+
<Alert
216+
type="info"
217+
showIcon
218+
message={t(selectedVectorProvider.description)}
219+
style={{ marginBottom: 16 }}
220+
/>
221+
) : null}
222+
{selectedVectorProvider?.config_schema?.map((field) => (
223+
<Form.Item
224+
key={field.key}
225+
name={['vector_db_config', field.key]}
226+
label={t(field.label)}
227+
rules={field.required ? [{ required: true, message: t('Please input {label}', { label: t(field.label) }) }] : []}
228+
>
229+
{field.type === 'password' ? (
230+
<Input.Password size="large" placeholder={field.placeholder ? t(field.placeholder) : undefined} />
231+
) : (
232+
<Input size="large" placeholder={field.placeholder ? t(field.placeholder) : undefined} />
233+
)}
234+
</Form.Item>
235+
))}
109236
</>
110237
)
111238
},
@@ -228,23 +355,30 @@ const SetupPage = () => {
228355
return (
229356
<div style={{
230357
display: 'flex',
231-
width: '100vw',
232-
height: '100vh',
233-
alignItems: 'center',
358+
width: '100%',
359+
minHeight: '100vh',
360+
alignItems: isMobile ? 'flex-start' : 'center',
234361
justifyContent: 'center',
362+
padding: isMobile ? '64px 12px 24px' : '32px 24px',
363+
boxSizing: 'border-box',
235364
background: 'linear-gradient(to right, var(--ant-color-bg-layout, #f0f2f5), var(--ant-color-fill-secondary, #d7d7d7))'
236365
}}>
237366
<div style={{ position: 'fixed', top: 12, right: 12, zIndex: 1000 }}>
238367
<LanguageSwitcher />
239368
</div>
240-
<Card style={{ width: 'clamp(400px, 40vw, 600px)', padding: '24px 16px' }}>
241-
<div style={{ textAlign: 'center', marginBottom: 32 }}>
369+
<Card
370+
style={{ width: '100%', maxWidth: 800 }}
371+
styles={{ body: { padding: isMobile ? '18px 14px' : '24px 20px' } }}
372+
>
373+
<div style={{ textAlign: 'center', marginBottom: isMobile ? 20 : 32 }}>
242374
<img src="/logo.svg" alt="Foxel Logo" style={{ width: 48, marginBottom: 16 }} />
243375
<Title level={2}>{t('System Initialization')}</Title>
244376
</div>
245377
<Steps
246378
current={currentStep}
247-
style={{ marginBottom: 32 }}
379+
direction={isMobile ? 'vertical' : 'horizontal'}
380+
size={isMobile ? 'small' : 'default'}
381+
style={{ marginBottom: isMobile ? 20 : 32 }}
248382
items={steps.map((item) => ({ title: item.title }))}
249383
/>
250384

@@ -256,20 +390,20 @@ const SetupPage = () => {
256390
))}
257391
</Form>
258392

259-
<div style={{ marginTop: 24 }}>
260-
<Space>
393+
<div style={{ marginTop: isMobile ? 16 : 24 }}>
394+
<Space direction={isMobile ? 'vertical' : 'horizontal'} style={{ width: '100%' }}>
261395
{currentStep > 0 && (
262-
<Button style={{ margin: '0 8px' }} onClick={() => prev()}>
396+
<Button block={isMobile} onClick={() => prev()}>
263397
{t('Previous')}
264398
</Button>
265399
)}
266400
{currentStep < steps.length - 1 && (
267-
<Button type="primary" onClick={() => next()}>
401+
<Button type="primary" block={isMobile} onClick={() => next()}>
268402
{t('Next')}
269403
</Button>
270404
)}
271405
{currentStep === steps.length - 1 && (
272-
<Button type="primary" htmlType="submit" loading={loading} onClick={() => form.submit()}>
406+
<Button type="primary" block={isMobile} htmlType="submit" loading={loading} onClick={() => form.submit()}>
273407
{t('Finish Initialization')}
274408
</Button>
275409
)}

0 commit comments

Comments
 (0)