11import { 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' ;
33import { UserOutlined , LockOutlined , HddOutlined } from '@ant-design/icons' ;
44import { adaptersApi } from '../api/adapters' ;
55import { setConfig } from '../api/config' ;
6+ import { vectorDBApi , type VectorDBProviderMeta } from '../api/vectorDB' ;
67import { useAuth } from '../contexts/AuthContext' ;
78import { useI18n } from '../i18n' ;
89import LanguageSwitcher from '../components/LanguageSwitcher' ;
910
1011const { 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+
1235const 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