diff --git a/web/src/components/layout/PageLayout.js b/web/src/components/layout/PageLayout.js index e25901ef..17d16fc0 100644 --- a/web/src/components/layout/PageLayout.js +++ b/web/src/components/layout/PageLayout.js @@ -11,7 +11,7 @@ import { API, getLogo, getSystemName, showError, setStatusData } from '../../hel import { UserContext } from '../../context/User/index.js'; import { StatusContext } from '../../context/Status/index.js'; import { useLocation } from 'react-router-dom'; -const { Sider, Content, Header, Footer } = Layout; +const { Sider, Content, Header } = Layout; const PageLayout = () => { const [userState, userDispatch] = useContext(UserContext); @@ -94,8 +94,6 @@ const PageLayout = () => { { }; return ( -
- - +
+
+
+ {/* 主卡片容器 */} + + {/* 顶部用户信息区域 */} + + {/* 装饰性背景元素 */} +
+
+
+
+
-
-
- {/* 主卡片容器 */} - - {/* 顶部用户信息区域 */} - - {/* 装饰性背景元素 */} -
-
-
-
-
- -
-
-
- - {getAvatarText()} - -
-
- {getUsername()} -
-
- {isRoot() ? ( - - {t('超级管理员')} - - ) : isAdmin() ? ( - - {t('管理员')} - - ) : ( - - {t('普通用户')} - - )} - - ID: {userState?.user?.id} - -
-
-
-
- -
-
- -
-
- {t('当前余额')} -
-
- {renderQuota(userState?.user?.quota)} -
-
- -
-
-
-
- {t('历史消耗')} -
-
- {renderQuota(userState?.user?.used_quota)} -
-
-
-
- {t('请求次数')} -
-
- {userState.user?.request_count || 0} -
-
-
-
- {t('用户分组')} -
-
- {userState?.user?.group || t('默认')} -
-
-
-
- -
-
-
- - {/* 主内容区域 - 使用Tabs组织不同功能模块 */} -
- - {/* 可用模型Tab */} - - - {t('可用模型')} -
- } - itemKey='models' +
+
+
+ -
- {/* 可用模型部分 */} -
-
-
- -
-
- {t('模型列表')} -
{t('点击模型名称可复制')}
+ {getAvatarText()} + +
+
+ {getUsername()} +
+
+ {isRoot() ? ( + + {t('超级管理员')} + + ) : isAdmin() ? ( + + {t('管理员')} + + ) : ( + + {t('普通用户')} + + )} + + ID: {userState?.user?.id} + +
+
+
+
+ +
+
+ +
+
+ {t('当前余额')} +
+
+ {renderQuota(userState?.user?.quota)} +
+
+ +
+
+
+
+ {t('历史消耗')} +
+
+ {renderQuota(userState?.user?.used_quota)} +
+
+
+
+ {t('请求次数')} +
+
+ {userState.user?.request_count || 0} +
+
+
+
+ {t('用户分组')} +
+
+ {userState?.user?.group || t('默认')} +
+
+
+
+ +
+
+ + + {/* 主内容区域 - 使用Tabs组织不同功能模块 */} +
+ + {/* 可用模型Tab */} + + + {t('可用模型')} +
+ } + itemKey='models' + > +
+ {/* 可用模型部分 */} +
+
+
+ +
+
+ {t('模型列表')} +
{t('点击模型名称可复制')}
+
+
+ + {modelsLoading ? ( + // 骨架屏加载状态 - 模拟实际加载后的布局 +
+ {/* 模拟分类标签 */} +
+
+ {Array.from({ length: 8 }).map((_, index) => ( + + ))}
- {modelsLoading ? ( - // 骨架屏加载状态 - 模拟实际加载后的布局 -
- {/* 模拟分类标签 */} -
-
- {Array.from({ length: 8 }).map((_, index) => ( - - ))} -
-
- - {/* 模拟模型标签列表 */} -
- {Array.from({ length: 20 }).map((_, index) => ( - - ))} -
-
- ) : models.length === 0 ? ( -
- } - darkModeImage={} - description={t('没有可用模型')} - style={{ padding: '24px 0' }} + {/* 模拟模型标签列表 */} +
+ {Array.from({ length: 20 }).map((_, index) => ( + -
- ) : ( - <> - {/* 模型分类标签页 */} -
- setActiveModelCategory(key)} - className="mt-2" - > - {Object.entries(getModelCategories(t)).map(([key, category]) => { - // 计算该分类下的模型数量 - const modelCount = key === 'all' - ? models.length - : models.filter(model => category.filter({ model_name: model })).length; + ))} +
+
+ ) : models.length === 0 ? ( +
+ } + darkModeImage={} + description={t('没有可用模型')} + style={{ padding: '24px 0' }} + /> +
+ ) : ( + <> + {/* 模型分类标签页 */} +
+ setActiveModelCategory(key)} + className="mt-2" + > + {Object.entries(getModelCategories(t)).map(([key, category]) => { + // 计算该分类下的模型数量 + const modelCount = key === 'all' + ? models.length + : models.filter(model => category.filter({ model_name: model })).length; - if (modelCount === 0 && key !== 'all') return null; + if (modelCount === 0 && key !== 'all') return null; - return ( - - {category.icon && {category.icon}} - {category.label} - - {modelCount} - - - } - itemKey={key} - key={key} - /> - ); - })} - -
+ return ( + + {category.icon && {category.icon}} + {category.label} + + {modelCount} + + + } + itemKey={key} + key={key} + /> + ); + })} + +
-
- {(() => { - // 根据当前选中的分类过滤模型 - const categories = getModelCategories(t); - const filteredModels = activeModelCategory === 'all' - ? models - : models.filter(model => categories[activeModelCategory].filter({ model_name: model })); +
+ {(() => { + // 根据当前选中的分类过滤模型 + const categories = getModelCategories(t); + const filteredModels = activeModelCategory === 'all' + ? models + : models.filter(model => categories[activeModelCategory].filter({ model_name: model })); - // 如果过滤后没有模型,显示空状态 - if (filteredModels.length === 0) { - return ( - } - darkModeImage={} - description={t('该分类下没有可用模型')} - style={{ padding: '16px 0' }} - /> - ); - } + // 如果过滤后没有模型,显示空状态 + if (filteredModels.length === 0) { + return ( + } + darkModeImage={} + description={t('该分类下没有可用模型')} + style={{ padding: '16px 0' }} + /> + ); + } - if (filteredModels.length <= MODELS_DISPLAY_COUNT) { - return ( + if (filteredModels.length <= MODELS_DISPLAY_COUNT) { + return ( + + {filteredModels.map((model) => ( + renderModelTag(model, { + size: 'large', + shape: 'circle', + onClick: () => copyText(model), + }) + ))} + + ); + } else { + return ( + <> + {filteredModels.map((model) => ( renderModelTag(model, { @@ -638,527 +649,513 @@ const PersonalSetting = () => { onClick: () => copyText(model), }) ))} + setIsModelsExpanded(false)} + icon={} + > + {t('收起')} + + + + {!isModelsExpanded && ( + + {filteredModels + .slice(0, MODELS_DISPLAY_COUNT) + .map((model) => ( + renderModelTag(model, { + size: 'large', + shape: 'circle', + onClick: () => copyText(model), + }) + ))} + setIsModelsExpanded(true)} + icon={} + > + {t('更多')} {filteredModels.length - MODELS_DISPLAY_COUNT} {t('个模型')} + - ); - } else { - return ( - <> - - - {filteredModels.map((model) => ( - renderModelTag(model, { - size: 'large', - shape: 'circle', - onClick: () => copyText(model), - }) - ))} - setIsModelsExpanded(false)} - icon={} - > - {t('收起')} - - - - {!isModelsExpanded && ( - - {filteredModels - .slice(0, MODELS_DISPLAY_COUNT) - .map((model) => ( - renderModelTag(model, { - size: 'large', - shape: 'circle', - onClick: () => copyText(model), - }) - ))} - setIsModelsExpanded(true)} - icon={} - > - {t('更多')} {filteredModels.length - MODELS_DISPLAY_COUNT} {t('个模型')} - - - )} - - ); - } - })()} -
- - )} -
-
- - - {/* 账户绑定Tab */} - - - {t('账户绑定')} -
- } - itemKey='account' - > -
-
- {/* 邮箱绑定 */} - -
-
-
- -
-
-
{t('邮箱')}
-
- {userState.user && userState.user.email !== '' - ? userState.user.email - : t('未绑定')} -
-
-
- -
-
- - {/* 微信绑定 */} - -
-
-
- -
-
-
{t('微信')}
-
- {userState.user && userState.user.wechat_id !== '' - ? t('已绑定') - : t('未绑定')} -
-
-
- -
-
- - {/* GitHub绑定 */} - -
-
-
- -
-
-
{t('GitHub')}
-
- {userState.user && userState.user.github_id !== '' - ? userState.user.github_id - : t('未绑定')} -
-
-
- -
-
- - {/* OIDC绑定 */} - -
-
-
- -
-
-
{t('OIDC')}
-
- {userState.user && userState.user.oidc_id !== '' - ? userState.user.oidc_id - : t('未绑定')} -
-
-
- -
-
- - {/* Telegram绑定 */} - -
-
-
- -
-
-
{t('Telegram')}
-
- {userState.user && userState.user.telegram_id !== '' - ? userState.user.telegram_id - : t('未绑定')} -
-
-
-
- {status.telegram_oauth ? ( - userState.user.telegram_id !== '' ? ( - - ) : ( -
- -
- ) - ) : ( - - )} -
-
-
- - {/* LinuxDO绑定 */} - -
-
-
- -
-
-
{t('LinuxDO')}
-
- {userState.user && userState.user.linux_do_id !== '' - ? userState.user.linux_do_id - : t('未绑定')} -
-
-
- -
-
-
-
- - - {/* 安全设置Tab */} - - - {t('安全设置')} -
- } - itemKey='security' - > -
-
- - {/* 系统访问令牌 */} - -
-
-
- -
-
- - {t('系统访问令牌')} - - - {t('用于API调用的身份验证令牌,请妥善保管')} - - {systemToken && ( -
- } - /> -
)} -
-
- -
-
+ + ); + } + })()} +
+ + )} +
+
+ - {/* 密码管理 */} - -
-
-
- -
-
- - {t('密码管理')} - - - {t('定期更改密码可以提高账户安全性')} - -
-
- + {/* 账户绑定Tab */} + + + {t('账户绑定')} +
+ } + itemKey='account' + > +
+
+ {/* 邮箱绑定 */} + +
+
+
+ +
+
+
{t('邮箱')}
+
+ {userState.user && userState.user.email !== '' + ? userState.user.email + : t('未绑定')}
- - - {/* 危险区域 */} - -
-
-
- -
-
- - {t('删除账户')} - - - {t('此操作不可逆,所有数据将被永久删除')} - -
-
- -
-
- -
-
- - - {/* 通知设置Tab */} - - - {t('其他设置')} -
- } - itemKey='notification' - > -
- - +
+ +
+ + + {/* 微信绑定 */} + +
+
+
+ +
+
+
{t('微信')}
+
+ {userState.user && userState.user.wechat_id !== '' + ? t('已绑定') + : t('未绑定')}
+
+
+ +
+
- {/* Webhook设置 */} - {notificationSettings.warningType === 'webhook' && ( -
-
- {t('Webhook地址')} - - handleNotificationSettingChange('webhookUrl', val) - } - placeholder={t('请输入Webhook地址,例如: https://example.com/webhook')} - size="large" - className="!rounded-lg" - prefix={} - /> -
- {t('只支持https,系统将以 POST 方式发送通知,请确保地址可以接收 POST 请求')} -
-
+ {/* GitHub绑定 */} + +
+
+
+ +
+
+
{t('GitHub')}
+
+ {userState.user && userState.user.github_id !== '' + ? userState.user.github_id + : t('未绑定')} +
+
+
+ +
+
-
- {t('接口凭证(可选)')} + {/* OIDC绑定 */} + +
+
+
+ +
+
+
{t('OIDC')}
+
+ {userState.user && userState.user.oidc_id !== '' + ? userState.user.oidc_id + : t('未绑定')} +
+
+
+ +
+
+ + {/* Telegram绑定 */} + +
+
+
+ +
+
+
{t('Telegram')}
+
+ {userState.user && userState.user.telegram_id !== '' + ? userState.user.telegram_id + : t('未绑定')} +
+
+
+
+ {status.telegram_oauth ? ( + userState.user.telegram_id !== '' ? ( + + ) : ( +
+ +
+ ) + ) : ( + + )} +
+
+
+ + {/* LinuxDO绑定 */} + +
+
+
+ +
+
+
{t('LinuxDO')}
+
+ {userState.user && userState.user.linux_do_id !== '' + ? userState.user.linux_do_id + : t('未绑定')} +
+
+
+ +
+
+
+
+ + + {/* 安全设置Tab */} + + + {t('安全设置')} +
+ } + itemKey='security' + > +
+
+ + {/* 系统访问令牌 */} + +
+
+
+ +
+
+ + {t('系统访问令牌')} + + + {t('用于API调用的身份验证令牌,请妥善保管')} + + {systemToken && ( +
- handleNotificationSettingChange('webhookSecret', val) - } - placeholder={t('请输入密钥')} + readOnly + value={systemToken} + onClick={handleSystemTokenClick} size="large" className="!rounded-lg" prefix={} /> -
- {t('密钥将以 Bearer 方式添加到请求头中,用于验证webhook请求的合法性')} -
+ )} +
+
+ +
+
-
-
setShowWebhookDocs(!showWebhookDocs)}> -
- - - {t('Webhook请求结构')} - -
- {showWebhookDocs ? : } -
- -
-                                        {`{
+                        {/* 密码管理 */}
+                        
+                          
+
+
+ +
+
+ + {t('密码管理')} + + + {t('定期更改密码可以提高账户安全性')} + +
+
+ +
+
+ + {/* 危险区域 */} + +
+
+
+ +
+
+ + {t('删除账户')} + + + {t('此操作不可逆,所有数据将被永久删除')} + +
+
+ +
+
+ +
+
+ + + {/* 通知设置Tab */} + + + {t('其他设置')} +
+ } + itemKey='notification' + > +
+ + +
+ {/* 通知方式选择 */} +
+ {t('通知方式')} + + handleNotificationSettingChange('warningType', value) + } + type="pureCard" + > + +
+ +
+
{t('邮件通知')}
+
{t('通过邮件接收通知')}
+
+
+
+ +
+ +
+
{t('Webhook通知')}
+
{t('通过HTTP请求接收通知')}
+
+
+
+
+
+ + {/* Webhook设置 */} + {notificationSettings.warningType === 'webhook' && ( +
+
+ {t('Webhook地址')} + + handleNotificationSettingChange('webhookUrl', val) + } + placeholder={t('请输入Webhook地址,例如: https://example.com/webhook')} + size="large" + className="!rounded-lg" + prefix={} + /> +
+ {t('只支持https,系统将以 POST 方式发送通知,请确保地址可以接收 POST 请求')} +
+
+ +
+ {t('接口凭证(可选)')} + + handleNotificationSettingChange('webhookSecret', val) + } + placeholder={t('请输入密钥')} + size="large" + className="!rounded-lg" + prefix={} + /> +
+ {t('密钥将以 Bearer 方式添加到请求头中,用于验证webhook请求的合法性')} +
+
+ +
+
setShowWebhookDocs(!showWebhookDocs)}> +
+ + + {t('Webhook请求结构')} + +
+ {showWebhookDocs ? : } +
+ +
+                                    {`{
   "type": "quota_exceed",      // 通知类型
   "title": "标题",             // 通知标题
   "content": "通知内容",       // 通知内容,支持 {{value}} 变量占位符
@@ -1174,158 +1171,156 @@ const PersonalSetting = () => {
   "values": ["$0.99"],
   "timestamp": 1739950503
 }`}
-                                      
-
-
-
- )} - - {/* 邮件设置 */} - {notificationSettings.warningType === 'email' && ( -
- {t('通知邮箱')} - - handleNotificationSettingChange('notificationEmail', val) - } - placeholder={t('留空则使用账号绑定的邮箱')} - size="large" - className="!rounded-lg" - prefix={} - /> -
- {t('设置用于接收额度预警的邮箱地址,不填则使用账号绑定的邮箱')} -
-
- )} - - {/* 预警阈值 */} -
- - {t('额度预警阈值')} {renderQuotaWithPrompt(notificationSettings.warningThreshold)} - - - handleNotificationSettingChange('warningThreshold', val) - } - size="large" - className="!rounded-lg w-full max-w-xs" - placeholder={t('请输入预警额度')} - data={[ - { value: 100000, label: '0.2$' }, - { value: 500000, label: '1$' }, - { value: 1000000, label: '5$' }, - { value: 5000000, label: '10$' }, - ]} - prefix={} - /> -
- {t('当剩余额度低于此数值时,系统将通过选择的方式发送通知')} -
+ +
-
+ )} - -
-
- {/* 接受未设置价格模型 */} -
-
-
- -
-
-
-
- - {t('接受未设置价格模型')} - -
- {t('当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用')} -
-
- - handleNotificationSettingChange( - 'acceptUnsetModelRatioModel', - e.target.checked, - ) - } - className="ml-4" - /> -
-
-
-
+ {/* 邮件设置 */} + {notificationSettings.warningType === 'email' && ( +
+ {t('通知邮箱')} + + handleNotificationSettingChange('notificationEmail', val) + } + placeholder={t('留空则使用账号绑定的邮箱')} + size="large" + className="!rounded-lg" + prefix={} + /> +
+ {t('设置用于接收额度预警的邮箱地址,不填则使用账号绑定的邮箱')}
- + )} - -
-
-
-
- -
-
-
-
- - {t('记录请求与错误日志 IP')} - -
- {t('开启后,仅“消费”和“错误”日志将记录您的客户端 IP 地址')} -
-
- - handleNotificationSettingChange( - 'recordIpLog', - e.target.checked, - ) - } - className="ml-4" - /> -
-
-
-
+ {/* 预警阈值 */} +
+ + {t('额度预警阈值')} {renderQuotaWithPrompt(notificationSettings.warningThreshold)} + + + handleNotificationSettingChange('warningThreshold', val) + } + size="large" + className="!rounded-lg w-full max-w-xs" + placeholder={t('请输入预警额度')} + data={[ + { value: 100000, label: '0.2$' }, + { value: 500000, label: '1$' }, + { value: 1000000, label: '5$' }, + { value: 5000000, label: '10$' }, + ]} + prefix={} + /> +
+ {t('当剩余额度低于此数值时,系统将通过选择的方式发送通知')}
- - - -
- +
-
-
- -
- + + + +
+
+ {/* 接受未设置价格模型 */} +
+
+
+ +
+
+
+
+ + {t('接受未设置价格模型')} + +
+ {t('当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用')} +
+
+ + handleNotificationSettingChange( + 'acceptUnsetModelRatioModel', + e.target.checked, + ) + } + className="ml-4" + /> +
+
+
+
+
+
+
+ + +
+
+
+
+ +
+
+
+
+ + {t('记录请求与错误日志 IP')} + +
+ {t('开启后,仅“消费”和“错误”日志将记录您的客户端 IP 地址')} +
+
+ + handleNotificationSettingChange( + 'recordIpLog', + e.target.checked, + ) + } + className="ml-4" + /> +
+
+
+
+
+
+ + +
+ +
+
+
+
-
- - + +
+
{/* 邮箱绑定模态框 */} {
-
+
{/* 主卡片容器 */} - + {/* 顶部状态卡片 */} diff --git a/web/src/pages/About/index.js b/web/src/pages/About/index.js index 3259449e..032562ca 100644 --- a/web/src/pages/About/index.js +++ b/web/src/pages/About/index.js @@ -105,7 +105,7 @@ const About = () => { ); return ( - <> +
{aboutLoaded && about === '' ? (
{ )} )} - +
); }; diff --git a/web/src/pages/Channel/index.js b/web/src/pages/Channel/index.js index dc9f8c48..9e43f318 100644 --- a/web/src/pages/Channel/index.js +++ b/web/src/pages/Channel/index.js @@ -3,9 +3,9 @@ import ChannelsTable from '../../components/table/ChannelsTable'; const File = () => { return ( - <> +
- +
); }; diff --git a/web/src/pages/Chat/index.js b/web/src/pages/Chat/index.js index 22c5c1e2..4b354752 100644 --- a/web/src/pages/Chat/index.js +++ b/web/src/pages/Chat/index.js @@ -37,12 +37,12 @@ const ChatPage = () => { return !isLoading && iframeSrc ? (