From 11a81c25ef7bedba139224fd05d848ceb676294a Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Sun, 17 Aug 2025 16:45:11 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20refactor:=20Setup=20Wizard=20UI?= =?UTF-8?q?=20&=20Clean=20Up=20Redundant=20Code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary of changes 1. SetupWizard.jsx • Center card (`min-h-screen flex items-center justify-center`) and remove top margin. • Merge step indicator/content into single card; added `Divider` separator. • Added sweep-shine animation to current step title via existing `shine-text` class. • Simplified imports (removed Avatar / Typography) and deleted unused modal state. 2. Step components • Stripped outer `Card` and header sections from `DatabaseStep.jsx`, `AdminStep.jsx`, `UsageModeStep.jsx`, `CompleteStep.jsx` to fit single-card layout. • Removed unused imports and props. 3. Components cleanup • Deleted obsolete files: - `components/setup/components/SetupSteps.jsx` - `components/setup/components/modals/UsageModeInfoModal.jsx` • Updated `setup/index.jsx` exports accordingly. 4. Styling • Ensured global sweep-shine effect already present in `index.css` is reused for step titles. 5. i18n • Pruned unused translation keys related to removed components from `i18n/locales/en.json`. 6. Miscellaneous • Removed redundant Avatar/Icon imports from multiple files. • All linter checks pass with no new warnings. This commit consolidates the initialization flow into a cleaner, centered single-card wizard, adds visual polish, and reduces dead code for easier maintenance. --- web/src/components/setup/SetupWizard.jsx | 328 ++++++++++ .../setup/components/StepNavigation.jsx | 78 +++ .../setup/components/steps/AdminStep.jsx | 120 ++++ .../setup/components/steps/CompleteStep.jsx | 65 ++ .../setup/components/steps/DatabaseStep.jsx | 102 +++ .../setup/components/steps/UsageModeStep.jsx | 71 +++ web/src/components/setup/index.jsx | 29 + web/src/components/topup/InvitationCard.jsx | 2 +- web/src/i18n/locales/en.json | 27 +- web/src/index.css | 2 +- web/src/pages/Setup/index.js | 580 +----------------- 11 files changed, 812 insertions(+), 592 deletions(-) create mode 100644 web/src/components/setup/SetupWizard.jsx create mode 100644 web/src/components/setup/components/StepNavigation.jsx create mode 100644 web/src/components/setup/components/steps/AdminStep.jsx create mode 100644 web/src/components/setup/components/steps/CompleteStep.jsx create mode 100644 web/src/components/setup/components/steps/DatabaseStep.jsx create mode 100644 web/src/components/setup/components/steps/UsageModeStep.jsx create mode 100644 web/src/components/setup/index.jsx diff --git a/web/src/components/setup/SetupWizard.jsx b/web/src/components/setup/SetupWizard.jsx new file mode 100644 index 00000000..1cec67d0 --- /dev/null +++ b/web/src/components/setup/SetupWizard.jsx @@ -0,0 +1,328 @@ +/* +Copyright (C) 2025 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ + +import React, { useEffect, useState, useRef } from 'react'; +import { Card, Divider, Steps, Form } from '@douyinfe/semi-ui'; +import { API, showError, showNotice } from '../../helpers'; +import { useTranslation } from 'react-i18next'; + +import StepNavigation from './components/StepNavigation'; +import DatabaseStep from './components/steps/DatabaseStep'; +import AdminStep from './components/steps/AdminStep'; +import UsageModeStep from './components/steps/UsageModeStep'; +import CompleteStep from './components/steps/CompleteStep'; + +const SetupWizard = () => { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [setupStatus, setSetupStatus] = useState({ + status: false, + root_init: false, + database_type: '', + }); + const [currentStep, setCurrentStep] = useState(0); + const formRef = useRef(null); + + const [formData, setFormData] = useState({ + username: '', + password: '', + confirmPassword: '', + usageMode: 'external', + }); + + // 确保默认选中“对外运营模式”,并同步到表单 + useEffect(() => { + if (formRef.current) { + formRef.current.setValue('usageMode', 'external'); + } + }, []); + + // 定义步骤内容 + const steps = [ + { + title: t('数据库检查'), + description: t('验证数据库连接状态'), + }, + { + title: t('管理员账号'), + description: t('设置管理员登录信息'), + }, + { + title: t('使用模式'), + description: t('选择系统运行模式'), + }, + { + title: t('完成初始化'), + description: t('确认设置并完成初始化'), + }, + ]; + + useEffect(() => { + fetchSetupStatus(); + }, []); + + const fetchSetupStatus = async () => { + try { + const res = await API.get('/api/setup'); + const { success, data } = res.data; + if (success) { + setSetupStatus(data); + + // If setup is already completed, redirect to home + if (data.status) { + window.location.href = '/'; + return; + } + + // 设置当前步骤 - 默认从数据库检查开始 + setCurrentStep(0); + } else { + showError(t('获取初始化状态失败')); + } + } catch (error) { + console.error('Failed to fetch setup status:', error); + showError(t('获取初始化状态失败')); + } + }; + + const handleUsageModeChange = (e) => { + const nextMode = e?.target?.value ?? e; + setFormData((prev) => ({ ...prev, usageMode: nextMode })); + // 同步到表单,便于 getValues() 拿到 usageMode + if (formRef.current) { + formRef.current.setValue('usageMode', nextMode); + } + }; + + const next = () => { + // 验证当前步骤是否可以继续 + if (!canProceedToNext()) { + return; + } + + const current = currentStep + 1; + setCurrentStep(current); + }; + + // 验证是否可以继续到下一步 + const canProceedToNext = () => { + switch (currentStep) { + case 0: // 数据库检查步骤 + return true; // 数据库检查总是可以继续 + case 1: // 管理员账号步骤 + if (setupStatus.root_init) { + return true; // 如果已经初始化,可以继续 + } + // 检查必填字段 + if (!formData.username || !formData.password || !formData.confirmPassword) { + showError(t('请填写完整的管理员账号信息')); + return false; + } + if (formData.password !== formData.confirmPassword) { + showError(t('两次输入的密码不一致')); + return false; + } + if (formData.password.length < 8) { + showError(t('密码长度至少为8个字符')); + return false; + } + return true; + case 2: // 使用模式步骤 + if (!formData.usageMode) { + showError(t('请选择使用模式')); + return false; + } + return true; + default: + return true; + } + }; + + const prev = () => { + const current = currentStep - 1; + setCurrentStep(current); + }; + + const onSubmit = () => { + if (!formRef.current) { + console.error('Form reference is null'); + showError(t('表单引用错误,请刷新页面重试')); + return; + } + + const values = formRef.current.getValues(); + + // For root_init=false, validate admin username and password + if (!setupStatus.root_init) { + if (!values.username || !values.username.trim()) { + showError(t('请输入管理员用户名')); + return; + } + + if (!values.password || values.password.length < 8) { + showError(t('密码长度至少为8个字符')); + return; + } + + if (values.password !== values.confirmPassword) { + showError(t('两次输入的密码不一致')); + return; + } + } + + // Prepare submission data + const formValues = { ...values }; + const usageMode = values.usageMode; + formValues.SelfUseModeEnabled = usageMode === 'self'; + formValues.DemoSiteEnabled = usageMode === 'demo'; + + // Remove usageMode as it's not needed by the backend + delete formValues.usageMode; + + // 提交表单至后端 + setLoading(true); + + // Submit to backend + API.post('/api/setup', formValues) + .then((res) => { + const { success, message } = res.data; + + if (success) { + showNotice(t('系统初始化成功,正在跳转...')); + setTimeout(() => { + window.location.reload(); + }, 1500); + } else { + showError(message || t('初始化失败,请重试')); + } + }) + .catch((error) => { + console.error('API error:', error); + showError(t('系统初始化失败,请重试')); + setLoading(false); + }) + .finally(() => { + setLoading(false); + }); + }; + + // 获取步骤内容 + const getStepContent = (step) => { + switch (step) { + case 0: + return ( + + ); + case 1: + return ( + + ); + case 2: + return ( + + ); + case 3: + return ( + + ); + default: + return null; + } + }; + + const stepNavigationProps = { + currentStep, + steps, + prev, + next, + onSubmit, + loading, + t, + }; + + return ( +
+
+ +
+
{t('系统初始化')}
+
+ {t('欢迎使用,请完成以下设置以开始使用系统')} +
+
+ +
+ + {steps.map((item, index) => ( + {item.title}} + description={item.description} + /> + ))} + +
+ + + + {/* 表单容器 */} +
{ + formRef.current = formApi; + }} + initValues={formData} + > + {/* 步骤内容:保持所有字段挂载,仅隐藏非当前步骤 */} +
+ {[0, 1, 2, 3].map((idx) => ( +
+ {React.cloneElement(getStepContent(idx), { + ...stepNavigationProps, + renderNavigationButtons: () => ( + + ), + })} +
+ ))} +
+
+
+
+
+ ); +}; + +export default SetupWizard; diff --git a/web/src/components/setup/components/StepNavigation.jsx b/web/src/components/setup/components/StepNavigation.jsx new file mode 100644 index 00000000..51274792 --- /dev/null +++ b/web/src/components/setup/components/StepNavigation.jsx @@ -0,0 +1,78 @@ +/* +Copyright (C) 2025 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ + +import React from 'react'; +import { Button } from '@douyinfe/semi-ui'; +import { IconCheckCircleStroked } from '@douyinfe/semi-icons'; + +/** + * 步骤导航组件 + * 负责渲染上一步、下一步和完成按钮 + */ +const StepNavigation = ({ + currentStep, + steps, + prev, + next, + onSubmit, + loading, + t +}) => { + return ( +
+ {/* 上一步按钮 */} + {currentStep > 0 && ( + + )} + +
+ + {/* 下一步按钮 */} + {currentStep < steps.length - 1 && ( + + )} + + {/* 完成按钮 */} + {currentStep === steps.length - 1 && ( + + )} +
+ ); +}; + +export default StepNavigation; diff --git a/web/src/components/setup/components/steps/AdminStep.jsx b/web/src/components/setup/components/steps/AdminStep.jsx new file mode 100644 index 00000000..f2b14ead --- /dev/null +++ b/web/src/components/setup/components/steps/AdminStep.jsx @@ -0,0 +1,120 @@ +/* +Copyright (C) 2025 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ + +import React from 'react'; +import { Banner, Form } from '@douyinfe/semi-ui'; +import { IconUser, IconLock } from '@douyinfe/semi-icons'; + +/** + * 管理员账号设置步骤组件 + * 提供管理员用户名和密码的设置界面 + */ +const AdminStep = ({ + setupStatus, + formData, + setFormData, + formRef, + renderNavigationButtons, + t +}) => { + return ( + <> + {setupStatus.root_init ? ( + + {t('管理员账号已经初始化过,请继续设置其他参数')} + + } + className="!rounded-lg" + /> + ) : ( + <> + } + showClear + noLabel={false} + validateStatus="default" + rules={[{ required: true, message: t('请输入管理员用户名') }]} + initValue={formData.username || ''} + onChange={(value) => { + setFormData({ ...formData, username: value }); + }} + /> + } + showClear + noLabel={false} + mode="password" + validateStatus="default" + rules={[ + { required: true, message: t('请输入管理员密码') }, + { min: 8, message: t('密码长度至少为8个字符') } + ]} + initValue={formData.password || ''} + onChange={(value) => { + setFormData({ ...formData, password: value }); + }} + /> + } + showClear + noLabel={false} + mode="password" + validateStatus="default" + rules={[ + { required: true, message: t('请确认管理员密码') }, + { + validator: (rule, value) => { + if (value && formRef.current) { + const password = formRef.current.getValue('password'); + if (value !== password) { + return Promise.reject(t('两次输入的密码不一致')); + } + } + return Promise.resolve(); + } + } + ]} + initValue={formData.confirmPassword || ''} + onChange={(value) => { + setFormData({ ...formData, confirmPassword: value }); + }} + /> + + )} + {renderNavigationButtons && renderNavigationButtons()} + + ); +}; + +export default AdminStep; diff --git a/web/src/components/setup/components/steps/CompleteStep.jsx b/web/src/components/setup/components/steps/CompleteStep.jsx new file mode 100644 index 00000000..916ed89d --- /dev/null +++ b/web/src/components/setup/components/steps/CompleteStep.jsx @@ -0,0 +1,65 @@ +/* +Copyright (C) 2025 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ + +import React from 'react'; +import { Avatar, Typography, Descriptions } from '@douyinfe/semi-ui'; +import { CheckCircle } from 'lucide-react'; + +const { Text, Title } = Typography; + +/** + * 完成步骤组件 + * 显示配置总结和初始化确认界面 + */ +const CompleteStep = ({ + setupStatus, + formData, + renderNavigationButtons, + t +}) => { + return ( +
+ + + + {t('准备完成初始化')} + + {t('请确认以下设置信息,点击"初始化系统"开始配置')} + + + + + {setupStatus.database_type === 'sqlite' ? 'SQLite' : + setupStatus.database_type === 'mysql' ? 'MySQL' : 'PostgreSQL'} + + + {setupStatus.root_init ? t('已初始化') : (formData.username || t('未设置'))} + + + {formData.usageMode === 'external' ? t('对外运营模式') : + formData.usageMode === 'self' ? t('自用模式') : t('演示站点模式')} + + + + {renderNavigationButtons && renderNavigationButtons()} +
+ ); +}; + +export default CompleteStep; diff --git a/web/src/components/setup/components/steps/DatabaseStep.jsx b/web/src/components/setup/components/steps/DatabaseStep.jsx new file mode 100644 index 00000000..b528174a --- /dev/null +++ b/web/src/components/setup/components/steps/DatabaseStep.jsx @@ -0,0 +1,102 @@ +/* +Copyright (C) 2025 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ + +import React from 'react'; +import { Banner } from '@douyinfe/semi-ui'; + +/** + * 数据库检查步骤组件 + * 显示当前数据库类型和相关警告信息 + */ +const DatabaseStep = ({ setupStatus, renderNavigationButtons, t }) => { + return ( + <> + {/* 数据库警告 */} + {setupStatus.database_type === 'sqlite' && ( + +

+ {t( + '您正在使用 SQLite 数据库。如果您在容器环境中运行,请确保已正确设置数据库文件的持久化映射,否则容器重启后所有数据将丢失!', + )} +

+

+ {t( + '建议在生产环境中使用 MySQL 或 PostgreSQL 数据库,或确保 SQLite 数据库文件已映射到宿主机的持久化存储。', + )} +

+ + } + className="!rounded-lg" + fullMode={false} + bordered + /> + )} + + {/* MySQL数据库提示 */} + {setupStatus.database_type === 'mysql' && ( + +

+ {t( + '您正在使用 MySQL 数据库。MySQL 是一个可靠的关系型数据库管理系统,适合生产环境使用。', + )} +

+ + } + className="!rounded-lg" + fullMode={false} + bordered + /> + )} + + {/* PostgreSQL数据库提示 */} + {setupStatus.database_type === 'postgres' && ( + +

+ {t( + '您正在使用 PostgreSQL 数据库。PostgreSQL 是一个功能强大的开源关系型数据库系统,提供了出色的可靠性和数据完整性,适合生产环境使用。', + )} +

+ + } + className="!rounded-lg" + fullMode={false} + bordered + /> + )} + {renderNavigationButtons && renderNavigationButtons()} + + ); +}; + +export default DatabaseStep; diff --git a/web/src/components/setup/components/steps/UsageModeStep.jsx b/web/src/components/setup/components/steps/UsageModeStep.jsx new file mode 100644 index 00000000..4390c711 --- /dev/null +++ b/web/src/components/setup/components/steps/UsageModeStep.jsx @@ -0,0 +1,71 @@ +/* +Copyright (C) 2025 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ + +import React from 'react'; +import { RadioGroup, Radio } from '@douyinfe/semi-ui'; + +/** + * 使用模式选择步骤组件 + * 提供系统使用模式的选择界面 + */ +const UsageModeStep = ({ + formData, + handleUsageModeChange, + renderNavigationButtons, + t +}) => { + return ( + <> + + + {t('对外运营模式')} + + + {t('自用模式')} + + + {t('演示站点模式')} + + + {renderNavigationButtons && renderNavigationButtons()} + + ); +}; + +export default UsageModeStep; diff --git a/web/src/components/setup/index.jsx b/web/src/components/setup/index.jsx new file mode 100644 index 00000000..8473ee09 --- /dev/null +++ b/web/src/components/setup/index.jsx @@ -0,0 +1,29 @@ +/* +Copyright (C) 2025 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ + +// 主要组件导出 +export { default as SetupWizard } from './SetupWizard'; + +export { default as StepNavigation } from './components/StepNavigation'; + +// 步骤组件导出 +export { default as DatabaseStep } from './components/steps/DatabaseStep'; +export { default as AdminStep } from './components/steps/AdminStep'; +export { default as UsageModeStep } from './components/steps/UsageModeStep'; +export { default as CompleteStep } from './components/steps/CompleteStep'; \ No newline at end of file diff --git a/web/src/components/topup/InvitationCard.jsx b/web/src/components/topup/InvitationCard.jsx index 85b18cc4..57fbbcdc 100644 --- a/web/src/components/topup/InvitationCard.jsx +++ b/web/src/components/topup/InvitationCard.jsx @@ -54,7 +54,7 @@ const InvitationCard = ({ {/* 收益展示区域 */}
{/* 主要收益卡片 - 待使用收益 */} - +
diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 9b0e73e0..c8e3f484 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1439,12 +1439,6 @@ "对外运营模式": "Default mode", "密码长度至少为8个字符": "Password must be at least 8 characters long", "表单引用错误,请刷新页面重试": "Form reference error, please refresh the page and try again", - "默认模式,适用于为多个用户提供服务的场景。": "Default mode, suitable for scenarios where multiple users are provided.", - "此模式下,系统将计算每次调用的用量,您需要对每个模型都设置价格,如果没有设置价格,用户将无法使用该模型。": "In this mode, the system will calculate the usage of each call, you need to set the price for each model, if the price is not set, the user will not be able to use the model.", - "适用于个人使用的场景。": "Suitable for personal use.", - "不需要设置模型价格,系统将弱化用量计算,您可专注于使用模型。": "No need to set the model price, the system will weaken the usage calculation, you can focus on using the model.", - "适用于展示系统功能的场景。": "Suitable for scenarios where the system functions are displayed.", - "可在初始化后修改": "Can be modified after initialization", "初始化系统": "Initialize system", "支持众多的大模型供应商": "Supporting various LLM providers", "统一的大模型接口网关": "The Unified LLMs API Gateway", @@ -1602,21 +1596,20 @@ "暂无公告": "No Notice", "操练场": "Playground", "欢迎使用,请完成以下设置以开始使用系统": "Welcome to use, please complete the following settings to start using the system", + "数据库检查": "Database Check", + "验证数据库连接状态": "Verify database connection status", + "设置管理员登录信息": "Set administrator login information", + "选择系统运行模式": "Select system running mode", + "完成初始化": "Complete initialization", + "确认设置并完成初始化": "Confirm settings and complete initialization", "数据库信息": "Database Information", + "请填写完整的管理员账号信息": "Please fill in the complete administrator account information", + "准备完成初始化": "Ready to complete initialization", + "请确认以下设置信息,点击\"初始化系统\"开始配置": "Please confirm the following settings information, click \"Initialize system\" to start configuration", + "数据库类型": "Database Type", "您正在使用 MySQL 数据库。MySQL 是一个可靠的关系型数据库管理系统,适合生产环境使用。": "You are using the MySQL database. MySQL is a reliable relational database management system, suitable for production environments.", "您正在使用 PostgreSQL 数据库。PostgreSQL 是一个功能强大的开源关系型数据库系统,提供了出色的可靠性和数据完整性,适合生产环境使用。": "You are using the PostgreSQL database. PostgreSQL is a powerful open-source relational database system that provides excellent reliability and data integrity, suitable for production environments.", - "设置系统管理员的登录信息": "Set the login information for the system administrator", "选择适合您使用场景的模式": "Select the mode suitable for your usage scenario", - "使用模式说明": "Usage mode description", - "计费模式": "Billing mode", - "多用户支持": "Multi-user support", - "个人使用": "Personal use", - "功能演示": "Function demonstration", - "体验试用": "Experience trial", - "默认模式": "Default Mode", - "无需计费": "No Charge", - "演示体验": "Demo Experience", - "提供基础功能演示,方便用户了解系统特性。": "Provide basic feature demonstrations to help users understand the system features.", "适用于为多个用户提供服务的场景": "Suitable for scenarios where multiple users are provided.", "适用于个人使用的场景,不需要设置模型价格": "Suitable for personal use, no need to set model price.", "适用于展示系统功能的场景,提供基础功能演示": "Suitable for scenarios where the system functions are displayed, providing basic feature demonstrations.", diff --git a/web/src/index.css b/web/src/index.css index 53c79511..964beb02 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -52,7 +52,7 @@ code { } /* ==================== 导航和侧边栏样式 ==================== */ -/* 导航项样式 */ +.semi-radio, .semi-tagInput, .semi-input-textarea-wrapper, .semi-navigation-sub-title, diff --git a/web/src/pages/Setup/index.js b/web/src/pages/Setup/index.js index 8d72a473..8e2f5cb6 100644 --- a/web/src/pages/Setup/index.js +++ b/web/src/pages/Setup/index.js @@ -17,581 +17,15 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -import React, { useEffect, useState, useRef } from 'react'; -import { - Card, - Form, - Button, - Typography, - Modal, - Banner, - Layout, - Tag, -} from '@douyinfe/semi-ui'; -import { API, showError, showNotice } from '../../helpers'; -import { useTranslation } from 'react-i18next'; -import { - IconHelpCircle, - IconInfoCircle, - IconUser, - IconLock, - IconSetting, - IconCheckCircleStroked, -} from '@douyinfe/semi-icons'; -import { Shield, Rocket, FlaskConical, Database, Layers } from 'lucide-react'; +import React from 'react'; +import { SetupWizard } from '../../components/setup'; +/** + * Setup页面组件 + * 使用新的组件化结构进行系统初始化 + */ const Setup = () => { - const { t } = useTranslation(); - const [loading, setLoading] = useState(false); - const [selfUseModeInfoVisible, setUsageModeInfoVisible] = useState(false); - const [setupStatus, setSetupStatus] = useState({ - status: false, - root_init: false, - database_type: '', - }); - const { Text, Title } = Typography; - const formRef = useRef(null); - - const [formData, setFormData] = useState({ - username: '', - password: '', - confirmPassword: '', - usageMode: 'external', - }); - - useEffect(() => { - fetchSetupStatus(); - }, []); - - const fetchSetupStatus = async () => { - try { - const res = await API.get('/api/setup'); - const { success, data } = res.data; - if (success) { - setSetupStatus(data); - - // If setup is already completed, redirect to home - if (data.status) { - window.location.href = '/'; - } - } else { - showError(t('获取初始化状态失败')); - } - } catch (error) { - console.error('Failed to fetch setup status:', error); - showError(t('获取初始化状态失败')); - } - }; - - const handleUsageModeChange = (val) => { - setFormData({ ...formData, usageMode: val }); - }; - - const onSubmit = () => { - if (!formRef.current) { - console.error('Form reference is null'); - showError(t('表单引用错误,请刷新页面重试')); - return; - } - - const values = formRef.current.getValues(); - console.log('Form values:', values); - - // For root_init=false, validate admin username and password - if (!setupStatus.root_init) { - if (!values.username || !values.username.trim()) { - showError(t('请输入管理员用户名')); - return; - } - - if (!values.password || values.password.length < 8) { - showError(t('密码长度至少为8个字符')); - return; - } - - if (values.password !== values.confirmPassword) { - showError(t('两次输入的密码不一致')); - return; - } - } - - // Prepare submission data - const formValues = { ...values }; - formValues.SelfUseModeEnabled = values.usageMode === 'self'; - formValues.DemoSiteEnabled = values.usageMode === 'demo'; - - // Remove usageMode as it's not needed by the backend - delete formValues.usageMode; - - console.log('Submitting data to backend:', formValues); - setLoading(true); - - // Submit to backend - API.post('/api/setup', formValues) - .then((res) => { - const { success, message } = res.data; - console.log('API response:', res.data); - - if (success) { - showNotice(t('系统初始化成功,正在跳转...')); - setTimeout(() => { - window.location.reload(); - }, 1500); - } else { - showError(message || t('初始化失败,请重试')); - } - }) - .catch((error) => { - console.error('API error:', error); - showError(t('系统初始化失败,请重试')); - setLoading(false); - }) - .finally(() => { - // setLoading(false); - }); - }; - - return ( -
- - -
-
- {/* 主卡片容器 */} - - {/* 顶部装饰性区域 */} - - {/* 装饰性背景元素 */} -
-
-
-
-
- -
-
- -
-
- - {t('系统初始化')} - - - {t('欢迎使用,请完成以下设置以开始使用系统')} - -
-
- {/* 数据库警告 */} - {setupStatus.database_type === 'sqlite' && ( -
- - -
- } - closeIcon={null} - title={ -
- {t('数据库警告')} - - SQLite - -
- } - description={ -
-

- {t( - '您正在使用 SQLite 数据库。如果您在容器环境中运行,请确保已正确设置数据库文件的持久化映射,否则容器重启后所有数据将丢失!', - )} -

-

- {t( - '建议在生产环境中使用 MySQL 或 PostgreSQL 数据库,或确保 SQLite 数据库文件已映射到宿主机的持久化存储。', - )} -

-
- } - className="!rounded-xl mb-6" - fullMode={false} - bordered - /> -
- )} - {/* MySQL数据库提示 */} - {setupStatus.database_type === 'mysql' && ( -
- - -
- } - closeIcon={null} - title={ -
- {t('数据库信息')} - - MySQL - -
- } - description={ -
-

- {t( - '您正在使用 MySQL 数据库。MySQL 是一个可靠的关系型数据库管理系统,适合生产环境使用。', - )} -

-
- } - className="!rounded-xl mb-6" - fullMode={false} - bordered - /> -
- )} - {/* PostgreSQL数据库提示 */} - {setupStatus.database_type === 'postgres' && ( -
- - -
- } - closeIcon={null} - title={ -
- {t('数据库信息')} - - PostgreSQL - -
- } - description={ -
-

- {t( - '您正在使用 PostgreSQL 数据库。PostgreSQL 是一个功能强大的开源关系型数据库系统,提供了出色的可靠性和数据完整性,适合生产环境使用。', - )} -

-
- } - className="!rounded-xl mb-6" - fullMode={false} - bordered - /> -
- )} - - - {/* 主内容区域 */} -
{ - formRef.current = formApi; - console.log('Form API set:', formApi); - }} - initValues={formData} - > - {/* 管理员账号设置 */} - -
-
-
-
-
-
- -
-
- {t('管理员账号')} -
{t('设置系统管理员的登录信息')}
-
-
- - {setupStatus.root_init ? ( - <> - - -
- } - closeIcon={null} - description={ -
- {t('管理员账号已经初始化过,请继续设置其他参数')} -
- } - className="!rounded-lg" - /> - - ) : ( - <> - } - showClear - size='large' - className="mb-4 !rounded-lg" - noLabel={false} - validateStatus="default" - onChange={(value) => - setFormData({ ...formData, username: value }) - } - /> - } - showClear - size='large' - className="mb-4 !rounded-lg" - noLabel={false} - mode="password" - validateStatus="default" - onChange={(value) => - setFormData({ ...formData, password: value }) - } - /> - } - showClear - size='large' - className="!rounded-lg" - noLabel={false} - mode="password" - validateStatus="default" - onChange={(value) => - setFormData({ ...formData, confirmPassword: value }) - } - /> - - )} - - - {/* 使用模式 */} - -
-
-
-
-
-
- -
-
-
- {t('使用模式')} -
-
{t('选择适合您使用场景的模式')}
-
-
- - -
- -
- -
-
-
{t('对外运营模式')}
-
{t('适用于为多个用户提供服务的场景')}
- - {t('默认模式')} - -
-
- } - /> - -
- -
-
-
{t('自用模式')}
-
{t('适用于个人使用的场景,不需要设置模型价格')}
- - {t('无需计费')} - -
-
- } - /> - -
- -
-
-
{t('演示站点模式')}
-
{t('适用于展示系统功能的场景,提供基础功能演示')}
- - {t('演示体验')} - -
-
- } - /> - - - - - -
- -
- - - - - - - {/* 使用模式说明模态框 */} - - - {t('使用模式说明')} - - } - visible={selfUseModeInfoVisible} - onOk={() => setUsageModeInfoVisible(false)} - onCancel={() => setUsageModeInfoVisible(false)} - closeOnEsc={true} - okText={t('我已了解')} - cancelText={null} - centered={true} - size='medium' - className="[&_.semi-modal-body]:!p-6" - > -
- {/* 对外运营模式 */} -
-
-
- -
-
- {t('对外运营模式')} -
-

{t('默认模式,适用于为多个用户提供服务的场景。')}

-

{t('此模式下,系统将计算每次调用的用量,您需要对每个模型都设置价格,如果没有设置价格,用户将无法使用该模型。')}

-
- {t('计费模式')} - {t('多用户支持')} -
-
-
-
-
- - {/* 自用模式 */} -
-
-
- -
-
- {t('自用模式')} -
-

{t('适用于个人使用的场景。')}

-

{t('不需要设置模型价格,系统将弱化用量计算,您可专注于使用模型。')}

-
- {t('无需计费')} - {t('个人使用')} -
-
-
-
-
- - {/* 演示站点模式 */} -
-
-
- -
-
- {t('演示站点模式')} -
-

{t('适用于展示系统功能的场景。')}

-

{t('提供基础功能演示,方便用户了解系统特性。')}

-
- {t('功能演示')} - {t('体验试用')} -
-
-
-
-
-
-
- - ); + return ; }; export default Setup;