mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-20 00:28:38 +00:00
✨ feat(ratio-sync, ui): add built‑in “Official Ratio Preset” and harden upstream sync
Backend (controller/ratio_sync.go): - Add built‑in official upstream to GetSyncableChannels (ID: -100, BaseURL: https://basellm.github.io) - Support absolute endpoint URLs; otherwise join BaseURL + endpoint (defaults to /api/ratio_config) - Harden HTTP client: - IPv4‑first with IPv6 fallback for github.io - Add ResponseHeaderTimeout - 3 attempts with exponential backoff (200/400/800ms) - Validate Content-Type and limit response body to 10MB (safe decode via io.LimitReader) - Robust parsing: support type1 ratio_config map and type2 pricing list - Use net.SplitHostPort for host parsing - Use float tolerance in differences comparison to avoid false mismatches - Remove unused code (tryDirect) and improve warnings Frontend: - UpstreamRatioSync.jsx: auto-assign official endpoint to /llm-metadata/api/newapi/ratio_config-v1-base.json - ChannelSelectorModal.jsx: - Pin the official source at the top of the list - Show a green “官方” tag next to the status - Refactor status renderer to accept the full record Notes: - Backward compatible; no API surface changes - Official ratio_config reference: https://basellm.github.io/llm-metadata/api/newapi/ratio_config-v1-base.json
This commit is contained in:
@@ -18,7 +18,15 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useContext } from 'react';
|
||||
import { Button, Card, Col, Form, Row, Switch, Typography } from '@douyinfe/semi-ui';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Form,
|
||||
Row,
|
||||
Switch,
|
||||
Typography,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { API, showError, showSuccess } from '../../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { StatusContext } from '../../../context/Status';
|
||||
@@ -29,14 +37,14 @@ export default function SettingsHeaderNavModules(props) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [statusState, statusDispatch] = useContext(StatusContext);
|
||||
|
||||
|
||||
// 顶栏模块管理状态
|
||||
const [headerNavModules, setHeaderNavModules] = useState({
|
||||
home: true,
|
||||
console: true,
|
||||
pricing: {
|
||||
enabled: true,
|
||||
requireAuth: false // 默认不需要登录鉴权
|
||||
requireAuth: false, // 默认不需要登录鉴权
|
||||
},
|
||||
docs: true,
|
||||
about: true,
|
||||
@@ -50,7 +58,7 @@ export default function SettingsHeaderNavModules(props) {
|
||||
// 对于pricing模块,只更新enabled属性
|
||||
newModules[moduleKey] = {
|
||||
...newModules[moduleKey],
|
||||
enabled: checked
|
||||
enabled: checked,
|
||||
};
|
||||
} else {
|
||||
newModules[moduleKey] = checked;
|
||||
@@ -64,7 +72,7 @@ export default function SettingsHeaderNavModules(props) {
|
||||
const newModules = { ...headerNavModules };
|
||||
newModules.pricing = {
|
||||
...newModules.pricing,
|
||||
requireAuth: checked
|
||||
requireAuth: checked,
|
||||
};
|
||||
setHeaderNavModules(newModules);
|
||||
}
|
||||
@@ -76,7 +84,7 @@ export default function SettingsHeaderNavModules(props) {
|
||||
console: true,
|
||||
pricing: {
|
||||
enabled: true,
|
||||
requireAuth: false
|
||||
requireAuth: false,
|
||||
},
|
||||
docs: true,
|
||||
about: true,
|
||||
@@ -102,8 +110,8 @@ export default function SettingsHeaderNavModules(props) {
|
||||
type: 'set',
|
||||
payload: {
|
||||
...statusState.status,
|
||||
HeaderNavModules: JSON.stringify(headerNavModules)
|
||||
}
|
||||
HeaderNavModules: JSON.stringify(headerNavModules),
|
||||
},
|
||||
});
|
||||
|
||||
// 刷新父组件状态
|
||||
@@ -130,7 +138,7 @@ export default function SettingsHeaderNavModules(props) {
|
||||
if (typeof modules.pricing === 'boolean') {
|
||||
modules.pricing = {
|
||||
enabled: modules.pricing,
|
||||
requireAuth: false // 默认不需要登录鉴权
|
||||
requireAuth: false, // 默认不需要登录鉴权
|
||||
};
|
||||
}
|
||||
|
||||
@@ -142,7 +150,7 @@ export default function SettingsHeaderNavModules(props) {
|
||||
console: true,
|
||||
pricing: {
|
||||
enabled: true,
|
||||
requireAuth: false
|
||||
requireAuth: false,
|
||||
},
|
||||
docs: true,
|
||||
about: true,
|
||||
@@ -157,35 +165,37 @@ export default function SettingsHeaderNavModules(props) {
|
||||
{
|
||||
key: 'home',
|
||||
title: t('首页'),
|
||||
description: t('用户主页,展示系统信息')
|
||||
description: t('用户主页,展示系统信息'),
|
||||
},
|
||||
{
|
||||
key: 'console',
|
||||
title: t('控制台'),
|
||||
description: t('用户控制面板,管理账户')
|
||||
description: t('用户控制面板,管理账户'),
|
||||
},
|
||||
{
|
||||
key: 'pricing',
|
||||
title: t('模型广场'),
|
||||
description: t('模型定价,需要登录访问'),
|
||||
hasSubConfig: true // 标识该模块有子配置
|
||||
hasSubConfig: true, // 标识该模块有子配置
|
||||
},
|
||||
{
|
||||
key: 'docs',
|
||||
title: t('文档'),
|
||||
description: t('系统文档和帮助信息')
|
||||
description: t('系统文档和帮助信息'),
|
||||
},
|
||||
{
|
||||
key: 'about',
|
||||
title: t('关于'),
|
||||
description: t('关于系统的详细信息')
|
||||
}
|
||||
description: t('关于系统的详细信息'),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Form.Section text={t('顶栏管理')} extraText={t('控制顶栏模块显示状态,全局生效')}>
|
||||
|
||||
<Form.Section
|
||||
text={t('顶栏管理')}
|
||||
extraText={t('控制顶栏模块显示状态,全局生效')}
|
||||
>
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: '24px' }}>
|
||||
{moduleConfigs.map((module) => (
|
||||
<Col key={module.key} xs={24} sm={12} md={6} lg={6} xl={6}>
|
||||
@@ -195,34 +205,38 @@ export default function SettingsHeaderNavModules(props) {
|
||||
border: '1px solid var(--semi-color-border)',
|
||||
transition: 'all 0.2s ease',
|
||||
background: 'var(--semi-color-bg-1)',
|
||||
minHeight: '80px'
|
||||
minHeight: '80px',
|
||||
}}
|
||||
bodyStyle={{ padding: '16px' }}
|
||||
hoverable
|
||||
>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
height: '100%'
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: 1, textAlign: 'left' }}>
|
||||
<div style={{
|
||||
fontWeight: '600',
|
||||
fontSize: '14px',
|
||||
color: 'var(--semi-color-text-0)',
|
||||
marginBottom: '4px'
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: '600',
|
||||
fontSize: '14px',
|
||||
color: 'var(--semi-color-text-0)',
|
||||
marginBottom: '4px',
|
||||
}}
|
||||
>
|
||||
{module.title}
|
||||
</div>
|
||||
<Text
|
||||
type="secondary"
|
||||
size="small"
|
||||
type='secondary'
|
||||
size='small'
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
color: 'var(--semi-color-text-2)',
|
||||
lineHeight: '1.4',
|
||||
display: 'block'
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
{module.description}
|
||||
@@ -230,78 +244,94 @@ export default function SettingsHeaderNavModules(props) {
|
||||
</div>
|
||||
<div style={{ marginLeft: '16px' }}>
|
||||
<Switch
|
||||
checked={module.key === 'pricing' ? headerNavModules[module.key]?.enabled : headerNavModules[module.key]}
|
||||
checked={
|
||||
module.key === 'pricing'
|
||||
? headerNavModules[module.key]?.enabled
|
||||
: headerNavModules[module.key]
|
||||
}
|
||||
onChange={handleHeaderNavModuleChange(module.key)}
|
||||
size="default"
|
||||
size='default'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 为模型广场添加权限控制子开关 */}
|
||||
{module.key === 'pricing' && (module.key === 'pricing' ? headerNavModules[module.key]?.enabled : headerNavModules[module.key]) && (
|
||||
<div style={{
|
||||
borderTop: '1px solid var(--semi-color-border)',
|
||||
marginTop: '12px',
|
||||
paddingTop: '12px'
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<div style={{ flex: 1, textAlign: 'left' }}>
|
||||
<div style={{
|
||||
fontWeight: '500',
|
||||
fontSize: '12px',
|
||||
color: 'var(--semi-color-text-1)',
|
||||
marginBottom: '2px'
|
||||
}}>
|
||||
{t('需要登录访问')}
|
||||
{module.key === 'pricing' &&
|
||||
(module.key === 'pricing'
|
||||
? headerNavModules[module.key]?.enabled
|
||||
: headerNavModules[module.key]) && (
|
||||
<div
|
||||
style={{
|
||||
borderTop: '1px solid var(--semi-color-border)',
|
||||
marginTop: '12px',
|
||||
paddingTop: '12px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: 1, textAlign: 'left' }}>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: '500',
|
||||
fontSize: '12px',
|
||||
color: 'var(--semi-color-text-1)',
|
||||
marginBottom: '2px',
|
||||
}}
|
||||
>
|
||||
{t('需要登录访问')}
|
||||
</div>
|
||||
<Text
|
||||
type='secondary'
|
||||
size='small'
|
||||
style={{
|
||||
fontSize: '11px',
|
||||
color: 'var(--semi-color-text-2)',
|
||||
lineHeight: '1.4',
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
{t('开启后未登录用户无法访问模型广场')}
|
||||
</Text>
|
||||
</div>
|
||||
<div style={{ marginLeft: '16px' }}>
|
||||
<Switch
|
||||
checked={
|
||||
headerNavModules.pricing?.requireAuth || false
|
||||
}
|
||||
onChange={handlePricingAuthChange}
|
||||
size='small'
|
||||
/>
|
||||
</div>
|
||||
<Text
|
||||
type="secondary"
|
||||
size="small"
|
||||
style={{
|
||||
fontSize: '11px',
|
||||
color: 'var(--semi-color-text-2)',
|
||||
lineHeight: '1.4',
|
||||
display: 'block'
|
||||
}}
|
||||
>
|
||||
{t('开启后未登录用户无法访问模型广场')}
|
||||
</Text>
|
||||
</div>
|
||||
<div style={{ marginLeft: '16px' }}>
|
||||
<Switch
|
||||
checked={headerNavModules.pricing?.requireAuth || false}
|
||||
onChange={handlePricingAuthChange}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
|
||||
</Row>
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
gap: '12px',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
paddingTop: '8px',
|
||||
borderTop: '1px solid var(--semi-color-border)'
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '12px',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
paddingTop: '8px',
|
||||
borderTop: '1px solid var(--semi-color-border)',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size='default'
|
||||
type='tertiary'
|
||||
onClick={resetHeaderNavModules}
|
||||
style={{
|
||||
borderRadius: '6px',
|
||||
fontWeight: '500'
|
||||
fontWeight: '500',
|
||||
}}
|
||||
>
|
||||
{t('重置为默认')}
|
||||
@@ -314,7 +344,7 @@ export default function SettingsHeaderNavModules(props) {
|
||||
style={{
|
||||
borderRadius: '6px',
|
||||
fontWeight: '500',
|
||||
minWidth: '100px'
|
||||
minWidth: '100px',
|
||||
}}
|
||||
>
|
||||
{t('保存设置')}
|
||||
|
||||
Reference in New Issue
Block a user