fix: 首页导航栏样式对齐 tokendance.space + 主题切换修复
- 首页导航字体/样式对齐 tokendance.space(text-[14px]、gap-1.5、SVG 图标) - 全局 Navigation.jsx 和 MobileSiteNavDropdown.jsx 添加 SVG 图标 - 首页 HeroSection header 固定定位(sticky→fixed),导航链接靠右 - PageLayout 首页时隐藏全局 HeaderBar,避免导航重叠/文字不清 - 修复 HomeThemeToggle 主题切换失效(使用正确的 localStorage key 和 DOM 操作) - 首页文字 TokenFactory→TuringToken,logo 靠左对齐 - i18n 多语言文件同步更新
This commit is contained in:
parent
1474917163
commit
e00999b88c
|
|
@ -0,0 +1,509 @@
|
|||
/*
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { lazy, Suspense, useContext, useMemo } from 'react';
|
||||
import { Route, Routes, useLocation, useParams } from 'react-router-dom';
|
||||
import Loading from './components/common/ui/Loading';
|
||||
import User from './pages/User';
|
||||
import { AuthRedirect, PrivateRoute, AdminRoute } from './helpers';
|
||||
import RegisterForm from './components/auth/RegisterForm';
|
||||
import LoginForm from './components/auth/LoginForm';
|
||||
import NotFound from './pages/NotFound';
|
||||
import Forbidden from './pages/Forbidden';
|
||||
import Setting from './pages/Setting';
|
||||
import { StatusContext } from './context/Status';
|
||||
|
||||
import PasswordResetForm from './components/auth/PasswordResetForm';
|
||||
import PasswordResetConfirm from './components/auth/PasswordResetConfirm';
|
||||
import Channel from './pages/Channel';
|
||||
import Token from './pages/Token';
|
||||
import Redemption from './pages/Redemption';
|
||||
import TopUp from './pages/TopUp';
|
||||
import Log from './pages/Log';
|
||||
import Chat from './pages/Chat';
|
||||
import Chat2Link from './pages/Chat2Link';
|
||||
import Midjourney from './pages/Midjourney';
|
||||
import Pricing from './pages/Pricing';
|
||||
import Task from './pages/Task';
|
||||
import ModelPage from './pages/Model';
|
||||
import ModelDeploymentPage from './pages/ModelDeployment';
|
||||
import ModelHeatPage from './pages/ModelHeat';
|
||||
import Playground from './pages/Playground';
|
||||
import Subscription from './pages/Subscription';
|
||||
import OAuth2Callback from './components/auth/OAuth2Callback';
|
||||
import PersonalSetting from './components/settings/PersonalSetting';
|
||||
import SupplierApplyPage from './pages/Supplier/Apply';
|
||||
import SupplierChannelPage from './pages/Supplier/Channel';
|
||||
import PricingSettingsPage from './pages/Supplier/PricingSettings';
|
||||
import SupplierDashboardPage from './pages/Supplier/Dashboard';
|
||||
import SupplierApplication from './pages/SupplierAdmin/application';
|
||||
import Suppliers from './pages/SupplierAdmin/list';
|
||||
import Setup from './pages/Setup';
|
||||
import SetupCheck from './components/layout/SetupCheck';
|
||||
import OperationLog from './pages/OperationLog';
|
||||
|
||||
const Home = lazy(() => import('./pages/Home'));
|
||||
const Dashboard = lazy(() => import('./pages/Dashboard'));
|
||||
const DistributorApply = lazy(() => import('./pages/DistributorApply'));
|
||||
const DistributorCenter = lazy(() => import('./pages/DistributorCenter'));
|
||||
const DistributorAdmin = lazy(() => import('./pages/DistributorAdmin'));
|
||||
const InviteRedirect = lazy(() => import('./pages/InviteRedirect'));
|
||||
const About = lazy(() => import('./pages/About'));
|
||||
const UserAgreement = lazy(() => import('./pages/UserAgreement'));
|
||||
const PrivacyPolicy = lazy(() => import('./pages/PrivacyPolicy'));
|
||||
|
||||
function DynamicOAuth2Callback() {
|
||||
const { provider } = useParams();
|
||||
return <OAuth2Callback type={provider} />;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const location = useLocation();
|
||||
const [statusState] = useContext(StatusContext);
|
||||
|
||||
// 获取模型广场权限配置
|
||||
const pricingRequireAuth = useMemo(() => {
|
||||
const headerNavModulesConfig = statusState?.status?.HeaderNavModules;
|
||||
if (headerNavModulesConfig) {
|
||||
try {
|
||||
const modules = JSON.parse(headerNavModulesConfig);
|
||||
|
||||
// 处理向后兼容性:如果pricing是boolean,默认不需要登录
|
||||
if (typeof modules.pricing === 'boolean') {
|
||||
return false; // 默认不需要登录鉴权
|
||||
}
|
||||
|
||||
// 如果是对象格式,使用requireAuth配置
|
||||
return modules.pricing?.requireAuth === true;
|
||||
} catch (error) {
|
||||
console.error('解析顶栏模块配置失败:', error);
|
||||
return false; // 默认不需要登录
|
||||
}
|
||||
}
|
||||
return false; // 默认不需要登录
|
||||
}, [statusState?.status?.HeaderNavModules]);
|
||||
|
||||
return (
|
||||
<SetupCheck>
|
||||
<Routes>
|
||||
<Route
|
||||
path='/'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<Home />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/setup'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<Setup />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route path='/forbidden' element={<Forbidden />} />
|
||||
<Route
|
||||
path='/console/models'
|
||||
element={
|
||||
<AdminRoute>
|
||||
<ModelPage />
|
||||
</AdminRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/deployment'
|
||||
element={
|
||||
<AdminRoute>
|
||||
<ModelDeploymentPage />
|
||||
</AdminRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/model-heat'
|
||||
element={
|
||||
<AdminRoute>
|
||||
<ModelHeatPage />
|
||||
</AdminRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/subscription'
|
||||
element={
|
||||
<AdminRoute>
|
||||
<Subscription />
|
||||
</AdminRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/channel'
|
||||
element={
|
||||
<AdminRoute>
|
||||
<Channel />
|
||||
</AdminRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/token'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Token />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/playground'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Playground />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/redemption'
|
||||
element={
|
||||
<AdminRoute>
|
||||
<Redemption />
|
||||
</AdminRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/user'
|
||||
element={
|
||||
<AdminRoute>
|
||||
<User />
|
||||
</AdminRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/supplier-application'
|
||||
element={
|
||||
<AdminRoute>
|
||||
<SupplierApplication />
|
||||
</AdminRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/suppliers'
|
||||
element={
|
||||
<AdminRoute>
|
||||
<Suppliers />
|
||||
</AdminRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/user/reset'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<PasswordResetConfirm />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/login'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<AuthRedirect>
|
||||
<LoginForm />
|
||||
</AuthRedirect>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/register'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<AuthRedirect>
|
||||
<RegisterForm />
|
||||
</AuthRedirect>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/r/:aff'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<InviteRedirect />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/reset'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<PasswordResetForm />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/oauth/github'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<OAuth2Callback type='github'></OAuth2Callback>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/oauth/discord'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<OAuth2Callback type='discord'></OAuth2Callback>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/oauth/oidc'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>}>
|
||||
<OAuth2Callback type='oidc'></OAuth2Callback>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/oauth/linuxdo'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<OAuth2Callback type='linuxdo'></OAuth2Callback>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/oauth/:provider'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<DynamicOAuth2Callback />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/setting'
|
||||
element={
|
||||
<AdminRoute>
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<Setting />
|
||||
</Suspense>
|
||||
</AdminRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/personal'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<PersonalSetting />
|
||||
</Suspense>
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/supplier'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<SupplierApplyPage />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/supplier/apply'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<SupplierApplyPage />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/supplier/channel'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<SupplierChannelPage />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/supplier/pricing-settings'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<PricingSettingsPage />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/supplier/dashboard'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<SupplierDashboardPage />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/distributor/apply'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<DistributorApply />
|
||||
</Suspense>
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/distributor/center'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<DistributorCenter />
|
||||
</Suspense>
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/distributor/admin'
|
||||
element={
|
||||
<AdminRoute>
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<DistributorAdmin />
|
||||
</Suspense>
|
||||
</AdminRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/topup'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<TopUp />
|
||||
</Suspense>
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/log'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Log />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/operation-log'
|
||||
element={
|
||||
<AdminRoute>
|
||||
<OperationLog />
|
||||
</AdminRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<Dashboard />
|
||||
</Suspense>
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/midjourney'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<Midjourney />
|
||||
</Suspense>
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/task'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<Task />
|
||||
</Suspense>
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/pricing'
|
||||
element={
|
||||
pricingRequireAuth ? (
|
||||
<PrivateRoute>
|
||||
<Suspense
|
||||
fallback={<Loading></Loading>}
|
||||
key={location.pathname}
|
||||
>
|
||||
<Pricing />
|
||||
</Suspense>
|
||||
</PrivateRoute>
|
||||
) : (
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<Pricing />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/about'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<About />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/user-agreement'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<UserAgreement />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/privacy-policy'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<PrivacyPolicy />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/console/chat/:id?'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<Chat />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
{/* 方便使用chat2link直接跳转聊天... */}
|
||||
<Route
|
||||
path='/chat2link'
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<Chat2Link />
|
||||
</Suspense>
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route path='*' element={<NotFound />} />
|
||||
</Routes>
|
||||
</SetupCheck>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,2 @@
|
|||
import sys
|
||||
print(" hello\)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import sys
|
||||
c=open(sys.argv[1,'r',encoding='utf-8').read()
|
||||
o=c
|
||||
print(len(c))
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import sys, re
|
||||
c = open(sys.argv[1], chr(39)+chr(114)+chr(39)+chr(44)+chr(101)+chr(110)+chr(99)+chr(111)+chr(100)+chr(105)+chr(110)+chr(103)+chr(61)+chr(39)+chr(117)+chr(116)+chr(102)+chr(45)+chr(56)+chr(39)+chr(41)+chr(46)+chr(114)+chr(101)+chr(97)+chr(100)+chr(40)+chr(41)
|
||||
o = c
|
||||
c = c.replace(chr(60)+chr(100)+chr(105)+chr(118)+chr(32)+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(78)+chr(97)+chr(109)+chr(101)+chr(61)+chr(34)+chr(103)+chr(114)+chr(105)+chr(100)+chr(32)+chr(109)+chr(100)+chr(58)+chr(103)+chr(114)+chr(105)+chr(100)+chr(45)+chr(99)+chr(111)+chr(108)+chr(115)+chr(45)+chr(51)+chr(32)+chr(98)+chr(111)+chr(114)+chr(100)+chr(101)+chr(114)+chr(32)+chr(98)+chr(111)+chr(114)+chr(100)+chr(101)+chr(114)+chr(45)+chr(98)+chr(111)+chr(114)+chr(100)+chr(101)+chr(114)+chr(34)+chr(62), chr(60)+chr(100)+chr(105)+chr(118)+chr(32)+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(78)+chr(97)+chr(109)+chr(101)+chr(61)+chr(34)+chr(103)+chr(114)+chr(105)+chr(100)+chr(32)+chr(115)+chr(109)+chr(58)+chr(103)+chr(114)+chr(105)+chr(100)+chr(45)+chr(99)+chr(111)+chr(108)+chr(115)+chr(45)+chr(50)+chr(32)+chr(108)+chr(103)+chr(58)+chr(103)+chr(114)+chr(105)+chr(100)+chr(45)+chr(99)+chr(111)+chr(108)+chr(115)+chr(45)+chr(51)+chr(34)+chr(62))
|
||||
c = c.replace(chr(116)+chr(114)+chr(97)+chr(110)+chr(115)+chr(105)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(99)+chr(111)+chr(108)+chr(111)+chr(114)+chr(115)+chr(32)+chr(100)+chr(117)+chr(114)+chr(97)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(49)+chr(48)+chr(48)+chr(32)+chr(104)+chr(111)+chr(118)+chr(101)+chr(114)+chr(58)+chr(98)+chr(103)+chr(45)+chr(102)+chr(111)+chr(114)+chr(101)+chr(103)+chr(114)+chr(111)+chr(117)+chr(110)+chr(100)+chr(32)+chr(104)+chr(111)+chr(118)+chr(101)+chr(114)+chr(58)+chr(116)+chr(101)+chr(120)+chr(116)+chr(45)+chr(98)+chr(97)+chr(99)+chr(107)+chr(103)+chr(114)+chr(111)+chr(117)+chr(110)+chr(100), chr(116)+chr(114)+chr(97)+chr(110)+chr(115)+chr(105)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(97)+chr(108)+chr(108)+chr(32)+chr(100)+chr(117)+chr(114)+chr(97)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(50)+chr(48)+chr(48)+chr(32)+chr(104)+chr(111)+chr(118)+chr(101)+chr(114)+chr(58)+chr(98)+chr(103)+chr(45)+chr(102)+chr(111)+chr(114)+chr(101)+chr(103)+chr(114)+chr(111)+chr(117)+chr(110)+chr(100)+chr(32)+chr(104)+chr(111)+chr(118)+chr(101)+chr(114)+chr(58)+chr(116)+chr(101)+chr(120)+chr(116)+chr(45)+chr(98)+chr(97)+chr(99)+chr(107)+chr(103)+chr(114)+chr(111)+chr(117)+chr(110)+chr(100)+chr(32)+chr(98)+chr(111)+chr(114)+chr(100)+chr(101)+chr(114)+chr(45)+chr(98)+chr(32)+chr(115)+chr(109)+chr(58)+chr(98)+chr(111)+chr(114)+chr(100)+chr(101)+chr(114)+chr(45)+chr(114)+chr(32)+chr(98)+chr(111)+chr(114)+chr(100)+chr(101)+chr(114)+chr(45)+chr(98)+chr(111)+chr(114)+chr(100)+chr(101)+chr(114))
|
||||
c = c.replace(chr(98)+chr(103)+chr(45)+chr(109)+chr(117)+chr(116)+chr(101)+chr(100)+chr(32)+chr(112)+chr(120)+chr(45)+chr(50)+chr(32)+chr(112)+chr(121)+chr(45)+chr(49)+chr(32)+chr(119)+chr(45)+chr(102)+chr(105)+chr(116)+chr(32)+chr(103)+chr(114)+chr(111)+chr(117)+chr(112)+chr(45)+chr(104)+chr(111)+chr(118)+chr(101)+chr(114)+chr(58)+chr(98)+chr(103)+chr(45)+chr(119)+chr(104)+chr(105)+chr(116)+chr(101)+chr(47)+chr(49)+chr(48)+chr(32)+chr(103)+chr(114)+chr(111)+chr(117)+chr(112)+chr(45)+chr(104)+chr(111)+chr(118)+chr(101)+chr(114)+chr(58)+chr(116)+chr(101)+chr(120)+chr(116)+chr(45)+chr(98)+chr(97)+chr(99)+chr(107)+chr(103)+chr(114)+chr(111)+chr(117)+chr(110)+chr(100)+chr(32)+chr(116)+chr(114)+chr(97)+chr(110)+chr(115)+chr(105)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(99)+chr(111)+chr(108)+chr(111)+chr(114)+chr(115)+chr(32)+chr(100)+chr(117)+chr(114)+chr(97)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(49)+chr(48)+chr(48), chr(98)+chr(111)+chr(114)+chr(100)+chr(101)+chr(114)+chr(32)+chr(98)+chr(111)+chr(114)+chr(100)+chr(101)+chr(114)+chr(45)+chr(102)+chr(111)+chr(114)+chr(101)+chr(103)+chr(114)+chr(111)+chr(117)+chr(110)+chr(100)+chr(47)+chr(49)+chr(48)+chr(32)+chr(112)+chr(120)+chr(45)+chr(50)+chr(32)+chr(112)+chr(121)+chr(45)+chr(49)+chr(32)+chr(119)+chr(45)+chr(102)+chr(105)+chr(116)+chr(32)+chr(103)+chr(114)+chr(111)+chr(117)+chr(112)+chr(45)+chr(104)+chr(111)+chr(118)+chr(101)+chr(114)+chr(58)+chr(98)+chr(111)+chr(114)+chr(100)+chr(101)+chr(114)+chr(45)+chr(98)+chr(97)+chr(99)+chr(107)+chr(103)+chr(114)+chr(111)+chr(117)+chr(110)+chr(100)+chr(47)+chr(51)+chr(48)+chr(32)+chr(103)+chr(114)+chr(111)+chr(117)+chr(112)+chr(45)+chr(104)+chr(111)+chr(118)+chr(101)+chr(114)+chr(58)+chr(116)+chr(101)+chr(120)+chr(116)+chr(45)+chr(98)+chr(97)+chr(99)+chr(107)+chr(103)+chr(114)+chr(111)+chr(117)+chr(110)+chr(100)+chr(32)+chr(116)+chr(114)+chr(97)+chr(110)+chr(115)+chr(105)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(97)+chr(108)+chr(108)+chr(32)+chr(100)+chr(117)+chr(114)+chr(97)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(50)+chr(48)+chr(48))
|
||||
c = c.replace(chr(116)+chr(114)+chr(97)+chr(110)+chr(115)+chr(105)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(99)+chr(111)+chr(108)+chr(111)+chr(114)+chr(115)+chr(32)+chr(100)+chr(117)+chr(114)+chr(97)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(49)+chr(48)+chr(48), chr(116)+chr(114)+chr(97)+chr(110)+chr(115)+chr(105)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(97)+chr(108)+chr(108)+chr(32)+chr(100)+chr(117)+chr(114)+chr(97)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(50)+chr(48)+chr(48))
|
||||
c = c.replace(chr(102)+chr(111)+chr(110)+chr(116)+chr(45)+chr(104)+chr(101)+chr(97)+chr(100)+chr(105)+chr(110)+chr(103)+chr(32)+chr(116)+chr(101)+chr(120)+chr(116)+chr(45)+chr(108)+chr(103)+chr(32)+chr(102)+chr(111)+chr(110)+chr(116)+chr(45)+chr(98)+chr(111)+chr(108)+chr(100)+chr(32)+chr(109)+chr(98)+chr(45)+chr(50)+chr(32)+chr(116)+chr(101)+chr(120)+chr(116)+chr(45)+chr(102)+chr(111)+chr(114)+chr(101)+chr(103)+chr(114)+chr(111)+chr(117)+chr(110)+chr(100)+chr(32)+chr(103)+chr(114)+chr(111)+chr(117)+chr(112)+chr(45)+chr(104)+chr(111)+chr(118)+chr(101)+chr(114)+chr(58)+chr(116)+chr(101)+chr(120)+chr(116)+chr(45)+chr(98)+chr(97)+chr(99)+chr(107)+chr(103)+chr(114)+chr(111)+chr(117)+chr(110)+chr(100), chr(102)+chr(111)+chr(110)+chr(116)+chr(45)+chr(104)+chr(101)+chr(97)+chr(100)+chr(105)+chr(110)+chr(103)+chr(32)+chr(116)+chr(101)+chr(120)+chr(116)+chr(45)+chr(108)+chr(103)+chr(32)+chr(102)+chr(111)+chr(110)+chr(116)+chr(45)+chr(98)+chr(111)+chr(108)+chr(100)+chr(32)+chr(109)+chr(98)+chr(45)+chr(50)+chr(32)+chr(116)+chr(101)+chr(120)+chr(116)+chr(45)+chr(102)+chr(111)+chr(114)+chr(101)+chr(103)+chr(114)+chr(111)+chr(117)+chr(110)+chr(100)+chr(32)+chr(103)+chr(114)+chr(111)+chr(117)+chr(112)+chr(45)+chr(104)+chr(111)+chr(118)+chr(101)+chr(114)+chr(58)+chr(116)+chr(101)+chr(120)+chr(116)+chr(45)+chr(98)+chr(97)+chr(99)+chr(107)+chr(103)+chr(114)+chr(111)+chr(117)+chr(110)+chr(100)+chr(32)+chr(116)+chr(114)+chr(97)+chr(110)+chr(115)+chr(105)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(97)+chr(108)+chr(108)+chr(32)+chr(100)+chr(117)+chr(114)+chr(97)+chr(116)+chr(105)+chr(111)+chr(110)+chr(45)+chr(50)+chr(48)+chr(48))
|
||||
import re
|
||||
c = re.sub(chr(92)+chr(36)+chr(92)+chr(123)+chr(91)+chr(94)+chr(125)+chr(93)+chr(43)+chr(92)+chr(125), '', c)
|
||||
c = c.replace(chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(78)+chr(97)+chr(109)+chr(101)+chr(61)+chr(96)+chr(103)+chr(114)+chr(111)+chr(117)+chr(112)+chr(32), chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(78)+chr(97)+chr(109)+chr(101)+chr(61)+chr(34)+chr(103)+chr(114)+chr(111)+chr(117)+chr(112)+chr(32))
|
||||
print(chr(67)+chr(72)+chr(65)+chr(78)+chr(71)+chr(69)+chr(68) if c != o else chr(78)+chr(79)+chr(67)+chr(72)+chr(65)+chr(78)+chr(71)+chr(69))
|
||||
if c != o:
|
||||
open(sys.argv[1], chr(119)+chr(44)+chr(101)+chr(110)+chr(99)+chr(111)+chr(100)+chr(105)+chr(110)+chr(103)+chr(61)+chr(39)+chr(117)+chr(116)+chr(102)+chr(45)+chr(56)+chr(39)+chr(41)+chr(46)+chr(119)+chr(114)+chr(105)+chr(116)+chr(101)+chr(40)+chr(99)
|
||||
|
|
@ -0,0 +1 @@
|
|||
print(" not using template\)
|
||||
|
|
@ -0,0 +1 @@
|
|||
#
|
||||
|
|
@ -0,0 +1 @@
|
|||
# test
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 507 KiB |
|
|
@ -0,0 +1 @@
|
|||
aW1wb3J0IHN5cwpwcmludCgndGVzdCcp
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package rankings_setting
|
||||
|
||||
import "github.com/QuantumNous/new-api/setting/config"
|
||||
|
||||
// ModelBenchmark 单个模型的评测数据
|
||||
type ModelBenchmark struct {
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
Intelligence float64 `json:"intelligence"` // 智能指数
|
||||
Speed float64 `json:"speed"` // 输出速度 (tokens/s)
|
||||
Price float64 `json:"price"` // 每百万 token 价格 ($)
|
||||
Latency float64 `json:"latency"` // 首 token 延迟 (秒)
|
||||
ContextWindow string `json:"context_window"` // 上下文窗口大小描述
|
||||
Type string `json:"type"` // reasoning / non-reasoning
|
||||
Openness string `json:"openness"` // proprietary / open-weights
|
||||
Category string `json:"category"` // language / image / video
|
||||
}
|
||||
|
||||
// BenchmarkSetting 评测数据配置
|
||||
type BenchmarkSetting struct {
|
||||
Models []ModelBenchmark `json:"models"`
|
||||
}
|
||||
|
||||
// 默认评测数据
|
||||
var benchmarkSetting = BenchmarkSetting{
|
||||
Models: []ModelBenchmark{
|
||||
{Name: "Claude Fable 5", Provider: "Anthropic", Intelligence: 60, Speed: 42.0, Price: 15.00, Latency: 0.45, ContextWindow: "200K", Type: "reasoning", Openness: "proprietary", Category: "language"},
|
||||
{Name: "Claude Opus 4.8", Provider: "Anthropic", Intelligence: 56, Speed: 38.0, Price: 15.00, Latency: 0.50, ContextWindow: "200K", Type: "reasoning", Openness: "proprietary", Category: "language"},
|
||||
{Name: "GPT-5.5", Provider: "OpenAI", Intelligence: 55, Speed: 85.0, Price: 10.00, Latency: 0.35, ContextWindow: "128K", Type: "reasoning", Openness: "proprietary", Category: "language"},
|
||||
{Name: "Claude Opus 4.7", Provider: "Anthropic", Intelligence: 54, Speed: 40.0, Price: 15.00, Latency: 0.48, ContextWindow: "200K", Type: "reasoning", Openness: "proprietary", Category: "language"},
|
||||
{Name: "MiniMax-M3", Provider: "MiniMax", Intelligence: 44, Speed: 120.0, Price: 2.00, Latency: 0.30, ContextWindow: "128K", Type: "reasoning", Openness: "open-weights", Category: "language"},
|
||||
{Name: "DeepSeek V4 Pro", Provider: "DeepSeek", Intelligence: 44, Speed: 95.0, Price: 1.50, Latency: 0.32, ContextWindow: "128K", Type: "reasoning", Openness: "open-weights", Category: "language"},
|
||||
{Name: "Kimi K2.6", Provider: "Kimi", Intelligence: 43, Speed: 88.0, Price: 2.50, Latency: 0.38, ContextWindow: "128K", Type: "reasoning", Openness: "proprietary", Category: "language"},
|
||||
{Name: "Qwen3.5 0.8B", Provider: "Alibaba", Intelligence: 18, Speed: 250.0, Price: 0.01, Latency: 0.25, ContextWindow: "32K", Type: "non-reasoning", Openness: "open-weights", Category: "language"},
|
||||
{Name: "Gemini 2.5 Flash", Provider: "Google", Intelligence: 38, Speed: 180.0, Price: 0.15, Latency: 0.37, ContextWindow: "1M", Type: "non-reasoning", Openness: "proprietary", Category: "language"},
|
||||
{Name: "Llama 4 Scout", Provider: "Meta", Intelligence: 35, Speed: 150.0, Price: 0.10, Latency: 0.40, ContextWindow: "10M", Type: "non-reasoning", Openness: "open-weights", Category: "language"},
|
||||
{Name: "Mercury 2", Provider: "Inception Labs", Intelligence: 22, Speed: 839.3, Price: 0.50, Latency: 0.28, ContextWindow: "32K", Type: "non-reasoning", Openness: "proprietary", Category: "language"},
|
||||
{Name: "GPT-4.1", Provider: "OpenAI", Intelligence: 42, Speed: 72.0, Price: 2.00, Latency: 0.42, ContextWindow: "128K", Type: "non-reasoning", Openness: "proprietary", Category: "language"},
|
||||
{Name: "Claude Sonnet 4.6", Provider: "Anthropic", Intelligence: 45, Speed: 68.0, Price: 3.00, Latency: 0.44, ContextWindow: "200K", Type: "non-reasoning", Openness: "proprietary", Category: "language"},
|
||||
{Name: "Gemma 3n E4B", Provider: "Google", Intelligence: 20, Speed: 300.0, Price: 0.02, Latency: 0.22, ContextWindow: "32K", Type: "non-reasoning", Openness: "open-weights", Category: "language"},
|
||||
{Name: "Command A+", Provider: "Cohere", Intelligence: 32, Speed: 130.0, Price: 0.80, Latency: 0.39, ContextWindow: "128K", Type: "non-reasoning", Openness: "proprietary", Category: "language"},
|
||||
{Name: "Grok 4", Provider: "xAI", Intelligence: 46, Speed: 75.0, Price: 5.00, Latency: 0.40, ContextWindow: "128K", Type: "reasoning", Openness: "proprietary", Category: "language"},
|
||||
{Name: "North Mini Code", Provider: "North AI", Intelligence: 28, Speed: 200.0, Price: 0.25, Latency: 0.31, ContextWindow: "64K", Type: "non-reasoning", Openness: "proprietary", Category: "language"},
|
||||
{Name: "LFM2 1.2B", Provider: "Liquid", Intelligence: 10, Speed: 542.6, Price: 0.05, Latency: 0.18, ContextWindow: "16K", Type: "non-reasoning", Openness: "open-weights", Category: "language"},
|
||||
{Name: "Gemini 2.0 Pro", Provider: "Google", Intelligence: 48, Speed: 55.0, Price: 2.50, Latency: 0.50, ContextWindow: "2M", Type: "reasoning", Openness: "proprietary", Category: "language"},
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
config.GlobalConfig.Register("benchmark_data", &benchmarkSetting)
|
||||
}
|
||||
|
||||
// GetBenchmarkSetting 获取评测数据配置
|
||||
func GetBenchmarkSetting() *BenchmarkSetting {
|
||||
return &benchmarkSetting
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
hello
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
- generic [ref=e2]:
|
||||
- generic [ref=e5]:
|
||||
- banner [ref=e6]:
|
||||
- generic [ref=e7]:
|
||||
- link "TokenDance 词元跳动 BETA" [ref=e8] [cursor=pointer]:
|
||||
- /url: /
|
||||
- img "TokenDance 词元跳动" [ref=e9]
|
||||
- generic [ref=e10]: BETA
|
||||
- generic [ref=e12]:
|
||||
- img [ref=e13]
|
||||
- searchbox "搜索模型..." [ref=e16]
|
||||
- navigation [ref=e17]:
|
||||
- link "模型" [ref=e18] [cursor=pointer]:
|
||||
- /url: /models
|
||||
- img [ref=e19]
|
||||
- generic [ref=e22]: 模型
|
||||
- link "体验" [ref=e23] [cursor=pointer]:
|
||||
- /url: /playground
|
||||
- img [ref=e24]
|
||||
- generic [ref=e26]: 体验
|
||||
- generic [ref=e27]:
|
||||
- link "评测" [ref=e28] [cursor=pointer]:
|
||||
- /url: /benchmarks
|
||||
- img [ref=e29]
|
||||
- generic [ref=e35]: 评测
|
||||
- link "数据" [ref=e36] [cursor=pointer]:
|
||||
- /url: /rankings
|
||||
- img [ref=e37]
|
||||
- generic [ref=e39]: 数据
|
||||
- link "文档" [ref=e40] [cursor=pointer]:
|
||||
- /url: /docs/quickstart
|
||||
- img [ref=e41]
|
||||
- generic [ref=e44]: 文档
|
||||
- link "登录" [ref=e46] [cursor=pointer]:
|
||||
- /url: /login
|
||||
- generic [ref=e47]: 登录
|
||||
- generic [ref=e48]:
|
||||
- generic [ref=e50]:
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]:
|
||||
- paragraph [ref=e53]: // unified model API gateway
|
||||
- heading "让每位 AI 创造者, 少走一步弯路" [level=1] [ref=e55]:
|
||||
- text: 让每位 AI 创造者,
|
||||
- text: 少走一步弯路
|
||||
- paragraph [ref=e56]: 兼容 OpenAI / Claude / Gemini 等协议,覆盖文本、图像、视频、语音,智能路由,统一计费。
|
||||
- generic [ref=e57]:
|
||||
- link "免费开始" [ref=e58] [cursor=pointer]:
|
||||
- /url: /keys
|
||||
- link "查看文档" [ref=e59] [cursor=pointer]:
|
||||
- /url: /docs/quickstart
|
||||
- link "战略合作方 无问芯穹" [ref=e61] [cursor=pointer]:
|
||||
- /url: https://cloud.infini-ai.com
|
||||
- generic [ref=e62]: 战略合作方
|
||||
- img "无问芯穹" [ref=e63]
|
||||
- generic [ref=e65]:
|
||||
- generic [ref=e70]: terminal
|
||||
- button "Copy" [ref=e71] [cursor=pointer]:
|
||||
- img [ref=e72]
|
||||
- code [ref=e76]: "$ curl https://tokendance.space/gateway/v1/chat/completions \\ -H \"Content-Type: application/json\" \\ -H \"Authorization: Bearer YOUR_KEY\" \\ -d '{ \"model\": \"MODEL_ID\", \"messages\": [{ \"role\": \"user\", \"content\": \"Hello World!\" }] }'"
|
||||
- paragraph [ref=e78]: 200 OK · latency 312ms · tokens 42
|
||||
- generic [ref=e81]:
|
||||
- generic [ref=e82]: TOKEN
|
||||
- generic [ref=e83]: DANCE
|
||||
- generic [ref=e85]:
|
||||
- generic [ref=e86]: OpenAI 协议
|
||||
- generic [ref=e87]: Claude 协议
|
||||
- generic [ref=e88]: Gemini 协议
|
||||
- generic [ref=e89]: 智能路由
|
||||
- generic [ref=e90]: 统一计费
|
||||
- generic [ref=e91]: 模型丰富
|
||||
- generic [ref=e92]: 安全可靠
|
||||
- generic [ref=e93]: 开箱即用
|
||||
- generic [ref=e94]: OpenAI 协议
|
||||
- generic [ref=e95]: Claude 协议
|
||||
- generic [ref=e96]: Gemini 协议
|
||||
- generic [ref=e97]: 智能路由
|
||||
- generic [ref=e98]: 统一计费
|
||||
- generic [ref=e99]: 模型丰富
|
||||
- generic [ref=e100]: 安全可靠
|
||||
- generic [ref=e101]: 开箱即用
|
||||
- generic [ref=e102]: OpenAI 协议
|
||||
- generic [ref=e103]: Claude 协议
|
||||
- generic [ref=e104]: Gemini 协议
|
||||
- generic [ref=e105]: 智能路由
|
||||
- generic [ref=e106]: 统一计费
|
||||
- generic [ref=e107]: 模型丰富
|
||||
- generic [ref=e108]: 安全可靠
|
||||
- generic [ref=e109]: 开箱即用
|
||||
- generic [ref=e110]: OpenAI 协议
|
||||
- generic [ref=e111]: Claude 协议
|
||||
- generic [ref=e112]: Gemini 协议
|
||||
- generic [ref=e113]: 智能路由
|
||||
- generic [ref=e114]: 统一计费
|
||||
- generic [ref=e115]: 模型丰富
|
||||
- generic [ref=e116]: 安全可靠
|
||||
- generic [ref=e117]: 开箱即用
|
||||
- generic [ref=e119]:
|
||||
- generic [ref=e120]: Features
|
||||
- heading "为接入 AI 模型的开发者而造。" [level=2] [ref=e121]
|
||||
- generic [ref=e122]:
|
||||
- generic [ref=e123]:
|
||||
- code [ref=e124]: "baseURL: \"tokendance.space\""
|
||||
- heading "多协议兼容" [level=3] [ref=e125]
|
||||
- paragraph [ref=e126]: 原生支持 OpenAI、Claude、Gemini 文本协议,覆盖图像 / 视频 / 文本转语音生成。无需修改代码,切换 Base URL 即可接入。
|
||||
- generic [ref=e127]:
|
||||
- code [ref=e128]: route(model) → provider
|
||||
- heading "智能路由" [level=3] [ref=e129]
|
||||
- paragraph [ref=e130]: 根据模型名称自动路由至对应供应商。一个入口,无需关心底层调度。
|
||||
- generic [ref=e131]:
|
||||
- code [ref=e132]: billing.unified()
|
||||
- heading "统一计费" [level=3] [ref=e133]
|
||||
- paragraph [ref=e134]: 跨供应商统一 Token 消耗统计与账单。告别多平台分别充值的混乱。
|
||||
- generic [ref=e135]:
|
||||
- code [ref=e136]: "fallback: model[] → provider[]"
|
||||
- heading "容错降级" [level=3] [ref=e137]
|
||||
- paragraph [ref=e138]: 同一模型支持多供应商端点自动切换;单次请求可指定多个候选模型,逐级降级,保障服务持续可用。
|
||||
- generic [ref=e139]:
|
||||
- code [ref=e140]: models.list() → 8+
|
||||
- heading "模型丰富" [level=3] [ref=e141]
|
||||
- paragraph [ref=e142]: 接入 MiniMax、通义千问、Kimi、智谱、DeepSeek 等国内头部模型。持续扩展中。
|
||||
- generic [ref=e143]:
|
||||
- code [ref=e144]: import OpenAI from "openai"
|
||||
- heading "开箱即用" [level=3] [ref=e145]
|
||||
- paragraph [ref=e146]: Watcha 一键登录,分钟级接入。兼容现有 SDK,零迁移成本。
|
||||
- generic [ref=e148]:
|
||||
- generic [ref=e149]:
|
||||
- generic [ref=e150]: // how it works
|
||||
- heading "三步接入,分钟级上线" [level=2] [ref=e151]
|
||||
- link "01 注册账号 通过 Watcha 一键登录,即刻开始使用。 →" [ref=e152] [cursor=pointer]:
|
||||
- /url: /login
|
||||
- generic [ref=e153]:
|
||||
- generic [ref=e155]: "01"
|
||||
- generic [ref=e156]:
|
||||
- heading "注册账号" [level=3] [ref=e157]
|
||||
- paragraph [ref=e158]: 通过 Watcha 一键登录,即刻开始使用。
|
||||
- generic [ref=e159]: →
|
||||
- link "02 创建 API Key 在控制台创建密钥,支持多 Key 管理与权限控制。 →" [ref=e160] [cursor=pointer]:
|
||||
- /url: /keys
|
||||
- generic [ref=e161]:
|
||||
- generic [ref=e163]: "02"
|
||||
- generic [ref=e164]:
|
||||
- heading "创建 API Key" [level=3] [ref=e165]
|
||||
- paragraph [ref=e166]: 在控制台创建密钥,支持多 Key 管理与权限控制。
|
||||
- generic [ref=e167]: →
|
||||
- link "03 发起请求 使用你熟悉的 SDK 调用任意模型,完全兼容原生协议。 →" [ref=e168] [cursor=pointer]:
|
||||
- /url: /docs/quickstart
|
||||
- generic [ref=e169]:
|
||||
- generic [ref=e171]: "03"
|
||||
- generic [ref=e172]:
|
||||
- heading "发起请求" [level=3] [ref=e173]
|
||||
- paragraph [ref=e174]: 使用你熟悉的 SDK 调用任意模型,完全兼容原生协议。
|
||||
- generic [ref=e175]: →
|
||||
- generic [ref=e178]:
|
||||
- paragraph [ref=e179]: Quick Start
|
||||
- heading "快速开始" [level=2] [ref=e180]
|
||||
- generic [ref=e181]:
|
||||
- button "OpenAI" [ref=e182] [cursor=pointer]
|
||||
- button "Claude" [ref=e183] [cursor=pointer]
|
||||
- button "Gemini" [ref=e184] [cursor=pointer]
|
||||
- generic [ref=e185]:
|
||||
- generic [ref=e186]:
|
||||
- generic [ref=e187]:
|
||||
- button "cURL" [ref=e188] [cursor=pointer]
|
||||
- button "Python" [ref=e189] [cursor=pointer]
|
||||
- button "Node.js" [ref=e190] [cursor=pointer]
|
||||
- button "Copy" [ref=e191] [cursor=pointer]:
|
||||
- img [ref=e192]
|
||||
- code [ref=e197]:
|
||||
- generic [ref=e198]: curl https://tokendance.space/gateway/v1/chat/completions \
|
||||
- generic [ref=e199]: "-H \"Authorization: Bearer YOUR_API_KEY\" \\"
|
||||
- generic [ref=e200]: "-H \"Content-Type: application/json\" \\"
|
||||
- generic [ref=e201]: "-d '{"
|
||||
- generic [ref=e202]: "\"model\": \"MODEL_ID\","
|
||||
- generic [ref=e203]: "\"messages\": [{\"role\": \"user\", \"content\": \"Hello!\"}]"
|
||||
- generic [ref=e204]: "}'"
|
||||
- link "Documentation 阅读完整文档,了解更多用法 →" [ref=e205] [cursor=pointer]:
|
||||
- /url: /docs/quickstart
|
||||
- generic [ref=e206]:
|
||||
- paragraph [ref=e207]: Documentation
|
||||
- paragraph [ref=e208]: 阅读完整文档,了解更多用法
|
||||
- generic [ref=e209]: →
|
||||
- generic [ref=e211]:
|
||||
- generic [ref=e212]: // pricing
|
||||
- heading "获取 Token 额度" [level=2] [ref=e213]
|
||||
- generic [ref=e214]:
|
||||
- generic [ref=e215]:
|
||||
- generic [ref=e216]: 6/30 截止
|
||||
- generic [ref=e217]: 注册即送 10 元额度
|
||||
- generic [ref=e218]: 新用户注册即赠 10 元 Token,7 天有效期
|
||||
- link "百亿 Token 补贴计划 AI 时代,Token 正在成为硬通货。当 Agent 开始吞噬软件,一个人也可以像一支队伍。我们在内测期间开启百亿 Token 补贴计划,赋能 AI 时代的超级个体 —— 无论你正在验证场景、打磨产品,还是准备扩大规模,都欢迎申请。 查看详情 →" [ref=e219] [cursor=pointer]:
|
||||
- /url: https://mp.weixin.qq.com/s/D_ZmohSbk1RiR1W1W4EWDQ
|
||||
- heading "百亿 Token 补贴计划" [level=3] [ref=e220]
|
||||
- paragraph [ref=e221]: AI 时代,Token 正在成为硬通货。当 Agent 开始吞噬软件,一个人也可以像一支队伍。我们在内测期间开启百亿 Token 补贴计划,赋能 AI 时代的超级个体 —— 无论你正在验证场景、打磨产品,还是准备扩大规模,都欢迎申请。
|
||||
- generic [ref=e222]: 查看详情 →
|
||||
- generic [ref=e223]:
|
||||
- generic [ref=e224]:
|
||||
- generic [ref=e226]: 限量 500 张
|
||||
- heading "浦发观猹联名卡" [level=3] [ref=e227]
|
||||
- generic [ref=e228]: 「浦耳猹」
|
||||
- paragraph [ref=e229]: 前 200 位办理用户,赠送近千万 Token(折合 RMB 100 元)
|
||||
- generic [ref=e230]:
|
||||
- link "立即申请 →" [ref=e231] [cursor=pointer]:
|
||||
- /url: https://mp.weixin.qq.com/s/kWmrIU4IYxuCIDzLFQAvXg
|
||||
- link "Token 申请问卷 →" [ref=e232] [cursor=pointer]:
|
||||
- /url: https://agentuniverse.feishu.cn/share/base/form/shrcnvZvZq6hK2xgBvVGIuKpR5e
|
||||
- generic [ref=e233]:
|
||||
- heading "观猹开发者计划" [level=3] [ref=e234]
|
||||
- paragraph [ref=e235]: 加入开发者计划,赠送超值 Token 额度。获取技术支持与优先体验新模型的机会。
|
||||
- link "了解详情 →" [ref=e236] [cursor=pointer]:
|
||||
- /url: https://agentuniverse.feishu.cn/wiki/J2FPwJp7zi6D6wklFbrcPbVNnNe
|
||||
- paragraph [ref=e241]: 免费注册,分钟级接入。兼容你现有的 SDK 和工作流。
|
||||
- contentinfo [ref=e242]:
|
||||
- generic [ref=e243]:
|
||||
- generic [ref=e244]:
|
||||
- generic [ref=e245]:
|
||||
- link "文档" [ref=e246] [cursor=pointer]:
|
||||
- /url: /docs/quickstart
|
||||
- link "模型" [ref=e247] [cursor=pointer]:
|
||||
- /url: /models
|
||||
- link "控制台" [ref=e248] [cursor=pointer]:
|
||||
- /url: /keys
|
||||
- link "战略合作方 无问芯穹" [ref=e249] [cursor=pointer]:
|
||||
- /url: https://cloud.infini-ai.com
|
||||
- generic [ref=e250]: 战略合作方
|
||||
- img "无问芯穹" [ref=e251]
|
||||
- generic [ref=e252]:
|
||||
- generic [ref=e253]: © 2026 TokenDance
|
||||
- generic [ref=e254]:
|
||||
- link "浙ICP备2024107375号-10" [ref=e255] [cursor=pointer]:
|
||||
- /url: https://beian.miit.gov.cn/
|
||||
- link "浙公网安备33019202002952号" [ref=e256] [cursor=pointer]:
|
||||
- /url: https://beian.mps.gov.cn/#/query/webSearch?code=33019202002952
|
||||
- region "Notifications (F8)":
|
||||
- list
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
- generic [ref=e2]:
|
||||
- generic [ref=e5]:
|
||||
- banner [ref=e6]:
|
||||
- generic [ref=e7]:
|
||||
- link "TokenDance 词元跳动 BETA" [ref=e8] [cursor=pointer]:
|
||||
- /url: /
|
||||
- img "TokenDance 词元跳动" [ref=e9]
|
||||
- generic [ref=e10]: BETA
|
||||
- generic [ref=e12]:
|
||||
- img [ref=e13]
|
||||
- searchbox "搜索模型..." [ref=e16]
|
||||
- navigation [ref=e17]:
|
||||
- link "模型" [ref=e18] [cursor=pointer]:
|
||||
- /url: /models
|
||||
- img [ref=e19]
|
||||
- generic [ref=e22]: 模型
|
||||
- link "体验" [ref=e23] [cursor=pointer]:
|
||||
- /url: /playground
|
||||
- img [ref=e24]
|
||||
- generic [ref=e26]: 体验
|
||||
- generic [ref=e27]:
|
||||
- link "评测" [ref=e28] [cursor=pointer]:
|
||||
- /url: /benchmarks
|
||||
- img [ref=e29]
|
||||
- generic [ref=e35]: 评测
|
||||
- link "数据" [ref=e36] [cursor=pointer]:
|
||||
- /url: /rankings
|
||||
- img [ref=e37]
|
||||
- generic [ref=e39]: 数据
|
||||
- link "文档" [ref=e40] [cursor=pointer]:
|
||||
- /url: /docs/quickstart
|
||||
- img [ref=e41]
|
||||
- generic [ref=e44]: 文档
|
||||
- link "登录" [ref=e46] [cursor=pointer]:
|
||||
- /url: /login
|
||||
- generic [ref=e47]: 登录
|
||||
- generic [ref=e48]:
|
||||
- generic [ref=e50]:
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]:
|
||||
- paragraph [ref=e53]: // unified model API gateway
|
||||
- heading "让每位 AI 创造者, 少走一步弯路" [level=1] [ref=e55]:
|
||||
- text: 让每位 AI 创造者,
|
||||
- text: 少走一步弯路
|
||||
- paragraph [ref=e56]: 兼容 OpenAI / Claude / Gemini 等协议,覆盖文本、图像、视频、语音,智能路由,统一计费。
|
||||
- generic [ref=e57]:
|
||||
- link "免费开始" [ref=e58] [cursor=pointer]:
|
||||
- /url: /keys
|
||||
- link "查看文档" [ref=e59] [cursor=pointer]:
|
||||
- /url: /docs/quickstart
|
||||
- link "战略合作方 无问芯穹" [ref=e61] [cursor=pointer]:
|
||||
- /url: https://cloud.infini-ai.com
|
||||
- generic [ref=e62]: 战略合作方
|
||||
- img "无问芯穹" [ref=e63]
|
||||
- generic [ref=e65]:
|
||||
- generic [ref=e70]: terminal
|
||||
- button "Copy" [ref=e71] [cursor=pointer]:
|
||||
- img [ref=e72]
|
||||
- code [ref=e76]: "$ curl https://tokendance.space/gateway/v1/chat/completions \\ -H \"Content-Type: application/json\" \\ -H \"Authorization: Bearer YOUR_KEY\" \\ -d '{ \"model\": \"MODEL_ID\", \"messages\": [{ \"role\": \"user\", \"content\": \"Hello World!\" }] }'"
|
||||
- paragraph [ref=e78]: 200 OK · latency 312ms · tokens 42
|
||||
- generic [ref=e81]:
|
||||
- generic [ref=e82]: TOKEN
|
||||
- generic [ref=e83]: DANCE
|
||||
- generic [ref=e85]:
|
||||
- generic [ref=e86]: OpenAI 协议
|
||||
- generic [ref=e87]: Claude 协议
|
||||
- generic [ref=e88]: Gemini 协议
|
||||
- generic [ref=e89]: 智能路由
|
||||
- generic [ref=e90]: 统一计费
|
||||
- generic [ref=e91]: 模型丰富
|
||||
- generic [ref=e92]: 安全可靠
|
||||
- generic [ref=e93]: 开箱即用
|
||||
- generic [ref=e94]: OpenAI 协议
|
||||
- generic [ref=e95]: Claude 协议
|
||||
- generic [ref=e96]: Gemini 协议
|
||||
- generic [ref=e97]: 智能路由
|
||||
- generic [ref=e98]: 统一计费
|
||||
- generic [ref=e99]: 模型丰富
|
||||
- generic [ref=e100]: 安全可靠
|
||||
- generic [ref=e101]: 开箱即用
|
||||
- generic [ref=e102]: OpenAI 协议
|
||||
- generic [ref=e103]: Claude 协议
|
||||
- generic [ref=e104]: Gemini 协议
|
||||
- generic [ref=e105]: 智能路由
|
||||
- generic [ref=e106]: 统一计费
|
||||
- generic [ref=e107]: 模型丰富
|
||||
- generic [ref=e108]: 安全可靠
|
||||
- generic [ref=e109]: 开箱即用
|
||||
- generic [ref=e110]: OpenAI 协议
|
||||
- generic [ref=e111]: Claude 协议
|
||||
- generic [ref=e112]: Gemini 协议
|
||||
- generic [ref=e113]: 智能路由
|
||||
- generic [ref=e114]: 统一计费
|
||||
- generic [ref=e115]: 模型丰富
|
||||
- generic [ref=e116]: 安全可靠
|
||||
- generic [ref=e117]: 开箱即用
|
||||
- generic [ref=e119]:
|
||||
- generic [ref=e120]: Features
|
||||
- heading "为接入 AI 模型的开发者而造。" [level=2] [ref=e121]
|
||||
- generic [ref=e122]:
|
||||
- generic [ref=e123]:
|
||||
- code [ref=e124]: "baseURL: \"tokendance.space\""
|
||||
- heading "多协议兼容" [level=3] [ref=e125]
|
||||
- paragraph [ref=e126]: 原生支持 OpenAI、Claude、Gemini 文本协议,覆盖图像 / 视频 / 文本转语音生成。无需修改代码,切换 Base URL 即可接入。
|
||||
- generic [ref=e127]:
|
||||
- code [ref=e128]: route(model) → provider
|
||||
- heading "智能路由" [level=3] [ref=e129]
|
||||
- paragraph [ref=e130]: 根据模型名称自动路由至对应供应商。一个入口,无需关心底层调度。
|
||||
- generic [ref=e131]:
|
||||
- code [ref=e132]: billing.unified()
|
||||
- heading "统一计费" [level=3] [ref=e133]
|
||||
- paragraph [ref=e134]: 跨供应商统一 Token 消耗统计与账单。告别多平台分别充值的混乱。
|
||||
- generic [ref=e135]:
|
||||
- code [ref=e136]: "fallback: model[] → provider[]"
|
||||
- heading "容错降级" [level=3] [ref=e137]
|
||||
- paragraph [ref=e138]: 同一模型支持多供应商端点自动切换;单次请求可指定多个候选模型,逐级降级,保障服务持续可用。
|
||||
- generic [ref=e139]:
|
||||
- code [ref=e140]: models.list() → 8+
|
||||
- heading "模型丰富" [level=3] [ref=e141]
|
||||
- paragraph [ref=e142]: 接入 MiniMax、通义千问、Kimi、智谱、DeepSeek 等国内头部模型。持续扩展中。
|
||||
- generic [ref=e143]:
|
||||
- code [ref=e144]: import OpenAI from "openai"
|
||||
- heading "开箱即用" [level=3] [ref=e145]
|
||||
- paragraph [ref=e146]: Watcha 一键登录,分钟级接入。兼容现有 SDK,零迁移成本。
|
||||
- generic [ref=e148]:
|
||||
- generic [ref=e149]:
|
||||
- generic [ref=e150]: // how it works
|
||||
- heading "三步接入,分钟级上线" [level=2] [ref=e151]
|
||||
- link "01 注册账号 通过 Watcha 一键登录,即刻开始使用。 →" [ref=e152] [cursor=pointer]:
|
||||
- /url: /login
|
||||
- generic [ref=e153]:
|
||||
- generic [ref=e155]: "01"
|
||||
- generic [ref=e156]:
|
||||
- heading "注册账号" [level=3] [ref=e157]
|
||||
- paragraph [ref=e158]: 通过 Watcha 一键登录,即刻开始使用。
|
||||
- generic [ref=e159]: →
|
||||
- link "02 创建 API Key 在控制台创建密钥,支持多 Key 管理与权限控制。 →" [ref=e160] [cursor=pointer]:
|
||||
- /url: /keys
|
||||
- generic [ref=e161]:
|
||||
- generic [ref=e163]: "02"
|
||||
- generic [ref=e164]:
|
||||
- heading "创建 API Key" [level=3] [ref=e165]
|
||||
- paragraph [ref=e166]: 在控制台创建密钥,支持多 Key 管理与权限控制。
|
||||
- generic [ref=e167]: →
|
||||
- link "03 发起请求 使用你熟悉的 SDK 调用任意模型,完全兼容原生协议。 →" [ref=e168] [cursor=pointer]:
|
||||
- /url: /docs/quickstart
|
||||
- generic [ref=e169]:
|
||||
- generic [ref=e171]: "03"
|
||||
- generic [ref=e172]:
|
||||
- heading "发起请求" [level=3] [ref=e173]
|
||||
- paragraph [ref=e174]: 使用你熟悉的 SDK 调用任意模型,完全兼容原生协议。
|
||||
- generic [ref=e175]: →
|
||||
- generic [ref=e178]:
|
||||
- paragraph [ref=e179]: Quick Start
|
||||
- heading "快速开始" [level=2] [ref=e180]
|
||||
- generic [ref=e181]:
|
||||
- button "OpenAI" [ref=e182] [cursor=pointer]
|
||||
- button "Claude" [ref=e183] [cursor=pointer]
|
||||
- button "Gemini" [ref=e184] [cursor=pointer]
|
||||
- generic [ref=e185]:
|
||||
- generic [ref=e186]:
|
||||
- generic [ref=e187]:
|
||||
- button "cURL" [ref=e188] [cursor=pointer]
|
||||
- button "Python" [ref=e189] [cursor=pointer]
|
||||
- button "Node.js" [ref=e190] [cursor=pointer]
|
||||
- button "Copy" [ref=e191] [cursor=pointer]:
|
||||
- img [ref=e192]
|
||||
- code [ref=e197]:
|
||||
- generic [ref=e198]: curl https://tokendance.space/gateway/v1/chat/completions \
|
||||
- generic [ref=e199]: "-H \"Authorization: Bearer YOUR_API_KEY\" \\"
|
||||
- generic [ref=e200]: "-H \"Content-Type: application/json\" \\"
|
||||
- generic [ref=e201]: "-d '{"
|
||||
- generic [ref=e202]: "\"model\": \"MODEL_ID\","
|
||||
- generic [ref=e203]: "\"messages\": [{\"role\": \"user\", \"content\": \"Hello!\"}]"
|
||||
- generic [ref=e204]: "}'"
|
||||
- link "Documentation 阅读完整文档,了解更多用法 →" [ref=e205] [cursor=pointer]:
|
||||
- /url: /docs/quickstart
|
||||
- generic [ref=e206]:
|
||||
- paragraph [ref=e207]: Documentation
|
||||
- paragraph [ref=e208]: 阅读完整文档,了解更多用法
|
||||
- generic [ref=e209]: →
|
||||
- generic [ref=e211]:
|
||||
- generic [ref=e212]: // pricing
|
||||
- heading "获取 Token 额度" [level=2] [ref=e213]
|
||||
- generic [ref=e214]:
|
||||
- generic [ref=e215]:
|
||||
- generic [ref=e216]: 6/16 截止
|
||||
- generic [ref=e217]: 注册即送 10 元额度
|
||||
- generic [ref=e218]: 新用户注册即赠 10 元 Token,7 天有效期
|
||||
- link "百亿 Token 补贴计划 AI 时代,Token 正在成为硬通货。当 Agent 开始吞噬软件,一个人也可以像一支队伍。我们在内测期间开启百亿 Token 补贴计划,赋能 AI 时代的超级个体 —— 无论你正在验证场景、打磨产品,还是准备扩大规模,都欢迎申请。 查看详情 →" [ref=e219] [cursor=pointer]:
|
||||
- /url: https://mp.weixin.qq.com/s/D_ZmohSbk1RiR1W1W4EWDQ
|
||||
- heading "百亿 Token 补贴计划" [level=3] [ref=e220]
|
||||
- paragraph [ref=e221]: AI 时代,Token 正在成为硬通货。当 Agent 开始吞噬软件,一个人也可以像一支队伍。我们在内测期间开启百亿 Token 补贴计划,赋能 AI 时代的超级个体 —— 无论你正在验证场景、打磨产品,还是准备扩大规模,都欢迎申请。
|
||||
- generic [ref=e222]: 查看详情 →
|
||||
- generic [ref=e223]:
|
||||
- generic [ref=e224]:
|
||||
- generic [ref=e226]: 限量 500 张
|
||||
- heading "浦发观猹联名卡" [level=3] [ref=e227]
|
||||
- generic [ref=e228]: 「浦耳猹」
|
||||
- paragraph [ref=e229]: 前 200 位办理用户,赠送近千万 Token(折合 RMB 100 元)
|
||||
- generic [ref=e230]:
|
||||
- link "立即申请 →" [ref=e231] [cursor=pointer]:
|
||||
- /url: https://mp.weixin.qq.com/s/kWmrIU4IYxuCIDzLFQAvXg
|
||||
- link "Token 申请问卷 →" [ref=e232] [cursor=pointer]:
|
||||
- /url: https://agentuniverse.feishu.cn/share/base/form/shrcnvZvZq6hK2xgBvVGIuKpR5e
|
||||
- generic [ref=e233]:
|
||||
- heading "观猹开发者计划" [level=3] [ref=e234]
|
||||
- paragraph [ref=e235]: 加入开发者计划,赠送超值 Token 额度。获取技术支持与优先体验新模型的机会。
|
||||
- link "了解详情 →" [ref=e236] [cursor=pointer]:
|
||||
- /url: https://agentuniverse.feishu.cn/wiki/J2FPwJp7zi6D6wklFbrcPbVNnNe
|
||||
- paragraph [ref=e241]: 免费注册,分钟级接入。兼容你现有的 SDK 和工作流。
|
||||
- contentinfo [ref=e242]:
|
||||
- generic [ref=e243]:
|
||||
- generic [ref=e244]:
|
||||
- generic [ref=e245]:
|
||||
- link "文档" [ref=e246] [cursor=pointer]:
|
||||
- /url: /docs/quickstart
|
||||
- link "模型" [ref=e247] [cursor=pointer]:
|
||||
- /url: /models
|
||||
- link "控制台" [ref=e248] [cursor=pointer]:
|
||||
- /url: /keys
|
||||
- link "战略合作方 无问芯穹" [ref=e249] [cursor=pointer]:
|
||||
- /url: https://cloud.infini-ai.com
|
||||
- generic [ref=e250]: 战略合作方
|
||||
- img "无问芯穹" [ref=e251]
|
||||
- generic [ref=e252]:
|
||||
- generic [ref=e253]: © 2026 TokenDance
|
||||
- generic [ref=e254]:
|
||||
- link "浙ICP备2024107375号-10" [ref=e255] [cursor=pointer]:
|
||||
- /url: https://beian.miit.gov.cn/
|
||||
- link "浙公网安备33019202002952号" [ref=e256] [cursor=pointer]:
|
||||
- /url: https://beian.mps.gov.cn/#/query/webSearch?code=33019202002952
|
||||
- region "Notifications (F8)":
|
||||
- list
|
||||
|
|
@ -1,22 +1,18 @@
|
|||
<!doctype html>
|
||||
<!doctype html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<!-- TokenDance Design System: Google Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;0,900;1,400;1,700&family=Source+Serif+4:ital,wght@0,400;0,600;0,700;1,400&family=Noto+Serif+SC:wght@400;600;700;900&family=JetBrains+Mono:wght@400;500;700&family=Bebas+Neue&display=swap" rel="stylesheet" />
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta
|
||||
name="description"
|
||||
lang="zh"
|
||||
content="统一的 AI 模型聚合与分发网关,支持将各类大语言模型跨格式转换为 OpenAI、Claude、Gemini 兼容接口,为个人与企业提供集中式模型管理与网关服务。"
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
lang="en"
|
||||
content="A unified AI model hub for aggregation & distribution. It supports cross-converting various LLMs into OpenAI-compatible, Claude-compatible, or Gemini-compatible formats. A centralized gateway for personal and enterprise model management."
|
||||
/>
|
||||
<meta name="description" content="Unified AI model gateway supporting OpenAI, Claude, Gemini and more." />
|
||||
<meta name="generator" content="toekn-factory" />
|
||||
<!-- 标题由 /api/status 写入 localStorage 后由前端同步;不在此处读 localStorage,以免旧缓存(如改名前默认值)抢先显示 -->
|
||||
<!-- 鏍囬鐢?/api/status 鍐欏叆 localStorage 鍚庣敱鍓嶇鍚屾锛涗笉鍦ㄦ澶勮 localStorage锛屼互鍏嶆棫缂撳瓨锛堝鏀瑰悕鍓嶉粯璁ゅ€硷級鎶㈠厛鏄剧ず -->
|
||||
<title></title>
|
||||
<!--umami-->
|
||||
<!--Google Analytics-->
|
||||
|
|
@ -28,3 +24,8 @@
|
|||
<script type="module" src="/src/index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"type": "module",
|
||||
"dependencies": {
|
||||
"@douyinfe/semi-icons": "^2.63.1",
|
||||
"@douyinfe/semi-illustrations": "^2.100.0",
|
||||
"@douyinfe/semi-ui": "^2.69.1",
|
||||
"@lobehub/icons": "^2.0.0",
|
||||
"@visactor/react-vchart": "~1.8.8",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,4 @@
|
|||
allowBuilds:
|
||||
'@parcel/watcher': set this to true or false
|
||||
'@swc/core': set this to true or false
|
||||
esbuild: set this to true or false
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -37,6 +37,7 @@ import Redemption from './pages/Redemption';
|
|||
import TopUp from './pages/TopUp';
|
||||
import Log from './pages/Log';
|
||||
import Chat from './pages/Chat';
|
||||
import Benchmarks from './pages/Benchmarks';
|
||||
import Chat2Link from './pages/Chat2Link';
|
||||
import Midjourney from './pages/Midjourney';
|
||||
import Pricing from './pages/Pricing';
|
||||
|
|
@ -499,6 +500,7 @@ function App() {
|
|||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route path='/benchmarks' element={<Benchmarks />} />
|
||||
<Route path='*' element={<NotFound />} />
|
||||
</Routes>
|
||||
</SetupCheck>
|
||||
|
|
@ -506,3 +508,4 @@ function App() {
|
|||
}
|
||||
|
||||
export default App;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const variants = {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
default: 'h-10 px-4 py-2',
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-10 w-10',
|
||||
};
|
||||
|
||||
const Button = React.forwardRef(
|
||||
({ className, variant = 'default', size = 'default', children, ...props }, ref) => {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||
'disabled:pointer-events-none disabled:opacity-50',
|
||||
variants[variant],
|
||||
sizes[size],
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Button.displayName = 'Button';
|
||||
export default Button;
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const Card = React.forwardRef(({ className, children, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
'rounded-lg border border-border bg-card text-card-foreground shadow-sm',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
));
|
||||
Card.displayName = 'Card';
|
||||
|
||||
const CardHeader = React.forwardRef(({ className, children, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx('flex flex-col space-y-1.5 p-6', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
));
|
||||
CardHeader.displayName = 'CardHeader';
|
||||
|
||||
const CardTitle = React.forwardRef(({ className, children, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
'text-2xl font-semibold leading-none tracking-tight',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</h3>
|
||||
));
|
||||
CardTitle.displayName = 'CardTitle';
|
||||
|
||||
const CardDescription = React.forwardRef(
|
||||
({ className, children, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={clsx('text-sm text-muted-foreground', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
)
|
||||
);
|
||||
CardDescription.displayName = 'CardDescription';
|
||||
|
||||
const CardContent = React.forwardRef(
|
||||
({ className, children, ...props }, ref) => (
|
||||
<div ref={ref} className={clsx('p-6 pt-0', className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
CardContent.displayName = 'CardContent';
|
||||
|
||||
const CardFooter = React.forwardRef(
|
||||
({ className, children, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx('flex items-center p-6 pt-0', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
CardFooter.displayName = 'CardFooter';
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
||||
export default Card;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
// shadcn/ui style base components
|
||||
// These are minimal wrappers around raw elements using CSS variable tokens
|
||||
|
||||
export { default as Button } from './Button';
|
||||
export { default as Card } from './Card';
|
||||
|
|
@ -322,6 +322,7 @@ const PageLayout = () => {
|
|||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{location.pathname !== '/' && (
|
||||
<Header
|
||||
style={{
|
||||
padding: 0,
|
||||
|
|
@ -338,6 +339,7 @@ const PageLayout = () => {
|
|||
drawerOpen={drawerOpen}
|
||||
/>
|
||||
</Header>
|
||||
)}
|
||||
<Layout
|
||||
style={{
|
||||
overflow: isMobile ? 'visible' : 'auto',
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ const routerMap = {
|
|||
deployment: '/console/deployment',
|
||||
'model-heat': '/console/model-heat',
|
||||
playground: '/console/playground',
|
||||
benchmarks: '/benchmarks',
|
||||
personal: '/console/personal',
|
||||
supplier: null,
|
||||
distributor: '/console/distributor/admin',
|
||||
|
|
@ -84,6 +85,11 @@ const SiderBar = ({ onNavigate = () => {} }) => {
|
|||
|
||||
const workspaceItems = useMemo(() => {
|
||||
const items = [
|
||||
{
|
||||
text: t('评测'),
|
||||
itemKey: 'benchmarks',
|
||||
to: '/benchmarks',
|
||||
},
|
||||
{
|
||||
text: t('数据看板'),
|
||||
itemKey: 'detail',
|
||||
|
|
@ -94,7 +100,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
|
|||
: 'tableHiddle',
|
||||
},
|
||||
{
|
||||
text: t('令牌管理'),
|
||||
text: t('API Key'),
|
||||
itemKey: 'token',
|
||||
to: '/token',
|
||||
},
|
||||
|
|
@ -303,7 +309,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
|
|||
const chatMenuItems = useMemo(() => {
|
||||
const items = [
|
||||
{
|
||||
text: t('操练场'),
|
||||
text: t('体验馆'),
|
||||
itemKey: 'playground',
|
||||
to: '/playground',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -18,7 +18,6 @@ For commercial licensing, please contact support@quantumnous.com
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import NewYearButton from './NewYearButton';
|
||||
import NotificationButton from './NotificationButton';
|
||||
import ThemeToggle from './ThemeToggle';
|
||||
import LanguageSelector from './LanguageSelector';
|
||||
|
|
@ -43,8 +42,7 @@ const ActionButtons = ({
|
|||
const shouldShowNoticeButton = Boolean(userState?.user?.id);
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-2 md:gap-3'>
|
||||
{/* <NewYearButton isNewYear={isNewYear} /> */}
|
||||
<div className="flex items-center gap-1 md:gap-1.5">
|
||||
{shouldShowNoticeButton && (
|
||||
<NotificationButton
|
||||
unreadCount={unreadCount}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -17,23 +17,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { Typography, Tag } from '@douyinfe/semi-ui';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Tag } from '@douyinfe/semi-ui';
|
||||
import SkeletonWrapper from '../components/SkeletonWrapper';
|
||||
import { userIsSupplierUser, showInfo, isAdmin } from '../../../helpers';
|
||||
import { StatusContext } from '../../../context/Status';
|
||||
|
||||
/**
|
||||
* 顶栏申请入口:浅色用 #409EFF 系。
|
||||
* 注意:tailwind.config 覆盖了默认 colors,无 blue/zinc 等键,暗色必须用任意值或 semi-color-*,否则 dark: 不会产出 CSS。
|
||||
*/
|
||||
const APPLY_BTN_BASE =
|
||||
'flex-shrink-0 inline-flex items-center justify-center text-sm font-semibold transition-all duration-200 ease-in-out rounded-lg px-3.5 py-2 min-h-[2.25rem] border shadow-sm dark:shadow-none';
|
||||
const APPLY_BTN_IDLE =
|
||||
'border-[#b3d8ff] bg-[#ecf5ff] text-[#409EFF] hover:bg-[#d9ecff] hover:border-[#409EFF] active:bg-[#c6e2ff] dark:border-[rgba(96,165,250,0.35)] dark:bg-[rgba(59,130,246,0.1)] dark:text-[rgba(147,197,253,0.92)] dark:hover:bg-[rgba(59,130,246,0.15)] dark:hover:border-[rgba(96,165,250,0.48)] dark:active:bg-[rgba(59,130,246,0.12)]';
|
||||
const APPLY_BTN_ACTIVE =
|
||||
'border-[#409EFF] bg-[#d9ecff] text-[#337ecc] shadow-md dark:border-[rgba(96,165,250,0.55)] dark:bg-[rgba(59,130,246,0.18)] dark:text-[rgba(191,219,254,0.95)] dark:shadow-none dark:ring-1 dark:ring-[rgba(96,165,250,0.28)]';
|
||||
|
||||
const HeaderLogo = ({
|
||||
isMobile,
|
||||
|
|
@ -44,93 +31,43 @@ const HeaderLogo = ({
|
|||
systemName,
|
||||
isSelfUseMode,
|
||||
isDemoSiteMode,
|
||||
userState,
|
||||
t,
|
||||
}) => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const user = userState?.user;
|
||||
const [statusState] = useContext(StatusContext);
|
||||
|
||||
const isSupplierApplyEnabled = React.useMemo(() => {
|
||||
try {
|
||||
const roleModulesRaw = statusState?.status?.SidebarModulesByRole;
|
||||
if (!roleModulesRaw) return true;
|
||||
const roleConfig = JSON.parse(roleModulesRaw);
|
||||
const userRole = String(user?.role ?? 0);
|
||||
const config = roleConfig[userRole];
|
||||
if (!config) return true;
|
||||
const consoleSection = config.console;
|
||||
if (!consoleSection) return true;
|
||||
if (consoleSection.enabled === false) return false;
|
||||
return consoleSection.supplierApply !== false;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}, [statusState?.status?.SidebarModulesByRole, user?.role]);
|
||||
|
||||
/** 未登录且已在登录页时:提示先登录,并同步 redirect 到目标申请页 */
|
||||
const handleApplyEntryClick = useCallback(
|
||||
(applyPath) => (e) => {
|
||||
if (user) return;
|
||||
if (location.pathname !== '/login') return;
|
||||
e.preventDefault();
|
||||
showInfo(t('请先登录'));
|
||||
navigate(`/login?redirect=${encodeURIComponent(applyPath)}`, {
|
||||
replace: true,
|
||||
});
|
||||
},
|
||||
[user, location.pathname, navigate, t],
|
||||
);
|
||||
|
||||
if (isMobile && isConsoleRoute) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const supplierApplyPath = '/console/supplier/apply';
|
||||
|
||||
/** 管理员不展示;已是供应商则隐藏「提供算力」 */
|
||||
const hideApplyLinks = isAdmin();
|
||||
const showSupplierApply = !hideApplyLinks && !userIsSupplierUser(user) && isSupplierApplyEnabled;
|
||||
|
||||
const supplierTo = user
|
||||
? supplierApplyPath
|
||||
: `/login?redirect=${encodeURIComponent(supplierApplyPath)}`;
|
||||
|
||||
const supplierActive = location.pathname.startsWith(supplierApplyPath);
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-2 md:gap-3 flex-shrink-0'>
|
||||
<Link to='/' className='group flex items-center gap-2'>
|
||||
<div className='relative w-8 h-8 md:w-8 md:h-8'>
|
||||
<SkeletonWrapper loading={isLoading || !logoLoaded} type='image' />
|
||||
<div className="flex items-center gap-2 md:gap-3 flex-shrink-0">
|
||||
<Link to="/" className="group flex items-center gap-2">
|
||||
<div className="relative w-8 h-8 md:w-8 md:h-8">
|
||||
<SkeletonWrapper loading={isLoading || !logoLoaded} type="image" />
|
||||
<img
|
||||
src={logo}
|
||||
alt='logo'
|
||||
className={`absolute inset-0 w-full h-full transition-all duration-200 group-hover:scale-110 rounded-full ${!isLoading && logoLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
alt="logo"
|
||||
className={`absolute inset-0 w-full h-full transition-all duration-200 group-hover:scale-110 rounded-full ${
|
||||
!isLoading && logoLoaded ? "opacity-100" : "opacity-0"
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<div className='hidden md:flex items-center gap-2'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className="hidden md:flex items-center gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<SkeletonWrapper
|
||||
loading={isLoading}
|
||||
type='title'
|
||||
type="title"
|
||||
width={120}
|
||||
height={24}
|
||||
>
|
||||
<Typography.Title
|
||||
heading={4}
|
||||
className='!text-lg !font-semibold !mb-0'
|
||||
>
|
||||
<span className="text-lg font-semibold text-foreground">
|
||||
{systemName}
|
||||
</Typography.Title>
|
||||
</span>
|
||||
</SkeletonWrapper>
|
||||
{(isSelfUseMode || isDemoSiteMode) && !isLoading && (
|
||||
<Tag
|
||||
color={isSelfUseMode ? 'purple' : 'blue'}
|
||||
className='text-xs px-1.5 py-0.5 rounded whitespace-nowrap shadow-sm'
|
||||
size='small'
|
||||
shape='circle'
|
||||
color={isSelfUseMode ? "purple" : "blue"}
|
||||
size="small"
|
||||
shape="circle"
|
||||
className="text-xs px-1.5 py-0.5 rounded whitespace-nowrap shadow-sm"
|
||||
>
|
||||
{isSelfUseMode ? t('自用模式') : t('演示站点')}
|
||||
</Tag>
|
||||
|
|
@ -138,17 +75,6 @@ const HeaderLogo = ({
|
|||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
{showSupplierApply && (
|
||||
<div className='hidden sm:flex items-center gap-2 flex-shrink-0'>
|
||||
<Link
|
||||
to={supplierTo}
|
||||
onClick={handleApplyEntryClick(supplierApplyPath)}
|
||||
className={`${APPLY_BTN_BASE} ${supplierActive ? APPLY_BTN_ACTIVE : APPLY_BTN_IDLE}`}
|
||||
>
|
||||
{t('提供算力')}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -17,60 +17,76 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, Dropdown } from '@douyinfe/semi-ui';
|
||||
import { Languages } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { Languages } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
normalizeLanguage,
|
||||
supportedLanguages,
|
||||
LANGUAGE_NATIVE_LABELS,
|
||||
} from '../../../i18n/language';
|
||||
} from "../../../i18n/language";
|
||||
|
||||
/** 语言在头部与下拉中一律显示其自称(如 English、简体中文),不随界面语言翻译 */
|
||||
const nativeLabel = (code) => LANGUAGE_NATIVE_LABELS[code] || code;
|
||||
|
||||
const itemClass = (active) =>
|
||||
`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${
|
||||
active
|
||||
? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold'
|
||||
: 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'
|
||||
}`;
|
||||
|
||||
const LanguageSelector = ({ currentLang, onLanguageChange }) => {
|
||||
const { t } = useTranslation();
|
||||
const normalized = normalizeLanguage(currentLang) || 'zh-CN';
|
||||
const currentLabel = nativeLabel(normalized);
|
||||
const [open, setOpen] = useState(false);
|
||||
const ref = useRef(null);
|
||||
const normalized = normalizeLanguage(currentLang) || "zh-CN";
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const handler = (e) => {
|
||||
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
||||
};
|
||||
document.addEventListener("mousedown", handler);
|
||||
return () => document.removeEventListener("mousedown", handler);
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
position='bottomRight'
|
||||
render={
|
||||
<Dropdown.Menu className='!bg-semi-color-bg-overlay !border-semi-color-border !shadow-lg !rounded-lg dark:!bg-gray-700 dark:!border-gray-600'>
|
||||
{supportedLanguages.map((code) => (
|
||||
<Dropdown.Item
|
||||
<div className="relative" ref={ref}>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`${t("切换语言")}: ${nativeLabel(normalized)}`}
|
||||
onClick={() => setOpen(!open)}
|
||||
className="inline-flex items-center gap-1 rounded-md px-2 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
<Languages size={16} />
|
||||
<span className="hidden sm:inline truncate max-w-[6rem]">
|
||||
{nativeLabel(normalized)}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div className="absolute right-0 top-full mt-1 z-50 min-w-[8rem] rounded-lg border border-border bg-popover p-1 shadow-md animate-fade-in">
|
||||
{supportedLanguages.map((code) => {
|
||||
const active = normalized === code;
|
||||
return (
|
||||
<button
|
||||
key={code}
|
||||
onClick={() => onLanguageChange(code)}
|
||||
className={itemClass(normalized === code)}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onLanguageChange(code);
|
||||
setOpen(false);
|
||||
}}
|
||||
className={`flex w-full items-center rounded-md px-2.5 py-1.5 text-sm transition-colors ${
|
||||
active
|
||||
? "bg-accent text-accent-foreground font-medium"
|
||||
: "text-popover-foreground hover:bg-accent hover:text-accent-foreground"
|
||||
}`}
|
||||
>
|
||||
{nativeLabel(code)}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
</Dropdown.Menu>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
icon={<Languages size={18} />}
|
||||
aria-label={`${t('common.changeLanguage')}: ${currentLabel}`}
|
||||
theme='borderless'
|
||||
type='tertiary'
|
||||
className='!px-2 !py-1.5 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700 !rounded-full !bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 hover:!bg-semi-color-fill-1 dark:hover:!bg-semi-color-fill-2 !max-w-[11rem] sm:!max-w-[14rem]'
|
||||
>
|
||||
<span className='truncate text-sm font-medium min-w-0'>
|
||||
{currentLabel}
|
||||
{active && (
|
||||
<span className="ml-auto flex h-3.5 w-3.5 items-center justify-center">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-primary"></span>
|
||||
</span>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -17,9 +17,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from '@douyinfe/semi-ui';
|
||||
import { IconClose, IconMenu } from '@douyinfe/semi-icons';
|
||||
import React from "react";
|
||||
import { X, Menu } from "lucide-react";
|
||||
|
||||
const MobileMenuButton = ({
|
||||
isConsoleRoute,
|
||||
|
|
@ -33,23 +32,17 @@ const MobileMenuButton = ({
|
|||
return null;
|
||||
}
|
||||
|
||||
const isOpen = isMobile ? drawerOpen : collapsed;
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon={
|
||||
(isMobile ? drawerOpen : collapsed) ? (
|
||||
<IconClose className='text-lg' />
|
||||
) : (
|
||||
<IconMenu className='text-lg' />
|
||||
)
|
||||
}
|
||||
aria-label={
|
||||
(isMobile ? drawerOpen : collapsed) ? t('关闭侧边栏') : t('打开侧边栏')
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
aria-label={isOpen ? t("关闭侧边栏") : t("打开侧边栏")}
|
||||
onClick={onToggle}
|
||||
theme='borderless'
|
||||
type='tertiary'
|
||||
className='!p-2 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700'
|
||||
/>
|
||||
className="inline-flex items-center justify-center p-2 rounded-md text-foreground hover:bg-accent hover:text-accent-foreground transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
{isOpen ? <X size={20} /> : <Menu size={20} />}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -20,19 +20,22 @@ For commercial licensing, please contact support@quantumnous.com
|
|||
import React, { useMemo, useCallback } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { Button, Dropdown } from '@douyinfe/semi-ui';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { ChevronDown, Box, LayoutDashboard, Trophy, BookOpen } from 'lucide-react';
|
||||
|
||||
const NAV_ICONS = {
|
||||
pricing: Box,
|
||||
console: LayoutDashboard,
|
||||
benchmark: Trophy,
|
||||
docs: BookOpen,
|
||||
};
|
||||
import SkeletonWrapper from '../components/SkeletonWrapper';
|
||||
import { StatusContext } from '../../../context/Status';
|
||||
import { isAdmin, userIsSupplierUser } from '../../../helpers';
|
||||
|
||||
/** 主站入口顺序(与桌面顶栏一致) */
|
||||
const PRIMARY_NAV_KEYS = ['home', 'pricing', 'docs', 'about'];
|
||||
const PRIMARY_NAV_KEYS = ['pricing', 'console', 'benchmark', 'docs'];
|
||||
|
||||
const menuClass =
|
||||
'!bg-semi-color-bg-overlay !border-semi-color-border !shadow-lg !rounded-lg dark:!bg-gray-700 dark:!border-gray-600';
|
||||
|
||||
const supplierApplyPath = '/console/supplier/apply';
|
||||
|
||||
const MobileSiteNavDropdown = ({
|
||||
mainNavLinks,
|
||||
pricingRequireAuth,
|
||||
|
|
@ -43,7 +46,6 @@ const MobileSiteNavDropdown = ({
|
|||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const user = userState?.user;
|
||||
const [statusState] = React.useContext(StatusContext);
|
||||
|
||||
const byNavKey = useMemo(
|
||||
() => Object.fromEntries(mainNavLinks.map((l) => [l.itemKey, l])),
|
||||
|
|
@ -51,53 +53,16 @@ const MobileSiteNavDropdown = ({
|
|||
);
|
||||
|
||||
const primaryAndConsoleLinks = useMemo(() => {
|
||||
const primary = PRIMARY_NAV_KEYS.map((k) => byNavKey[k]).filter(Boolean);
|
||||
const consoleLink = byNavKey.console;
|
||||
if (consoleLink) {
|
||||
return [...primary, consoleLink];
|
||||
}
|
||||
return primary;
|
||||
return PRIMARY_NAV_KEYS.map((k) => byNavKey[k]).filter(Boolean);
|
||||
}, [byNavKey]);
|
||||
|
||||
const applyEntries = useMemo(() => {
|
||||
const hideApplyLinks = isAdmin();
|
||||
if (!hideApplyLinks && !userIsSupplierUser(user)) {
|
||||
try {
|
||||
const roleModulesRaw = statusState?.status?.SidebarModulesByRole;
|
||||
if (roleModulesRaw) {
|
||||
const roleConfig = JSON.parse(roleModulesRaw);
|
||||
const userRole = String(user?.role ?? 0);
|
||||
const config = roleConfig[userRole];
|
||||
if (config) {
|
||||
const consoleSection = config.console;
|
||||
if (consoleSection) {
|
||||
if (consoleSection.enabled === false || consoleSection.supplierApply === false) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
return [
|
||||
{
|
||||
itemKey: 'supplier-apply',
|
||||
text: t('提供算力'),
|
||||
redirectPath: supplierApplyPath,
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}, [user, t, statusState?.status?.SidebarModulesByRole]);
|
||||
|
||||
const currentPageLabel = useMemo(() => {
|
||||
const p = location.pathname;
|
||||
if (p === '/') return t('首页');
|
||||
if (p === '/pricing' || p.startsWith('/pricing/')) return t('模型广场');
|
||||
if (p === '/about' || p.startsWith('/about/')) return t('关于');
|
||||
if (p === '/pricing' || p.startsWith('/pricing/')) return t('模型');
|
||||
if (p.startsWith('/benchmarks')) return t('评测');
|
||||
if (/\/[a-z]{2}\/docs\b/i.test(p) || p.includes('/docs')) {
|
||||
return t('文档');
|
||||
}
|
||||
if (p.startsWith(supplierApplyPath)) return t('提供算力');
|
||||
if (p.startsWith('/console')) return t('控制台');
|
||||
if (p === '/login') return t('登录');
|
||||
return t('页面导航');
|
||||
|
|
@ -148,20 +113,7 @@ const MobileSiteNavDropdown = ({
|
|||
[navigate, resolveInternalTarget],
|
||||
);
|
||||
|
||||
const handleSelectApply = useCallback(
|
||||
(entry) => {
|
||||
window.setTimeout(() => {
|
||||
if (user) {
|
||||
navigate(entry.redirectPath);
|
||||
return;
|
||||
}
|
||||
navigate(`/login?redirect=${encodeURIComponent(entry.redirectPath)}`);
|
||||
}, 10);
|
||||
},
|
||||
[navigate, user],
|
||||
);
|
||||
|
||||
if (primaryAndConsoleLinks.length === 0 && applyEntries.length === 0) {
|
||||
if (primaryAndConsoleLinks.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -199,28 +151,14 @@ const MobileSiteNavDropdown = ({
|
|||
onClick={() => handleSelectMain(link)}
|
||||
className={itemClass(active)}
|
||||
>
|
||||
<span className="inline-flex items-center gap-2">
|
||||
{NAV_ICONS[link.itemKey] && React.createElement(NAV_ICONS[link.itemKey], { size: 16 })}
|
||||
{link.text}
|
||||
</span>
|
||||
</Dropdown.Item>
|
||||
);
|
||||
})}
|
||||
{applyEntries.length > 0 && primaryAndConsoleLinks.length > 0 && (
|
||||
<Dropdown.Divider />
|
||||
)}
|
||||
{applyEntries.map((entry) => {
|
||||
const active = isActivePath(entry.redirectPath);
|
||||
return (
|
||||
<Dropdown.Item
|
||||
key={entry.itemKey}
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onClick={() => handleSelectApply(entry)}
|
||||
className={itemClass(active)}
|
||||
>
|
||||
{entry.text}
|
||||
</Dropdown.Item>
|
||||
);
|
||||
})}
|
||||
|
||||
</Dropdown.Menu>
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -19,8 +19,16 @@ For commercial licensing, please contact support@quantumnous.com
|
|||
|
||||
import React from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { Box, LayoutDashboard, Trophy, BookOpen } from 'lucide-react';
|
||||
import SkeletonWrapper from '../components/SkeletonWrapper';
|
||||
|
||||
const NAV_ICONS = {
|
||||
pricing: Box,
|
||||
console: LayoutDashboard,
|
||||
benchmark: Trophy,
|
||||
docs: BookOpen,
|
||||
};
|
||||
|
||||
const Navigation = ({
|
||||
mainNavLinks,
|
||||
isMobile,
|
||||
|
|
@ -33,21 +41,26 @@ const Navigation = ({
|
|||
const isActive = (linkPath) => {
|
||||
if (!linkPath) return false;
|
||||
const currentPath = location.pathname;
|
||||
if (linkPath === '/') {
|
||||
return currentPath === '/';
|
||||
if (linkPath === "/") {
|
||||
return currentPath === "/";
|
||||
}
|
||||
return currentPath.startsWith(linkPath);
|
||||
};
|
||||
|
||||
const renderNavLinks = () => {
|
||||
const baseClasses =
|
||||
'flex-shrink-0 flex items-center text-sm font-medium transition-all duration-200 ease-in-out rounded-md';
|
||||
const spacingClasses = isMobile ? 'px-2 py-1' : 'px-3 py-2';
|
||||
"flex-shrink-0 inline-flex items-center gap-1.5 text-[14px] transition-colors duration-150 rounded-md px-3 py-1.5";
|
||||
const hoverClasses =
|
||||
'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-700/50 hover:!text-semi-color-text-0 dark:hover:!text-white';
|
||||
"hover:bg-accent hover:text-accent-foreground";
|
||||
|
||||
return mainNavLinks.map((link) => {
|
||||
const linkContent = <span>{link.text}</span>;
|
||||
const Icon = NAV_ICONS[link.itemKey];
|
||||
const linkContent = (
|
||||
<>
|
||||
{Icon && <Icon size={16} className="shrink-0" />}
|
||||
<span>{link.text}</span>
|
||||
</>
|
||||
);
|
||||
|
||||
if (link.isExternal) {
|
||||
const openInNewTab = link.openInNewTab !== false;
|
||||
|
|
@ -56,9 +69,9 @@ const Navigation = ({
|
|||
key={link.itemKey}
|
||||
href={link.externalLink}
|
||||
{...(openInNewTab
|
||||
? { target: '_blank', rel: 'noopener noreferrer' }
|
||||
? { target: "_blank", rel: "noopener noreferrer" }
|
||||
: {})}
|
||||
className={`${baseClasses} ${spacingClasses} ${hoverClasses} !text-semi-color-text-1 dark:!text-gray-300`}
|
||||
className={`${baseClasses} ${hoverClasses} text-muted-foreground`}
|
||||
>
|
||||
{linkContent}
|
||||
</a>
|
||||
|
|
@ -66,23 +79,23 @@ const Navigation = ({
|
|||
}
|
||||
|
||||
let targetPath = link.to;
|
||||
if (link.itemKey === 'console' && !userState.user) {
|
||||
targetPath = '/login';
|
||||
if (link.itemKey === "console" && !userState.user) {
|
||||
targetPath = "/login";
|
||||
}
|
||||
if (link.itemKey === 'pricing' && pricingRequireAuth && !userState.user) {
|
||||
targetPath = '/login';
|
||||
if (link.itemKey === "pricing" && pricingRequireAuth && !userState.user) {
|
||||
targetPath = "/login";
|
||||
}
|
||||
|
||||
const active = isActive(link.to);
|
||||
const activeClasses = active
|
||||
? '!bg-semi-color-fill-2 dark:!bg-gray-700 !text-semi-color-text-0 dark:!text-white'
|
||||
: '!text-semi-color-text-1 dark:!text-gray-300';
|
||||
? "bg-accent text-accent-foreground font-semibold"
|
||||
: "text-muted-foreground";
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={link.itemKey}
|
||||
to={targetPath}
|
||||
className={`${baseClasses} ${spacingClasses} ${hoverClasses} ${activeClasses}`}
|
||||
className={`${baseClasses} ${hoverClasses} ${activeClasses}`}
|
||||
>
|
||||
{linkContent}
|
||||
</Link>
|
||||
|
|
@ -91,10 +104,10 @@ const Navigation = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<nav className='hidden md:flex items-center gap-1 overflow-x-auto whitespace-nowrap scrollbar-hide'>
|
||||
<nav className="hidden md:flex items-center gap-1 overflow-x-auto whitespace-nowrap scrollbar-hide">
|
||||
<SkeletonWrapper
|
||||
loading={isLoading}
|
||||
type='navigation'
|
||||
type="navigation"
|
||||
count={4}
|
||||
width={60}
|
||||
height={16}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -17,31 +17,25 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, Badge } from '@douyinfe/semi-ui';
|
||||
import { Bell } from 'lucide-react';
|
||||
import React from "react";
|
||||
import { Bell } from "lucide-react";
|
||||
|
||||
// NotificationButton 展示站内消息铃铛与未读角标。
|
||||
const NotificationButton = ({ unreadCount, onNoticeOpen, t }) => {
|
||||
const buttonProps = {
|
||||
icon: <Bell size={18} />,
|
||||
'aria-label': t('站内消息'),
|
||||
onClick: onNoticeOpen,
|
||||
theme: 'borderless',
|
||||
type: 'tertiary',
|
||||
className:
|
||||
'!p-1.5 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700 !rounded-full !bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 hover:!bg-semi-color-fill-1 dark:hover:!bg-semi-color-fill-2',
|
||||
};
|
||||
|
||||
if (unreadCount > 0) {
|
||||
return (
|
||||
<Badge count={unreadCount} type='danger' overflowCount={99}>
|
||||
<Button {...buttonProps} />
|
||||
</Badge>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t("站内消息")}
|
||||
onClick={onNoticeOpen}
|
||||
className="relative inline-flex items-center justify-center p-2 rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
<Bell size={18} />
|
||||
{unreadCount > 0 && (
|
||||
<span className="absolute -top-0.5 -right-0.5 flex h-4 min-w-[1rem] items-center justify-center rounded-full bg-destructive px-1 text-[10px] font-bold text-destructive-foreground ring-2 ring-background">
|
||||
{unreadCount > 99 ? "99+" : unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return <Button {...buttonProps} />;
|
||||
};
|
||||
|
||||
export default NotificationButton;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -17,101 +17,98 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { Dropdown } from '@douyinfe/semi-ui';
|
||||
import { Sun, Moon, Monitor } from 'lucide-react';
|
||||
import { useActualTheme } from '../../../context/Theme';
|
||||
import React, { useMemo, useState, useRef, useEffect } from "react";
|
||||
import { Sun, Moon, Monitor } from "lucide-react";
|
||||
import { useActualTheme } from "../../../context/Theme";
|
||||
|
||||
const ThemeToggle = ({ theme, onThemeToggle, t }) => {
|
||||
const actualTheme = useActualTheme();
|
||||
const [open, setOpen] = useState(false);
|
||||
const ref = useRef(null);
|
||||
|
||||
const themeOptions = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: 'light',
|
||||
icon: <Sun size={18} />,
|
||||
buttonIcon: <Sun size={18} />,
|
||||
label: t('浅色模式'),
|
||||
description: t('始终使用浅色主题'),
|
||||
key: "light",
|
||||
icon: <Sun size={16} />,
|
||||
label: t("浅色模式"),
|
||||
},
|
||||
{
|
||||
key: 'dark',
|
||||
icon: <Moon size={18} />,
|
||||
buttonIcon: <Moon size={18} />,
|
||||
label: t('深色模式'),
|
||||
description: t('始终使用深色主题'),
|
||||
key: "dark",
|
||||
icon: <Moon size={16} />,
|
||||
label: t("深色模式"),
|
||||
},
|
||||
{
|
||||
key: 'auto',
|
||||
icon: <Monitor size={18} />,
|
||||
buttonIcon: <Monitor size={18} />,
|
||||
label: t('自动模式'),
|
||||
description: t('跟随系统主题设置'),
|
||||
key: "auto",
|
||||
icon: <Monitor size={16} />,
|
||||
label: t("自动模式"),
|
||||
},
|
||||
],
|
||||
[t],
|
||||
);
|
||||
|
||||
const getItemClassName = (isSelected) =>
|
||||
isSelected
|
||||
? '!bg-semi-color-primary-light-default !font-semibold'
|
||||
: 'hover:!bg-semi-color-fill-1';
|
||||
|
||||
const currentButtonIcon = useMemo(() => {
|
||||
const currentOption = themeOptions.find((option) => option.key === theme);
|
||||
return currentOption?.buttonIcon || themeOptions[2].buttonIcon;
|
||||
const currentIcon = useMemo(() => {
|
||||
const opt = themeOptions.find((o) => o.key === theme);
|
||||
return opt?.icon || themeOptions[2].icon;
|
||||
}, [theme, themeOptions]);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
position='bottomRight'
|
||||
trigger='click'
|
||||
clickToHide
|
||||
render={
|
||||
<Dropdown.Menu>
|
||||
{themeOptions.map((option) => (
|
||||
<Dropdown.Item
|
||||
key={option.key}
|
||||
icon={option.icon}
|
||||
onClick={() => onThemeToggle(option.key)}
|
||||
className={getItemClassName(theme === option.key)}
|
||||
>
|
||||
<div className='flex flex-col'>
|
||||
<span>{option.label}</span>
|
||||
<span className='text-xs text-semi-color-text-2'>
|
||||
{option.description}
|
||||
</span>
|
||||
</div>
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
// Close on outside click
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const handler = (e) => {
|
||||
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
||||
};
|
||||
document.addEventListener("mousedown", handler);
|
||||
return () => document.removeEventListener("mousedown", handler);
|
||||
}, [open]);
|
||||
|
||||
{theme === 'auto' && (
|
||||
<>
|
||||
<Dropdown.Divider />
|
||||
<div className='px-3 py-2 text-xs text-semi-color-text-2'>
|
||||
{t('当前跟随系统')}:
|
||||
{actualTheme === 'dark' ? t('深色') : t('浅色')}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Dropdown.Menu>
|
||||
}
|
||||
return (
|
||||
<div className="relative" ref={ref}>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t("切换主题")}
|
||||
onClick={() => setOpen(!open)}
|
||||
className="inline-flex items-center justify-center p-2 rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-colors"
|
||||
>
|
||||
<span
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
aria-label={t('切换主题')}
|
||||
className='inline-flex items-center justify-center p-1.5 rounded-full cursor-pointer text-current bg-semi-color-fill-0 hover:bg-semi-color-fill-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-semi-color-primary'
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
e.currentTarget.click();
|
||||
}
|
||||
{currentIcon}
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div className="absolute right-0 top-full mt-1 z-50 min-w-[10rem] rounded-lg border border-border bg-popover p-1 shadow-md animate-fade-in">
|
||||
{themeOptions.map((option) => {
|
||||
const selected = theme === option.key;
|
||||
return (
|
||||
<button
|
||||
key={option.key}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onThemeToggle(option.key);
|
||||
setOpen(false);
|
||||
}}
|
||||
className={`flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-sm transition-colors ${
|
||||
selected
|
||||
? "bg-accent text-accent-foreground font-medium"
|
||||
: "text-popover-foreground hover:bg-accent hover:text-accent-foreground"
|
||||
}`}
|
||||
>
|
||||
{currentButtonIcon}
|
||||
{option.icon}
|
||||
<span>{option.label}</span>
|
||||
{selected && (
|
||||
<span className="ml-auto flex h-3.5 w-3.5 items-center justify-center">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-primary"></span>
|
||||
</span>
|
||||
</Dropdown>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{theme === "auto" && (
|
||||
<div className="border-t border-border mt-1 pt-1 px-2.5 py-1.5 text-xs text-muted-foreground">
|
||||
{t("当前跟随系统")}:{actualTheme === "dark" ? t("深色") : t("浅色")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ const UserArea = ({
|
|||
size='small'
|
||||
className='text-gray-500 dark:text-gray-400'
|
||||
/>
|
||||
<span>{t('令牌管理')}</span>
|
||||
<span>{t('API Key')}</span>
|
||||
</div>
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -57,11 +57,9 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
|||
const [messageModalVisible, setMessageModalVisible] = useState(false);
|
||||
const { unreadCount: messageUnreadCount, refreshUnreadCount } =
|
||||
useUserMessageUnreadCount(userState?.user);
|
||||
// handleMessageModalOpen 打开站内消息弹窗。
|
||||
const handleMessageModalOpen = useCallback(() => {
|
||||
setMessageModalVisible(true);
|
||||
}, []);
|
||||
// handleMessageModalClose 关闭站内消息弹窗并刷新未读计数。
|
||||
const handleMessageModalClose = useCallback(async () => {
|
||||
setMessageModalVisible(false);
|
||||
await refreshUnreadCount();
|
||||
|
|
@ -70,7 +68,7 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
|||
const { mainNavLinks } = useNavigation(t, docsNav, headerNavModules);
|
||||
|
||||
return (
|
||||
<header className='text-semi-color-text-0 sticky top-0 z-50 transition-colors duration-300 bg-white/75 dark:bg-[rgba(24,24,27,0.75)] backdrop-blur-lg border-b border-[#f5f5f5] dark:border-semi-color-border'>
|
||||
<header className="sticky top-0 z-50 w-full border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<UserMessageModal
|
||||
visible={messageModalVisible}
|
||||
onClose={handleMessageModalClose}
|
||||
|
|
@ -79,9 +77,8 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
|||
t={t}
|
||||
/>
|
||||
|
||||
<div className='w-full px-4 md:px-6'>
|
||||
<div className='flex items-center justify-between h-14 gap-2'>
|
||||
<div className='flex items-center gap-2 md:gap-4 flex-1 min-w-0 md:flex-initial'>
|
||||
<div className="flex h-14 items-center px-4 md:px-6">
|
||||
<div className="flex items-center gap-2 md:gap-4">
|
||||
<MobileMenuButton
|
||||
isConsoleRoute={isConsoleRoute}
|
||||
isMobile={isMobile}
|
||||
|
|
@ -100,11 +97,11 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
|||
systemName={systemName}
|
||||
isSelfUseMode={isSelfUseMode}
|
||||
isDemoSiteMode={isDemoSiteMode}
|
||||
userState={userState}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='min-w-0 flex-1 overflow-hidden md:hidden'>
|
||||
<div className="min-w-0 overflow-hidden md:hidden">
|
||||
<MobileSiteNavDropdown
|
||||
mainNavLinks={mainNavLinks}
|
||||
pricingRequireAuth={pricingRequireAuth}
|
||||
|
|
@ -114,10 +111,9 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* {!isMobile && <SearchDropdown isMobile={isMobile} />} */}
|
||||
</div>
|
||||
<div className="flex-1" />
|
||||
|
||||
<div className='flex flex-shrink-0 items-center gap-2 md:gap-6'>
|
||||
<div className="flex items-center gap-1 md:gap-2">
|
||||
<Navigation
|
||||
mainNavLinks={mainNavLinks}
|
||||
isMobile={isMobile}
|
||||
|
|
@ -144,7 +140,6 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import React, { useEffect, useRef, useState } from 'react';
|
|||
const ROOT_MARGIN = '280px 0px 280px 0px';
|
||||
|
||||
/**
|
||||
* 操练场消息气泡:进入(或接近)可视区域后再挂载子树,减轻长对话下 DOM/媒体压力。
|
||||
* 体验馆消息气泡:进入(或接近)可视区域后再挂载子树,减轻长对话下 DOM/媒体压力。
|
||||
* variant=media:更高占位,避免图片/视频模式切换后列表高度不足导致滚不到底部。
|
||||
*/
|
||||
const LazyVisibleMessage = ({
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import {
|
|||
import ParameterControl from './ParameterControl';
|
||||
|
||||
/**
|
||||
* 操练场分组下拉:与全局 renderGroupOption 一致,但不展示倍率角标。
|
||||
* 体验馆分组下拉:与全局 renderGroupOption 一致,但不展示倍率角标。
|
||||
* @param {Record<string, unknown>} item Semi Select 传入的选项渲染参数
|
||||
*/
|
||||
const renderPlaygroundGroupOption = (item) =>
|
||||
|
|
@ -85,7 +85,7 @@ const SettingsPanel = ({
|
|||
const mediaModeEnabled = isImageMode || isVideoMode;
|
||||
const videoMediaHint = isVideoMode
|
||||
? t(
|
||||
'操练场视频素材提示',
|
||||
'体验馆视频素材提示',
|
||||
'图片地址:第 1 张为首帧,2 张为首尾帧,更多张时最后一张为尾帧。视频地址:填写则作为源视频参与生成。未填写的字段不会加入请求。',
|
||||
)
|
||||
: '';
|
||||
|
|
|
|||
|
|
@ -83,11 +83,11 @@ const VideoUrlInput = ({
|
|||
|
||||
{!enabled ? (
|
||||
<Typography.Text className='text-xs text-gray-500 mb-2 block'>
|
||||
{t('操练场视频地址停用提示', '启用后可添加视频 URL(视频生视频、视频编辑等)')}
|
||||
{t('体验馆视频地址停用提示', '启用后可添加视频 URL(视频生视频、视频编辑等)')}
|
||||
</Typography.Text>
|
||||
) : list.length === 0 ? (
|
||||
<Typography.Text className='text-xs text-gray-500 mb-2 block'>
|
||||
{t('操练场视频地址空列表提示', '点击 + 添加 .mp4 / .mov 等可访问的视频链接')}
|
||||
{t('体验馆视频地址空列表提示', '点击 + 添加 .mp4 / .mov 等可访问的视频链接')}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
<Typography.Text className='text-xs text-gray-500 mb-2 block'>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
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.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Form,
|
||||
Table,
|
||||
Modal,
|
||||
Typography,
|
||||
Toast,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { API, showError, showSuccess } from '../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Plus, Pencil, Trash2, RefreshCw } from 'lucide-react';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const EMPTY_MODEL = {
|
||||
name: '',
|
||||
provider: '',
|
||||
intelligence: 0,
|
||||
speed: 0,
|
||||
price: 0,
|
||||
latency: 0,
|
||||
context_window: '',
|
||||
type: 'non-reasoning',
|
||||
openness: 'proprietary',
|
||||
category: 'language',
|
||||
};
|
||||
|
||||
export default function BenchmarkDataManager() {
|
||||
const { t } = useTranslation();
|
||||
const [models, setModels] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [editModalVisible, setEditModalVisible] = useState(false);
|
||||
const [editingIndex, setEditingIndex] = useState(null);
|
||||
const [editForm, setEditForm] = useState({ ...EMPTY_MODEL });
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await API.get('/api/option/');
|
||||
if (res.data.success && res.data.data) {
|
||||
const option = res.data.data.find(
|
||||
(o) => o.key === 'benchmark_data.models',
|
||||
);
|
||||
if (option && option.value) {
|
||||
setModels(JSON.parse(option.value));
|
||||
} else {
|
||||
// try to fetch via the specific key
|
||||
const fallbackRes = await API.get(
|
||||
'/api/option/?key=benchmark_data.models',
|
||||
);
|
||||
if (fallbackRes.data?.data) {
|
||||
setModels(JSON.parse(fallbackRes.data.data.value || '[]'));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
showError(t('获取评测数据失败'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
const saveData = async (newModels) => {
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await API.put('/api/option/', {
|
||||
key: 'benchmark_data.models',
|
||||
value: JSON.stringify(newModels),
|
||||
});
|
||||
if (res.data.success) {
|
||||
showSuccess(t('保存成功'));
|
||||
setModels(newModels);
|
||||
} else {
|
||||
showError(res.data.message || t('保存失败'));
|
||||
}
|
||||
} catch (error) {
|
||||
showError(t('保存失败,请重试'));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const openAddModal = () => {
|
||||
setEditingIndex(null);
|
||||
setEditForm({ ...EMPTY_MODEL });
|
||||
setEditModalVisible(true);
|
||||
};
|
||||
|
||||
const openEditModal = (index) => {
|
||||
setEditingIndex(index);
|
||||
setEditForm({ ...models[index] });
|
||||
setEditModalVisible(true);
|
||||
};
|
||||
|
||||
const handleSaveModel = async () => {
|
||||
if (!editForm.name || !editForm.provider) {
|
||||
showError(t('模型名称和供应商不能为空'));
|
||||
return;
|
||||
}
|
||||
const newModels = [...models];
|
||||
if (editingIndex !== null) {
|
||||
newModels[editingIndex] = { ...editForm };
|
||||
} else {
|
||||
newModels.push({ ...editForm });
|
||||
}
|
||||
await saveData(newModels);
|
||||
setEditModalVisible(false);
|
||||
};
|
||||
|
||||
const handleDeleteModel = async (index) => {
|
||||
const newModels = models.filter((_, i) => i !== index);
|
||||
await saveData(newModels);
|
||||
};
|
||||
|
||||
const handleFieldChange = (field, value) => {
|
||||
setEditForm((prev) => ({
|
||||
...prev,
|
||||
[field]:
|
||||
['intelligence', 'speed', 'price', 'latency'].includes(field)
|
||||
? parseFloat(value) || 0
|
||||
: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t('模型名称'),
|
||||
dataIndex: 'name',
|
||||
width: 180,
|
||||
render: (text, record) => (
|
||||
<div>
|
||||
<div className='font-medium'>{text}</div>
|
||||
<Text type='secondary' size='small'>
|
||||
{record.provider}
|
||||
</Text>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('智能指数'),
|
||||
dataIndex: 'intelligence',
|
||||
width: 100,
|
||||
render: (val) => (
|
||||
<span className='text-purple-400 font-mono'>{val}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('速度 (tok/s)'),
|
||||
dataIndex: 'speed',
|
||||
width: 120,
|
||||
render: (val) => (
|
||||
<span className='text-cyan-400 font-mono'>
|
||||
{val?.toLocaleString()}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('价格 ($/M)'),
|
||||
dataIndex: 'price',
|
||||
width: 100,
|
||||
render: (val) => (
|
||||
<span className='text-emerald-400 font-mono'>${val?.toFixed(2)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('延迟 (s)'),
|
||||
dataIndex: 'latency',
|
||||
width: 100,
|
||||
render: (val) => (
|
||||
<span className='text-amber-400 font-mono'>{val?.toFixed(2)}s</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('类型'),
|
||||
dataIndex: 'type',
|
||||
width: 80,
|
||||
render: (val) => (
|
||||
<span
|
||||
className={`px-2 py-0.5 rounded text-xs ${
|
||||
val === 'reasoning'
|
||||
? 'bg-purple-900/50 text-purple-300'
|
||||
: 'bg-blue-900/50 text-blue-300'
|
||||
}`}
|
||||
>
|
||||
{val === 'reasoning' ? t('推理') : t('通用')}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('操作'),
|
||||
width: 120,
|
||||
render: (_, __, index) => (
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
size='small'
|
||||
type='tertiary'
|
||||
icon={<Pencil size={14} />}
|
||||
onClick={() => openEditModal(index)}
|
||||
/>
|
||||
<Button
|
||||
size='small'
|
||||
type='danger'
|
||||
icon={<Trash2 size={14} />}
|
||||
onClick={() => handleDeleteModel(index)}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t('AI 模型评测数据管理')}
|
||||
style={{ marginBottom: 16 }}
|
||||
headerExtraContent={
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
size='small'
|
||||
type='tertiary'
|
||||
icon={<RefreshCw size={14} />}
|
||||
onClick={fetchData}
|
||||
loading={loading}
|
||||
>
|
||||
{t('刷新')}
|
||||
</Button>
|
||||
<Button
|
||||
size='small'
|
||||
type='primary'
|
||||
icon={<Plus size={14} />}
|
||||
onClick={openAddModal}
|
||||
>
|
||||
{t('添加模型')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Text type='secondary' size='small' style={{ marginBottom: 16, display: 'block' }}>
|
||||
{t('在此管理 AI 模型评测数据,添加、编辑或删除模型条目。保存后前端评测页面将自动更新。')}
|
||||
</Text>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={models}
|
||||
pagination={{ pageSize: 20 }}
|
||||
loading={loading}
|
||||
rowKey={(_, idx) => idx}
|
||||
size='small'
|
||||
bordered
|
||||
/>
|
||||
|
||||
{/* Edit/Add Modal */}
|
||||
<Modal
|
||||
title={editingIndex !== null ? t('编辑模型') : t('添加模型')}
|
||||
visible={editModalVisible}
|
||||
onOk={handleSaveModel}
|
||||
onCancel={() => setEditModalVisible(false)}
|
||||
okText={t('保存')}
|
||||
cancelText={t('取消')}
|
||||
confirmLoading={saving}
|
||||
style={{ width: 520 }}
|
||||
>
|
||||
<Form layout='vertical'>
|
||||
<div className='grid grid-cols-2 gap-3'>
|
||||
<Form.Input
|
||||
field='name'
|
||||
label={t('模型名称')}
|
||||
value={editForm.name}
|
||||
onChange={(v) => handleFieldChange('name', v)}
|
||||
placeholder='e.g. Claude Opus 4.8'
|
||||
/>
|
||||
<Form.Input
|
||||
field='provider'
|
||||
label={t('供应商')}
|
||||
value={editForm.provider}
|
||||
onChange={(v) => handleFieldChange('provider', v)}
|
||||
placeholder='e.g. Anthropic'
|
||||
/>
|
||||
<Form.InputNumber
|
||||
field='intelligence'
|
||||
label={t('智能指数')}
|
||||
value={editForm.intelligence}
|
||||
onChange={(v) => handleFieldChange('intelligence', v)}
|
||||
min={0}
|
||||
max={100}
|
||||
/>
|
||||
<Form.InputNumber
|
||||
field='speed'
|
||||
label={t('速度 (tokens/s)')}
|
||||
value={editForm.speed}
|
||||
onChange={(v) => handleFieldChange('speed', v)}
|
||||
min={0}
|
||||
/>
|
||||
<Form.InputNumber
|
||||
field='price'
|
||||
label={t('价格 ($/M tokens)')}
|
||||
value={editForm.price}
|
||||
onChange={(v) => handleFieldChange('price', v)}
|
||||
min={0}
|
||||
step={0.01}
|
||||
/>
|
||||
<Form.InputNumber
|
||||
field='latency'
|
||||
label={t('延迟 (秒)')}
|
||||
value={editForm.latency}
|
||||
onChange={(v) => handleFieldChange('latency', v)}
|
||||
min={0}
|
||||
step={0.01}
|
||||
/>
|
||||
<Form.Input
|
||||
field='context_window'
|
||||
label={t('上下文窗口')}
|
||||
value={editForm.context_window}
|
||||
onChange={(v) => handleFieldChange('context_window', v)}
|
||||
placeholder='e.g. 128K'
|
||||
/>
|
||||
<Form.Select
|
||||
field='type'
|
||||
label={t('模型类型')}
|
||||
value={editForm.type}
|
||||
onChange={(v) => handleFieldChange('type', v)}
|
||||
>
|
||||
<Form.Select.Option value='reasoning'>
|
||||
{t('推理模型')}
|
||||
</Form.Select.Option>
|
||||
<Form.Select.Option value='non-reasoning'>
|
||||
{t('通用模型')}
|
||||
</Form.Select.Option>
|
||||
</Form.Select>
|
||||
<Form.Select
|
||||
field='openness'
|
||||
label={t('开放性')}
|
||||
value={editForm.openness}
|
||||
onChange={(v) => handleFieldChange('openness', v)}
|
||||
>
|
||||
<Form.Select.Option value='proprietary'>
|
||||
{t('闭源')}
|
||||
</Form.Select.Option>
|
||||
<Form.Select.Option value='open-weights'>
|
||||
{t('开放权重')}
|
||||
</Form.Select.Option>
|
||||
</Form.Select>
|
||||
<Form.Select
|
||||
field='category'
|
||||
label={t('类别')}
|
||||
value={editForm.category}
|
||||
onChange={(v) => handleFieldChange('category', v)}
|
||||
>
|
||||
<Form.Select.Option value='language'>
|
||||
{t('语言模型')}
|
||||
</Form.Select.Option>
|
||||
<Form.Select.Option value='image'>
|
||||
{t('图像模型')}
|
||||
</Form.Select.Option>
|
||||
<Form.Select.Option value='video'>
|
||||
{t('视频模型')}
|
||||
</Form.Select.Option>
|
||||
</Form.Select>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -212,11 +212,11 @@ const NotificationSettings = ({
|
|||
{
|
||||
key: 'chat',
|
||||
title: t('聊天区域'),
|
||||
description: t('操练场和聊天功能'),
|
||||
description: t('体验馆和聊天功能'),
|
||||
modules: [
|
||||
{
|
||||
key: 'playground',
|
||||
title: t('操练场'),
|
||||
title: t('体验馆'),
|
||||
description: t('AI模型测试环境'),
|
||||
},
|
||||
{ key: 'chat', title: t('聊天'), description: t('聊天会话管理') },
|
||||
|
|
@ -228,7 +228,7 @@ const NotificationSettings = ({
|
|||
description: t('数据管理和日志查看'),
|
||||
modules: [
|
||||
{ key: 'detail', title: t('数据看板'), description: t('系统数据统计') },
|
||||
{ key: 'token', title: t('令牌管理'), description: t('API令牌管理') },
|
||||
{ key: 'token', title: t('API Key'), description: t('APIAPI Key') },
|
||||
{ key: 'log', title: t('使用日志'), description: t('API使用记录') },
|
||||
{
|
||||
key: 'midjourney',
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ export default function ChannelImportModal({ refresh }) {
|
|||
/>
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Text type='quaternary' size='small'>
|
||||
{t('此密钥将作为所有建站渠道的 API Key,用于访问上游平台。请在目标平台的令牌管理页面创建获取。')}
|
||||
{t('此密钥将作为所有建站渠道的 API Key,用于访问上游平台。请在目标平台的API Key页面创建获取。')}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ const ModelTokenList = ({ visible, t }) => {
|
|||
}
|
||||
}, [tokens.length]);
|
||||
|
||||
/** 跳转到令牌管理页 */
|
||||
/** 跳转到API Key页 */
|
||||
const goTokenPage = () => {
|
||||
navigate('/console/token');
|
||||
};
|
||||
|
|
@ -299,7 +299,7 @@ const ModelTokenList = ({ visible, t }) => {
|
|||
goTokenPage();
|
||||
}}
|
||||
>
|
||||
{t('前往令牌管理')}
|
||||
{t('前往API Key')}
|
||||
</Button>
|
||||
</div>
|
||||
{tokens.length > 0 ? (
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ const TokensDescription = ({ compactMode, setCompactMode, t }) => {
|
|||
<div className='flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full'>
|
||||
<div className='flex items-center text-blue-500'>
|
||||
<Key size={16} className='mr-2' />
|
||||
<Text>{t('令牌管理')}</Text>
|
||||
<Text>{t('API Key')}</Text>
|
||||
</div>
|
||||
|
||||
<CompactModeToggle
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ export const ERROR_MESSAGES = {
|
|||
NETWORK_ERROR: '网络连接失败或服务器无响应',
|
||||
};
|
||||
|
||||
/** 操练场文生视频时长(秒):3~30,默认 5 */
|
||||
/** 体验馆文生视频时长(秒):3~30,默认 5 */
|
||||
export const PLAYGROUND_VIDEO_DURATION_OPTIONS = Array.from(
|
||||
{ length: 28 },
|
||||
(_, i) => {
|
||||
|
|
@ -153,7 +153,7 @@ export const PLAYGROUND_VIDEO_DURATION_OPTIONS = Array.from(
|
|||
},
|
||||
);
|
||||
|
||||
// 操练场图片分辨率:value 为上游 size,label 仅展示 480p / 720p 等
|
||||
// 体验馆图片分辨率:value 为上游 size,label 仅展示 480p / 720p 等
|
||||
export const PLAYGROUND_IMAGE_SIZE_OPTIONS = [
|
||||
{ label: '480p', value: '854x480' },
|
||||
{ label: '720p', value: '1280x720' },
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export function buildImageMarkdown(sources) {
|
|||
.join('\n\n');
|
||||
}
|
||||
|
||||
/** 构造操练场消息 patch:generatedImages + markdown 正文 */
|
||||
/** 构造体验馆消息 patch:generatedImages + markdown 正文 */
|
||||
export function buildImageMessageContentPatch(sources) {
|
||||
const images = dedupeImageSources(sources);
|
||||
if (!images.length) return null;
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export function applyVideoFrameMetadata(metadata, imageUrls) {
|
|||
}
|
||||
|
||||
/**
|
||||
* 操练场 metadata.input:只传 prompt,media.type 由后端 alivideo adaptor 按模型规范化。
|
||||
* 体验馆 metadata.input:只传 prompt,media.type 由后端 alivideo adaptor 按模型规范化。
|
||||
*/
|
||||
export function buildVideoNativeInput(prompt) {
|
||||
return { prompt: String(prompt || '').trim() };
|
||||
|
|
|
|||
|
|
@ -190,6 +190,8 @@ export function getLucideIcon(key, selected = false) {
|
|||
return <Store {...commonProps} color={iconColor} />;
|
||||
case 'operation-log':
|
||||
return <ClipboardCheck {...commonProps} color={iconColor} />;
|
||||
case 'benchmarks':
|
||||
return <BarChart3 {...commonProps} color={iconColor} />;
|
||||
default:
|
||||
return <CircleUser {...commonProps} color={iconColor} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,11 +23,10 @@ export const useNavigation = (t, docsNav, headerNavModules) => {
|
|||
const mainNavLinks = useMemo(() => {
|
||||
// 默认配置,如果没有传入配置则显示所有模块
|
||||
const defaultModules = {
|
||||
home: true,
|
||||
console: true,
|
||||
pricing: true,
|
||||
console: true,
|
||||
benchmark: true,
|
||||
docs: true,
|
||||
about: true,
|
||||
};
|
||||
|
||||
// 使用传入的配置或默认配置
|
||||
|
|
@ -35,9 +34,9 @@ export const useNavigation = (t, docsNav, headerNavModules) => {
|
|||
|
||||
const allLinks = [
|
||||
{
|
||||
text: t('首页'),
|
||||
itemKey: 'home',
|
||||
to: '/',
|
||||
text: t('模型'),
|
||||
itemKey: 'pricing',
|
||||
to: '/pricing',
|
||||
},
|
||||
{
|
||||
text: t('控制台'),
|
||||
|
|
@ -45,9 +44,9 @@ export const useNavigation = (t, docsNav, headerNavModules) => {
|
|||
to: '/console',
|
||||
},
|
||||
{
|
||||
text: t('模型广场'),
|
||||
itemKey: 'pricing',
|
||||
to: '/pricing',
|
||||
text: t('评测'),
|
||||
itemKey: 'benchmark',
|
||||
to: '/benchmarks',
|
||||
},
|
||||
...(docsNav?.href
|
||||
? [
|
||||
|
|
@ -60,11 +59,6 @@ export const useNavigation = (t, docsNav, headerNavModules) => {
|
|||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
text: t('关于'),
|
||||
itemKey: 'about',
|
||||
to: '/about',
|
||||
},
|
||||
];
|
||||
|
||||
// 根据配置过滤导航链接
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export const DEFAULT_SIDEBAR_CONFIG = {
|
|||
},
|
||||
console: {
|
||||
enabled: true,
|
||||
benchmarks: true,
|
||||
detail: true,
|
||||
token: true,
|
||||
log: true,
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ const normalizeTagList = (csv) => {
|
|||
const hasTag = (csv, tag) => normalizeTagList(csv).includes(tag);
|
||||
|
||||
/**
|
||||
* 操练场数据加载:拉取用户模型与类型(与模型广场同源元数据)、按「全部/类型」在客户端筛模型(同模型广场逻辑),分组单独加载。
|
||||
* 体验馆数据加载:拉取用户模型与类型(与模型广场同源元数据)、按「全部/类型」在客户端筛模型(同模型广场逻辑),分组单独加载。
|
||||
* @param {{ user?: object }} userState 已登录用户状态
|
||||
* @param {{ model?: string, model_type?: string|number, group?: string, selected_route_slug?: string }} inputs 当前表单/配置
|
||||
* @param {Array<{ label: string, value: string|number }>} modelTypes 类型下拉项(与接口同步后的状态,用于按类型重算模型列表)
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@
|
|||
"AI模型配置": "AI model configuration",
|
||||
"AK/SK 模式:使用 AccessKey 和 SecretAccessKey;API Key 模式:使用 API Key": "AK/SK mode uses AccessKey and SecretAccessKey; API Key mode uses an API Key",
|
||||
"API": "API",
|
||||
"API Key": "The Api Key",
|
||||
"API Key": "Token Management",
|
||||
"API Key 模式下不支持批量创建": "Batch creation not supported in API Key mode",
|
||||
"API Key 验证失败": "API Key verification failed",
|
||||
"API Key 验证成功!连接到 io.net 服务正常": "API Key verification successful! Connection to io.net service is normal",
|
||||
|
|
@ -159,7 +159,7 @@
|
|||
"API 时间窗口": "API Time Window",
|
||||
"API 配置": "API Configuration",
|
||||
"API 限制次数": "API Limits",
|
||||
"API令牌管理": "API token management",
|
||||
"APIAPI Key": "API token management",
|
||||
"API使用记录": "API usage records",
|
||||
"API信息": "API Information",
|
||||
"API信息管理,可以配置多个API地址用于状态展示和负载均衡(最多50个)": "API information management, you can configure multiple API addresses for status display and load balancing (maximum 50)",
|
||||
|
|
@ -685,7 +685,6 @@
|
|||
"令牌更新成功!": "Token updated successfully!",
|
||||
"令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制": "The quota of the token is only used to limit the maximum quota usage of the token itself, and the actual usage is limited by the remaining quota of the account",
|
||||
"令牌端点": "Token Endpoint",
|
||||
"令牌管理": "Token Management",
|
||||
"以下上游数据可能不可信:": "The following upstream data may not be reliable: ",
|
||||
"以下文件解析失败,已忽略:{{list}}": "The following files failed to parse and have been ignored: {{list}}",
|
||||
"以及": "and",
|
||||
|
|
@ -1167,7 +1166,7 @@
|
|||
"刷新缓存统计失败": "Failed to refresh cache statistics",
|
||||
"前往": "Pergi ke",
|
||||
"前往 io.net API Keys": "Go to io.net API Keys",
|
||||
"前往令牌管理": "Go to token management",
|
||||
"前往API Key": "Go to token management",
|
||||
"前往倍率设置": "Go to magnification settings",
|
||||
"前往创建令牌": "Go to Create Token",
|
||||
"前往申请": "Go to Apply",
|
||||
|
|
@ -2340,8 +2339,8 @@
|
|||
"操作暂时被禁用": "Operation temporarily disabled",
|
||||
"操作确认": "Operation confirmation",
|
||||
"操作类型": "Operation Type",
|
||||
"操练场": "Playground",
|
||||
"操练场和聊天功能": "Playground and chat functions",
|
||||
"体验馆": "Playground",
|
||||
"体验馆和聊天功能": "Playground and chat functions",
|
||||
"支付": "Pay",
|
||||
"支付地址": "Payment address",
|
||||
"支付失败": "Payment failed",
|
||||
|
|
@ -4246,7 +4245,7 @@
|
|||
"视频本地按 token 计费": "Video billed locally by estimated tokens",
|
||||
"视频本地按条/分辨率计价": "Video billed locally per clip / resolution",
|
||||
"视频模式支持图片或视频 URL 作为素材": "Video mode supports image or video URLs as elements",
|
||||
"操练场视频素材提示": "Image URLs: 1st = first frame, 2 = first+last frame, 3+ = first, references in between, last = final frame. Video URLs: included when filled. Empty fields are omitted from the request.",
|
||||
"体验馆视频素材提示": "Image URLs: 1st = first frame, 2 = first+last frame, 3+ = first, references in between, last = final frame. Video URLs: included when filled. Empty fields are omitted from the request.",
|
||||
"视频地址": "Video URL",
|
||||
"视频生成": "Video Generation",
|
||||
"视频生成中,请稍后": "Generating video, please wait",
|
||||
|
|
@ -5673,6 +5672,14 @@
|
|||
"沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment",
|
||||
"沙箱环境商户私钥": "Sandbox Merchant Private Key",
|
||||
"请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.",
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL"
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL",
|
||||
"评测": "Benchmarks",
|
||||
"数据": "Data",
|
||||
"让每位 AI 创造者,": "For every AI creator,",
|
||||
"少走一步弯路": "one less detour",
|
||||
"兼容 OpenAI / Claude / Gemini 等协议,覆盖文本、图像、视频、语音,智能路由,统一计费。": "Compatible with OpenAI, Claude, Gemini, and more. Covers text, image, video, and voice with intelligent routing and unified billing.",
|
||||
"开始使用": "Get Started",
|
||||
"查看价格": "View Pricing",
|
||||
"页面正在建设中...": "Page under construction..."
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
"AI模型配置": "Configuration du modèle d'IA",
|
||||
"AK/SK 模式:使用 AccessKey 和 SecretAccessKey;API Key 模式:使用 API Key": "Mode AK/SK : utiliser AccessKey et SecretAccessKey ; mode API Key : utiliser API Key",
|
||||
"API": "API",
|
||||
"API Key": "API Key",
|
||||
"API Key": "Jetons",
|
||||
"API Key 模式下不支持批量创建": "Création en lot non prise en charge en mode clé API",
|
||||
"API Key 验证失败": "API Key verification failed",
|
||||
"API Key 验证成功!连接到 io.net 服务正常": "API Key verification successful! Connection to io.net service is normal",
|
||||
|
|
@ -158,7 +158,7 @@
|
|||
"API 时间窗口": "Fenêtre de temps API",
|
||||
"API 配置": "Config. API",
|
||||
"API 限制次数": "Nombre limite API",
|
||||
"API令牌管理": "Jetons API",
|
||||
"APIAPI Key": "Jetons API",
|
||||
"API使用记录": "Journaux d'API",
|
||||
"API信息": "Informations sur l'API",
|
||||
"API信息管理,可以配置多个API地址用于状态展示和负载均衡(最多50个)": "Infos API, vous pouvez configurer plusieurs adresses d'API pour l'affichage de l'état et l'équilibrage de charge (maximum 50)",
|
||||
|
|
@ -660,7 +660,6 @@
|
|||
"令牌更新成功!": "Jeton mis à jour avec succès !",
|
||||
"令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制": "Le quota du jeton est uniquement utilisé pour limiter l'utilisation maximale du quota du jeton lui-même, et l'utilisation réelle est limitée par le quota restant du compte",
|
||||
"令牌端点": "Token Endpoint",
|
||||
"令牌管理": "Jetons",
|
||||
"以下上游数据可能不可信:": "Les données en amont suivantes peuvent ne pas être fiables : ",
|
||||
"以下文件解析失败,已忽略:{{list}}": "L'analyse des fichiers suivants a échoué, ignorés : {{list}}",
|
||||
"以及": "et",
|
||||
|
|
@ -1137,7 +1136,7 @@
|
|||
"刷新缓存统计失败": "Échec de l'actualisation des statistiques du cache",
|
||||
"前往": "Pergi ke",
|
||||
"前往 io.net API Keys": "Go to io.net API Keys",
|
||||
"前往令牌管理": "Accédez à la gestion token",
|
||||
"前往API Key": "Accédez à la gestion token",
|
||||
"前往倍率设置": "Accédez aux paramètres de grossissement",
|
||||
"前往创建令牌": "Allez dans Créer un jeton",
|
||||
"前往申请": "Go to Apply",
|
||||
|
|
@ -2307,8 +2306,8 @@
|
|||
"操作暂时被禁用": "Opération temporairement désactivée",
|
||||
"操作确认": "Operation confirmation",
|
||||
"操作类型": "Type d'opération",
|
||||
"操练场": "Terrain de jeu",
|
||||
"操练场和聊天功能": "Terrain de jeu et fonctions de discussion",
|
||||
"体验馆": "Terrain de jeu",
|
||||
"体验馆和聊天功能": "Terrain de jeu et fonctions de discussion",
|
||||
"支付": "Payer",
|
||||
"支付地址": "Adresse de paiement",
|
||||
"支付失败": "Paiement échoué",
|
||||
|
|
@ -5584,6 +5583,9 @@
|
|||
"沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment",
|
||||
"沙箱环境商户私钥": "Sandbox Merchant Private Key",
|
||||
"请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.",
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL"
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL",
|
||||
"评测": "Benchmarks",
|
||||
"数据": "Data",
|
||||
"页面正在建设中...": "Page under construction..."
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
"AI模型配置": "Konfigurasi model AI",
|
||||
"AK/SK 模式:使用 AccessKey 和 SecretAccessKey;API Key 模式:使用 API Key": "Mode AK/SK memakai AccessKey dan SecretAccessKey; mode API Key memakai API Key",
|
||||
"API": "API",
|
||||
"API Key": "API Key",
|
||||
"API Key": "Manajemen token",
|
||||
"API Key 模式下不支持批量创建": "Pembuatan massal tidak didukung dalam mode API Key",
|
||||
"API Key 验证失败": "Verifikasi API Key gagal",
|
||||
"API Key 验证成功!连接到 io.net 服务正常": "Verifikasi API Key berhasil! Koneksi ke layanan io.net normal",
|
||||
|
|
@ -158,7 +158,7 @@
|
|||
"API 时间窗口": "Jendela waktu API",
|
||||
"API 配置": "Konfigurasi API",
|
||||
"API 限制次数": "Batas waktu API",
|
||||
"API令牌管理": "Manajemen token API",
|
||||
"APIAPI Key": "Manajemen token API",
|
||||
"API使用记录": "Riwayat penggunaan API",
|
||||
"API信息": "Informasi API",
|
||||
"API信息管理,可以配置多个API地址用于状态展示和负载均衡(最多50个)": "Manajemen info API: konfigurasi beberapa alamat API untuk status dan load balancing (maks. 50)",
|
||||
|
|
@ -660,7 +660,6 @@
|
|||
"令牌更新成功!": "Token berhasil diperbarui!",
|
||||
"令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制": "Kuota token hanya membatasi penggunaan kuota maksimum token itu sendiri; penggunaan aktual dibatasi sisa kuota akun",
|
||||
"令牌端点": "Endpoint token",
|
||||
"令牌管理": "Manajemen token",
|
||||
"以下上游数据可能不可信:": "Data hulu berikut mungkin tidak dapat dipercaya: ",
|
||||
"以下文件解析失败,已忽略:{{list}}": "File berikut gagal di-parse dan diabaikan: {{list}}",
|
||||
"以及": "dan",
|
||||
|
|
@ -1137,7 +1136,7 @@
|
|||
"刷新缓存统计失败": "Gagal menyegarkan statistik cache",
|
||||
"前往": "Pergi ke",
|
||||
"前往 io.net API Keys": "Buka io.net API Keys",
|
||||
"前往令牌管理": "Buka manajemen token",
|
||||
"前往API Key": "Buka manajemen token",
|
||||
"前往倍率设置": "Buka pengaturan pembesaran",
|
||||
"前往创建令牌": "Pergi untuk membuat token",
|
||||
"前往申请": "Go to Apply",
|
||||
|
|
@ -2307,8 +2306,8 @@
|
|||
"操作暂时被禁用": "Operasi sementara dinonaktifkan",
|
||||
"操作确认": "Konfirmasi operasi",
|
||||
"操作类型": "Jenis operasi",
|
||||
"操练场": "Arena latihan",
|
||||
"操练场和聊天功能": "Arena latihan dan fungsi obrolan",
|
||||
"体验馆": "Arena latihan",
|
||||
"体验馆和聊天功能": "Arena latihan dan fungsi obrolan",
|
||||
"支付": "Bayar",
|
||||
"支付地址": "Alamat pembayaran",
|
||||
"支付失败": "Pembayaran gagal",
|
||||
|
|
@ -5584,6 +5583,9 @@
|
|||
"沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment",
|
||||
"沙箱环境商户私钥": "Sandbox Merchant Private Key",
|
||||
"请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.",
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL"
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL",
|
||||
"评测": "Benchmarks",
|
||||
"数据": "Data",
|
||||
"页面正在建设中...": "Page under construction..."
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
"AI模型配置": "AIモデル設定",
|
||||
"AK/SK 模式:使用 AccessKey 和 SecretAccessKey;API Key 模式:使用 API Key": "AK/SK モードでは AccessKey と SecretAccessKey を使用し、API Key モードでは API Key を使用します",
|
||||
"API": "API",
|
||||
"API Key": "API Key",
|
||||
"API Key": "トークン管理",
|
||||
"API Key 模式下不支持批量创建": "APIキーモードでは一括作成はサポート対象外です",
|
||||
"API Key 验证失败": "API Key の検証に失敗しました",
|
||||
"API Key 验证成功!连接到 io.net 服务正常": "API Key の検証に成功しました。io.net サービスへの接続は正常です",
|
||||
|
|
@ -158,7 +158,7 @@
|
|||
"API 时间窗口": "API 時間枠",
|
||||
"API 配置": "API設定",
|
||||
"API 限制次数": "API 制限回数",
|
||||
"API令牌管理": "APIトークン管理",
|
||||
"APIAPI Key": "APIトークン管理",
|
||||
"API使用记录": "API利用履歴",
|
||||
"API信息": "API情報",
|
||||
"API信息管理,可以配置多个API地址用于状态展示和负载均衡(最多50个)": "API情報管理:ステータス表示とロードバランシング用に、複数のベースURL(最大50個)を設定できます",
|
||||
|
|
@ -660,7 +660,6 @@
|
|||
"令牌更新成功!": "トークンの更新に成功しました",
|
||||
"令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制": "トークンのクォータは、トークン自体の最大クォータ使用量を制限するためにのみ使用され、実際の使用量はアカウントの残りクォータによって制限されます",
|
||||
"令牌端点": "Token Endpoint",
|
||||
"令牌管理": "トークン管理",
|
||||
"以下上游数据可能不可信:": "以下のアップストリームデータは信頼できない可能性があります:",
|
||||
"以下文件解析失败,已忽略:{{list}}": "以下のファイルは解析に失敗したため無視されました:{{list}}",
|
||||
"以及": "および",
|
||||
|
|
@ -1137,7 +1136,7 @@
|
|||
"刷新缓存统计失败": "キャッシュ統計の更新に失敗しました",
|
||||
"前往": "前往",
|
||||
"前往 io.net API Keys": "Go to io.net API Keys",
|
||||
"前往令牌管理": "前往令牌管理",
|
||||
"前往API Key": "前往API Key",
|
||||
"前往倍率设置": "前往倍率設定",
|
||||
"前往创建令牌": "前往创建令牌",
|
||||
"前往申请": "Go to Apply",
|
||||
|
|
@ -2307,8 +2306,8 @@
|
|||
"操作暂时被禁用": "この操作は一時的に無効にされています",
|
||||
"操作确认": "Operation confirmation",
|
||||
"操作类型": "操作タイプ",
|
||||
"操练场": "Playground",
|
||||
"操练场和聊天功能": "プレイグラウンドとチャット機能",
|
||||
"体验馆": "Playground",
|
||||
"体验馆和聊天功能": "プレイグラウンドとチャット機能",
|
||||
"支付": "支払う",
|
||||
"支付地址": "決済URL",
|
||||
"支付失败": "支払いに失敗しました",
|
||||
|
|
@ -5584,6 +5583,9 @@
|
|||
"沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment",
|
||||
"沙箱环境商户私钥": "Sandbox Merchant Private Key",
|
||||
"请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.",
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL"
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL",
|
||||
"评测": "Benchmarks",
|
||||
"数据": "Data",
|
||||
"页面正在建设中...": "Page under construction..."
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
"AI模型配置": "Konfigurasi model AI",
|
||||
"AK/SK 模式:使用 AccessKey 和 SecretAccessKey;API Key 模式:使用 API Key": "Mod AK/SK menggunakan AccessKey dan SecretAccessKey; mod API Key menggunakan API Key",
|
||||
"API": "API",
|
||||
"API Key": "API Key",
|
||||
"API Key": "Pengurusan token",
|
||||
"API Key 模式下不支持批量创建": "Penciptaan pukal tidak disokong dalam mod API Key",
|
||||
"API Key 验证失败": "Pengesahan API Key gagal",
|
||||
"API Key 验证成功!连接到 io.net 服务正常": "Pengesahan API Key berjaya! Sambungan ke perkhidmatan io.net normal",
|
||||
|
|
@ -158,7 +158,7 @@
|
|||
"API 时间窗口": "Tetingkap masa API",
|
||||
"API 配置": "Konfigurasi API",
|
||||
"API 限制次数": "Masa had API",
|
||||
"API令牌管理": "Pengurusan token API",
|
||||
"APIAPI Key": "Pengurusan token API",
|
||||
"API使用记录": "Rekod penggunaan API",
|
||||
"API信息": "Maklumat API",
|
||||
"API信息管理,可以配置多个API地址用于状态展示和负载均衡(最多50个)": "Pengurusan maklumat API: konfigurasi beberapa alamat API untuk paparan status dan pengimbangan beban (maks. 50)",
|
||||
|
|
@ -660,7 +660,6 @@
|
|||
"令牌更新成功!": "Token dikemas kini berjaya!",
|
||||
"令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制": "Kuota token hanya mengehadkan penggunaan kuota maksimum token itu sendiri; penggunaan sebenar terhad oleh baki kuota akaun",
|
||||
"令牌端点": "Endpoint token",
|
||||
"令牌管理": "Pengurusan token",
|
||||
"以下上游数据可能不可信:": "Data hulu berikut mungkin tidak boleh dipercayai: ",
|
||||
"以下文件解析失败,已忽略:{{list}}": "Fail berikut gagal dihuraikan dan diabaikan: {{list}}",
|
||||
"以及": "dan",
|
||||
|
|
@ -1137,7 +1136,7 @@
|
|||
"刷新缓存统计失败": "Gagal menyegar semula statistik cache",
|
||||
"前往": "Pergi ke",
|
||||
"前往 io.net API Keys": "Pergi ke io.net API Keys",
|
||||
"前往令牌管理": "Pergi ke pengurusan token",
|
||||
"前往API Key": "Pergi ke pengurusan token",
|
||||
"前往倍率设置": "Pergi ke tetapan pembesaran",
|
||||
"前往创建令牌": "Pergi untuk mencipta token",
|
||||
"前往申请": "Go to Apply",
|
||||
|
|
@ -2307,8 +2306,8 @@
|
|||
"操作暂时被禁用": "Operasi dilumpuhkan buat sementara",
|
||||
"操作确认": "Pengesahan operasi",
|
||||
"操作类型": "Jenis operasi",
|
||||
"操练场": "Padang latihan",
|
||||
"操练场和聊天功能": "Padang latihan dan fungsi sembang",
|
||||
"体验馆": "Padang latihan",
|
||||
"体验馆和聊天功能": "Padang latihan dan fungsi sembang",
|
||||
"支付": "Bayar",
|
||||
"支付地址": "Alamat pembayaran",
|
||||
"支付失败": "Pembayaran gagal",
|
||||
|
|
@ -5584,6 +5583,9 @@
|
|||
"沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment",
|
||||
"沙箱环境商户私钥": "Sandbox Merchant Private Key",
|
||||
"请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.",
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL"
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL",
|
||||
"评测": "Benchmarks",
|
||||
"数据": "Data",
|
||||
"页面正在建设中...": "Page under construction..."
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
"AI模型配置": "Конфигурация AI моделей",
|
||||
"AK/SK 模式:使用 AccessKey 和 SecretAccessKey;API Key 模式:使用 API Key": "Режим AK/SK: используйте AccessKey и SecretAccessKey; режим API Key: используйте API Key",
|
||||
"API": "API",
|
||||
"API Key": "API Key",
|
||||
"API Key": "Управление токенами",
|
||||
"API Key 模式下不支持批量创建": "Режим API Key не поддерживает массовое создание",
|
||||
"API Key 验证失败": "API Key verification failed",
|
||||
"API Key 验证成功!连接到 io.net 服务正常": "API Key verification successful! Connection to io.net service is normal",
|
||||
|
|
@ -158,7 +158,7 @@
|
|||
"API 时间窗口": "API временной интервал",
|
||||
"API 配置": "Конфигурация API",
|
||||
"API 限制次数": "API Ограничить количество раз",
|
||||
"API令牌管理": "Управление токенами API",
|
||||
"APIAPI Key": "Управление токенами API",
|
||||
"API使用记录": "История использования API",
|
||||
"API信息": "Информация об API",
|
||||
"API信息管理,可以配置多个API地址用于状态展示和负载均衡(最多50个)": "Управление информацией API, можно настроить несколько адресов API для отображения статуса и балансировки нагрузки (максимум 50)",
|
||||
|
|
@ -660,7 +660,6 @@
|
|||
"令牌更新成功!": "Токен успешно обновлен!",
|
||||
"令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制": "Лимит токена используется только для ограничения максимального использования самого токена, фактическое использование ограничено остаточным лимитом аккаунта",
|
||||
"令牌端点": "Token Endpoint",
|
||||
"令牌管理": "Управление токенами",
|
||||
"以下上游数据可能不可信:": "Следующие upstream данные могут быть недостоверными:",
|
||||
"以下文件解析失败,已忽略:{{list}}": "Не удалось проанализировать следующие файлы, они проигнорированы: {{list}}",
|
||||
"以及": "а также",
|
||||
|
|
@ -1137,7 +1136,7 @@
|
|||
"刷新缓存统计失败": "Failed to refresh cache statistics",
|
||||
"前往": "Перейти к",
|
||||
"前往 io.net API Keys": "Go to io.net API Keys",
|
||||
"前往令牌管理": "Перейти к управлению токенами",
|
||||
"前往API Key": "Перейти к управлению токенами",
|
||||
"前往倍率设置": "Перейти к настройке увеличения",
|
||||
"前往创建令牌": "Перейти к созданию токена",
|
||||
"前往申请": "Go to Apply",
|
||||
|
|
@ -2307,8 +2306,8 @@
|
|||
"操作暂时被禁用": "Операция временно отключена",
|
||||
"操作确认": "Operation confirmation",
|
||||
"操作类型": "Тип операции",
|
||||
"操练场": "Тренировочная площадка",
|
||||
"操练场和聊天功能": "Тренировочная площадка и чат-функции",
|
||||
"体验馆": "Тренировочная площадка",
|
||||
"体验馆和聊天功能": "Тренировочная площадка и чат-функции",
|
||||
"支付": "Оплатить",
|
||||
"支付地址": "Адрес оплаты",
|
||||
"支付失败": "Оплата не удалась",
|
||||
|
|
@ -5584,6 +5583,9 @@
|
|||
"沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment",
|
||||
"沙箱环境商户私钥": "Sandbox Merchant Private Key",
|
||||
"请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.",
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL"
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL",
|
||||
"评测": "Benchmarks",
|
||||
"数据": "Data",
|
||||
"页面正在建设中...": "Page under construction..."
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
"AI模型配置": "Usanidi wa mfano wa AI",
|
||||
"AK/SK 模式:使用 AccessKey 和 SecretAccessKey;API Key 模式:使用 API Key": "Hali ya AK/SK inatumia AccessKey na SecretAccessKey; hali ya API Key inatumia API Key",
|
||||
"API": "API",
|
||||
"API Key": "API Key",
|
||||
"API Key": "Usimamizi wa tokeni",
|
||||
"API Key 模式下不支持批量创建": "Uundaji wa wingi hauauniwi katika hali ya API Key",
|
||||
"API Key 验证失败": "Uthibitishaji wa API Key umeshindwa",
|
||||
"API Key 验证成功!连接到 io.net 服务正常": "Uthibitishaji wa API Key umefanikiwa! Muunganisho kwa huduma ya io.net ni wa kawaida",
|
||||
|
|
@ -158,7 +158,7 @@
|
|||
"API 时间窗口": "Dirisha la saa la API",
|
||||
"API 配置": "Usanidi wa API",
|
||||
"API 限制次数": "API idadi ya kikomo ya nyakati",
|
||||
"API令牌管理": "Usimamizi wa tokeni za API",
|
||||
"APIAPI Key": "Usimamizi wa tokeni za API",
|
||||
"API使用记录": "Rekodi za matumizi ya API",
|
||||
"API信息": "Taarifa za API",
|
||||
"API信息管理,可以配置多个API地址用于状态展示和负载均衡(最多50个)": "Usimamizi wa taarifa za API: sanidi anwani nyingi za API kwa hali na usawa wa mzigo (kadiri ya juu 50)",
|
||||
|
|
@ -660,7 +660,6 @@
|
|||
"令牌更新成功!": "Tokeni imesasishwa kwa mafanikio!",
|
||||
"令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制": "Kiwango cha tokeni kinazuia matumizi ya juu ya tokeni hiyo; matumizi halisi yana mipaka ya kiwango kilichobaki cha akaunti",
|
||||
"令牌端点": "Kituo cha tokeni",
|
||||
"令牌管理": "Usimamizi wa tokeni",
|
||||
"以下上游数据可能不可信:": "Data ya juu ifuatayo inaweza kuwa si ya kuaminika: ",
|
||||
"以下文件解析失败,已忽略:{{list}}": "Faili zifuatazo zilishindwa kuchanganuliwa na zimepuuzwa: {{list}}",
|
||||
"以及": "na",
|
||||
|
|
@ -1137,7 +1136,7 @@
|
|||
"刷新缓存统计失败": "Imeshindwa kuonyesha upya takwimu za akiba",
|
||||
"前往": "Nenda kwa",
|
||||
"前往 io.net API Keys": "Nenda kwa io.net API Keys",
|
||||
"前往令牌管理": "Nenda kwa usimamizi wa ishara",
|
||||
"前往API Key": "Nenda kwa usimamizi wa ishara",
|
||||
"前往倍率设置": "Nenda kwa mipangilio ya ukuzaji",
|
||||
"前往创建令牌": "Nenda kuunda tokeni",
|
||||
"前往申请": "Go to Apply",
|
||||
|
|
@ -2307,8 +2306,8 @@
|
|||
"操作暂时被禁用": "Operesheni imezimwa kwa muda",
|
||||
"操作确认": "Uthibitisho wa operesheni",
|
||||
"操作类型": "Aina ya operesheni",
|
||||
"操练场": "Uwanja wa mazoezi",
|
||||
"操练场和聊天功能": "Uwanja wa mazoezi na mazungumzo",
|
||||
"体验馆": "Uwanja wa mazoezi",
|
||||
"体验馆和聊天功能": "Uwanja wa mazoezi na mazungumzo",
|
||||
"支付": "Lipa",
|
||||
"支付地址": "Anwani ya malipo",
|
||||
"支付失败": "Malipo yameshindwa",
|
||||
|
|
@ -5584,6 +5583,9 @@
|
|||
"沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment",
|
||||
"沙箱环境商户私钥": "Sandbox Merchant Private Key",
|
||||
"请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.",
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL"
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL",
|
||||
"评测": "Benchmarks",
|
||||
"数据": "Data",
|
||||
"页面正在建设中...": "Page under construction..."
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
"AI模型配置": "การตั้งค่าโมเดล AI",
|
||||
"AK/SK 模式:使用 AccessKey 和 SecretAccessKey;API Key 模式:使用 API Key": "โหมด AK/SK ใช้ AccessKey และ SecretAccessKey โหมด API Key ใช้ API Key",
|
||||
"API": "API",
|
||||
"API Key": "API Key",
|
||||
"API Key": "การจัดการโทเค็น",
|
||||
"API Key 模式下不支持批量创建": "โหมด API Key ไม่รองรับการสร้างแบบกลุ่ม",
|
||||
"API Key 验证失败": "ตรวจสอบ API Key ล้มเหลว",
|
||||
"API Key 验证成功!连接到 io.net 服务正常": "ตรวจสอบ API Key สำเร็จ! เชื่อมต่อบริการ io.net ปกติ",
|
||||
|
|
@ -158,7 +158,7 @@
|
|||
"API 时间窗口": "กรอบเวลา API",
|
||||
"API 配置": "การตั้งค่า API",
|
||||
"API 限制次数": "API จำกัดจำนวนครั้ง",
|
||||
"API令牌管理": "จัดการโทเค็น API",
|
||||
"APIAPI Key": "จัดการโทเค็น API",
|
||||
"API使用记录": "บันทึกการใช้ API",
|
||||
"API信息": "ข้อมูล API",
|
||||
"API信息管理,可以配置多个API地址用于状态展示和负载均衡(最多50个)": "จัดการข้อมูล API: ตั้งค่าหลายที่อยู่สำหรับแสดงสถานะและโหลดบาลานซ์ (สูงสุด 50)",
|
||||
|
|
@ -660,7 +660,6 @@
|
|||
"令牌更新成功!": "อัปเดตโทเค็นสำเร็จ!",
|
||||
"令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制": "โควตาโทเค็นจำกัดการใช้สูงสุดของโทเค็นนั้น การใช้งานจริงถูกจำกัดด้วยโควตาคงเหลือของบัญชี",
|
||||
"令牌端点": "เอนด์พอยต์โทเค็น",
|
||||
"令牌管理": "การจัดการโทเค็น",
|
||||
"以下上游数据可能不可信:": "ข้อมูลต้นทางต่อไปนี้อาจไม่น่าเชื่อถือ: ",
|
||||
"以下文件解析失败,已忽略:{{list}}": "ไฟล์ต่อไปนี้แยกวิเคราะห์ล้มเหลวและถูกข้าม: {{list}}",
|
||||
"以及": "และ",
|
||||
|
|
@ -1137,7 +1136,7 @@
|
|||
"刷新缓存统计失败": "รีเฟรชสถิติแคชล้มเหลว",
|
||||
"前往": "ไปที่",
|
||||
"前往 io.net API Keys": "ไปที่ io.net API Keys",
|
||||
"前往令牌管理": "ไปที่การจัดการโทเค็น",
|
||||
"前往API Key": "ไปที่การจัดการโทเค็น",
|
||||
"前往倍率设置": "ไปที่การตั้งค่าการขยาย",
|
||||
"前往创建令牌": "ไปสร้างโทเค็น",
|
||||
"前往申请": "Go to Apply",
|
||||
|
|
@ -2307,8 +2306,8 @@
|
|||
"操作暂时被禁用": "การดำเนินการถูกปิดชั่วคราว",
|
||||
"操作确认": "ยืนยันการดำเนินการ",
|
||||
"操作类型": "ประเภทการดำเนินการ",
|
||||
"操练场": "สนามฝึก",
|
||||
"操练场和聊天功能": "สนามฝึกและแชท",
|
||||
"体验馆": "สนามฝึก",
|
||||
"体验馆和聊天功能": "สนามฝึกและแชท",
|
||||
"支付": "ชำระเงิน",
|
||||
"支付地址": "ที่อยู่การชำระเงิน",
|
||||
"支付失败": "ชำระเงินไม่สำเร็จ",
|
||||
|
|
@ -5584,6 +5583,9 @@
|
|||
"沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment",
|
||||
"沙箱环境商户私钥": "Sandbox Merchant Private Key",
|
||||
"请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.",
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL"
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL",
|
||||
"评测": "Benchmarks",
|
||||
"数据": "Data",
|
||||
"页面正在建设中...": "Page under construction..."
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
"AI模型配置": "Cấu hình mô hình AI",
|
||||
"AK/SK 模式:使用 AccessKey 和 SecretAccessKey;API Key 模式:使用 API Key": "AK/SK mode uses AccessKey and SecretAccessKey; API Key mode uses an API Key",
|
||||
"API": "API",
|
||||
"API Key": "API Key",
|
||||
"API Key": "Quản lý mã thông báo",
|
||||
"API Key 模式下不支持批量创建": "Không hỗ trợ tạo hàng loạt trong chế độ API Key",
|
||||
"API Key 验证失败": "API Key verification failed",
|
||||
"API Key 验证成功!连接到 io.net 服务正常": "API Key verification successful! Connection to io.net service is normal",
|
||||
|
|
@ -158,7 +158,7 @@
|
|||
"API 时间窗口": "API cửa sổ thời gian",
|
||||
"API 配置": "Cấu hình API",
|
||||
"API 限制次数": "API Giới hạn số lần",
|
||||
"API令牌管理": "Quản lý mã thông báo API",
|
||||
"APIAPI Key": "Quản lý mã thông báo API",
|
||||
"API使用记录": "Hồ sơ sử dụng API",
|
||||
"API信息": "Thông tin API",
|
||||
"API信息管理,可以配置多个API地址用于状态展示和负载均衡(最多50个)": "Quản lý thông tin API, bạn có thể cấu hình nhiều địa chỉ API để hiển thị trạng thái và cân bằng tải (tối đa 50)",
|
||||
|
|
@ -660,7 +660,6 @@
|
|||
"令牌更新成功!": "Cập nhật mã thông báo thành công!",
|
||||
"令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制": "Hạn ngạch của mã thông báo chỉ được sử dụng để giới hạn mức sử dụng hạn ngạch tối đa của chính mã thông báo, và việc sử dụng thực tế bị giới hạn bởi hạn ngạch còn lại của tài khoản",
|
||||
"令牌端点": "Token Endpoint",
|
||||
"令牌管理": "Quản lý mã thông báo",
|
||||
"以下上游数据可能不可信:": "Dữ liệu thượng nguồn sau đây có thể không đáng tin cậy: ",
|
||||
"以下文件解析失败,已忽略:{{list}}": "Các tệp sau không phân tích được và đã bị bỏ qua: {{list}}",
|
||||
"以及": "và",
|
||||
|
|
@ -1137,7 +1136,7 @@
|
|||
"刷新缓存统计失败": "Làm mới thống kê bộ nhớ đệm thất bại",
|
||||
"前往": "Đi tới",
|
||||
"前往 io.net API Keys": "Go to io.net API Keys",
|
||||
"前往令牌管理": "Chuyển đến Quản lý mã thông báo",
|
||||
"前往API Key": "Chuyển đến Quản lý mã thông báo",
|
||||
"前往倍率设置": "Chuyển đến cài đặt phóng to",
|
||||
"前往创建令牌": "Đi tới Tạo mã thông báo",
|
||||
"前往申请": "Go to Apply",
|
||||
|
|
@ -2307,8 +2306,8 @@
|
|||
"操作暂时被禁用": "Thao tác tạm thời bị vô hiệu hóa",
|
||||
"操作确认": "Operation confirmation",
|
||||
"操作类型": "Loại thao tác",
|
||||
"操练场": "Sân chơi",
|
||||
"操练场和聊天功能": "Chức năng sân chơi và trò chuyện",
|
||||
"体验馆": "Sân chơi",
|
||||
"体验馆和聊天功能": "Chức năng sân chơi và trò chuyện",
|
||||
"支付": "Thanh toán",
|
||||
"支付地址": "Địa chỉ thanh toán",
|
||||
"支付失败": "Thanh toán thất bại",
|
||||
|
|
@ -5584,6 +5583,9 @@
|
|||
"沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment",
|
||||
"沙箱环境商户私钥": "Sandbox Merchant Private Key",
|
||||
"请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.",
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL"
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL",
|
||||
"评测": "Benchmarks",
|
||||
"数据": "Data",
|
||||
"页面正在建设中...": "Page under construction..."
|
||||
}
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
"API 密钥": "API 密钥",
|
||||
"API 文档": "API 文档",
|
||||
"API 配置": "API 配置",
|
||||
"API令牌管理": "API令牌管理",
|
||||
"APIAPI Key": "APIAPI Key",
|
||||
"API使用记录": "API使用记录",
|
||||
"API信息": "API信息",
|
||||
"API信息管理,可以配置多个API地址用于状态展示和负载均衡(最多50个)": "API信息管理,可以配置多个API地址用于状态展示和负载均衡(最多50个)",
|
||||
|
|
@ -315,7 +315,6 @@
|
|||
"令牌已重置并已复制到剪贴板": "令牌已重置并已复制到剪贴板",
|
||||
"令牌更新成功!": "令牌更新成功!",
|
||||
"令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制": "令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制",
|
||||
"令牌管理": "令牌管理",
|
||||
"以下上游数据可能不可信:": "以下上游数据可能不可信:",
|
||||
"以下文件解析失败,已忽略:{{list}}": "以下文件解析失败,已忽略:{{list}}",
|
||||
"以及": "以及",
|
||||
|
|
@ -1269,8 +1268,8 @@
|
|||
"操作失败,请重试": "操作失败,请重试",
|
||||
"操作成功完成!": "操作成功完成!",
|
||||
"操作暂时被禁用": "操作暂时被禁用",
|
||||
"操练场": "操练场",
|
||||
"操练场和聊天功能": "操练场和聊天功能",
|
||||
"体验馆": "体验馆",
|
||||
"体验馆和聊天功能": "体验馆和聊天功能",
|
||||
"支付地址": "支付地址",
|
||||
"支付宝": "支付宝",
|
||||
"支付方式": "支付方式",
|
||||
|
|
@ -4748,7 +4747,7 @@
|
|||
"刷新临时黑名单": "刷新临时黑名单",
|
||||
"刷新状态": "刷新状态",
|
||||
"前往": "前往",
|
||||
"前往令牌管理": "前往令牌管理",
|
||||
"前往API Key": "前往API Key",
|
||||
"前往倍率设置": "前往倍率设置",
|
||||
"前往创建令牌": "前往创建令牌",
|
||||
"前置": "前置",
|
||||
|
|
@ -5286,7 +5285,7 @@
|
|||
"视频数量": "视频数量",
|
||||
"视频时长(秒)": "视频时长(秒)",
|
||||
"视频模式支持图片或视频 URL 作为素材": "视频模式支持图片或视频 URL 作为素材",
|
||||
"操练场视频素材提示": "图片地址:第 1 张为首帧,2 张为首尾帧,更多张时最后一张为尾帧。视频地址:填写则作为源视频参与生成。未填写的字段不会加入请求。",
|
||||
"体验馆视频素材提示": "图片地址:第 1 张为首帧,2 张为首尾帧,更多张时最后一张为尾帧。视频地址:填写则作为源视频参与生成。未填写的字段不会加入请求。",
|
||||
"视频地址": "视频地址",
|
||||
"视频生成": "视频生成",
|
||||
"视频生成中,请稍后": "视频生成中,请稍后",
|
||||
|
|
@ -5532,6 +5531,14 @@
|
|||
"沙箱环境 APIv3 密钥": "沙箱环境 APIv3 密钥",
|
||||
"沙箱环境商户私钥": "沙箱环境商户私钥",
|
||||
"请确保已配置 APIv3 密钥,并下载商户证书序列号。": "请确保已配置 APIv3 密钥,并下载商户证书序列号。",
|
||||
"可选,留空则使用默认地址": "可选,留空则使用默认地址"
|
||||
"可选,留空则使用默认地址": "可选,留空则使用默认地址",
|
||||
"评测": "评测",
|
||||
"数据": "数据",
|
||||
"让每位 AI 创造者,": "让每位 AI 创造者,",
|
||||
"少走一步弯路": "少走一步弯路",
|
||||
"兼容 OpenAI / Claude / Gemini 等协议,覆盖文本、图像、视频、语音,智能路由,统一计费。": "兼容 OpenAI / Claude / Gemini 等协议,覆盖文本、图像、视频、语音,智能路由,统一计费。",
|
||||
"开始使用": "开始使用",
|
||||
"查看价格": "查看价格",
|
||||
"页面正在建设中...": "页面正在建设中..."
|
||||
}
|
||||
}
|
||||
|
|
@ -142,7 +142,7 @@
|
|||
"AI模型配置": "AI模型配置",
|
||||
"AK/SK 模式:使用 AccessKey 和 SecretAccessKey;API Key 模式:使用 API Key": "AK/SK 模式:使用 AccessKey 和 SecretAccessKey;API Key 模式:使用 API Key",
|
||||
"API": "API",
|
||||
"API Key": "API Key",
|
||||
"API Key": "權杖管理",
|
||||
"API Key 模式下不支持批量创建": "API Key 模式下不支援批量建立",
|
||||
"API Key 验证失败": "API Key 驗證失敗",
|
||||
"API Key 验证成功!连接到 io.net 服务正常": "API Key 驗證成功!連線到 io.net 服務正常",
|
||||
|
|
@ -157,7 +157,7 @@
|
|||
"API 时间窗口": "API 時間視窗",
|
||||
"API 配置": "API 配置",
|
||||
"API 限制次数": "API 限制次數",
|
||||
"API令牌管理": "API權杖管理",
|
||||
"APIAPI Key": "API權杖管理",
|
||||
"API使用记录": "API使用紀錄",
|
||||
"API信息": "API資訊",
|
||||
"API信息管理,可以配置多个API地址用于状态展示和负载均衡(最多50个)": "API資訊管理,可以配置多個API位址用於狀態顯示和負載平衡(最多50個)",
|
||||
|
|
@ -659,7 +659,6 @@
|
|||
"令牌更新成功!": "權杖更新成功!",
|
||||
"令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制": "權杖的額度僅用於限制權杖本身的最大額度使用量,實際的使用受到帳號的剩餘額度限制",
|
||||
"令牌端点": "權杖端點",
|
||||
"令牌管理": "權杖管理",
|
||||
"以下上游数据可能不可信:": "以下上游資料可能不可信:",
|
||||
"以下文件解析失败,已忽略:{{list}}": "以下檔案解析失敗,已忽略:{{list}}",
|
||||
"以及": "以及",
|
||||
|
|
@ -1136,7 +1135,7 @@
|
|||
"刷新缓存统计失败": "刷新快取統計失敗",
|
||||
"前往": "前往",
|
||||
"前往 io.net API Keys": "前往 io.net API Keys",
|
||||
"前往令牌管理": "前往權杖管理",
|
||||
"前往API Key": "前往權杖管理",
|
||||
"前往倍率设置": "前往倍率設定",
|
||||
"前往创建令牌": "前往建立權杖",
|
||||
"前往申请": "前往申請",
|
||||
|
|
@ -2306,8 +2305,8 @@
|
|||
"操作暂时被禁用": "操作暫時被禁用",
|
||||
"操作确认": "操作確認",
|
||||
"操作类型": "操作類型",
|
||||
"操练场": "操練場",
|
||||
"操练场和聊天功能": "操練場和聊天功能",
|
||||
"体验馆": "操練場",
|
||||
"体验馆和聊天功能": "操練場和聊天功能",
|
||||
"支付": "付款",
|
||||
"支付地址": "付款位址",
|
||||
"支付失败": "付款失敗",
|
||||
|
|
@ -5583,6 +5582,9 @@
|
|||
"沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment",
|
||||
"沙箱环境商户私钥": "Sandbox Merchant Private Key",
|
||||
"请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.",
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL"
|
||||
"可选,留空则使用默认地址": "Optional, leave blank to use default URL",
|
||||
"评测": "评测",
|
||||
"数据": "数据",
|
||||
"页面正在建设中...": "页面正在建设中..."
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,78 @@
|
|||
/* ==================== TokenDance Design System (shadcn/ui CSS Variables) ==================== */
|
||||
/* 这些变量叠加在 Semi Design 之上,供新的 shadcn 风格组件使用 */
|
||||
|
||||
:root {
|
||||
/* shadcn/ui 基础色板 — Slate 中性色调 (220° hue) */
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 220 5% 96%;
|
||||
--secondary-foreground: 220 6% 10%;
|
||||
--muted: 220 5% 96%;
|
||||
--muted-foreground: 220 4% 56%;
|
||||
--accent: 220 5% 96%;
|
||||
--accent-foreground: 220 6% 10%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 220 13% 91%;
|
||||
--input: 220 13% 91%;
|
||||
--ring: 0 0% 9%;
|
||||
--radius: 0.5rem;
|
||||
--link: 0 0% 9%;
|
||||
--link-hover: 0 0% 20%;
|
||||
--chart-1: 160 60% 45%;
|
||||
--chart-2: 217 91% 60%;
|
||||
--chart-3: 43 96% 56%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--secondary: 220 4% 16%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 220 4% 16%;
|
||||
--muted-foreground: 220 5% 65%;
|
||||
--accent: 220 4% 16%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 220 4% 16%;
|
||||
--input: 220 4% 16%;
|
||||
--ring: 222 56% 55%;
|
||||
--link: 222 56% 65%;
|
||||
--link-hover: 222 56% 55%;
|
||||
--chart-1: 160 60% 45%;
|
||||
--chart-2: 217 91% 60%;
|
||||
--chart-3: 43 96% 56%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
|
||||
/* 全局基础样式 — 使用新字体族 */
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans SC', 'Microsoft YaHei', sans-serif;
|
||||
color: hsl(var(--foreground));
|
||||
background-color: hsl(var(--background));
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* ==================== Tailwind CSS 配置 ==================== */
|
||||
|
||||
/* ==================== Tailwind CSS 閰嶇疆 ==================== */
|
||||
@layer tailwind-base, semi, tailwind-components, tailwind-utils;
|
||||
|
||||
@layer tailwind-base {
|
||||
|
|
@ -13,7 +87,7 @@
|
|||
@tailwind utilities;
|
||||
}
|
||||
|
||||
/* ==================== 全局基础样式 ==================== */
|
||||
/* ==================== 鍏ㄥ眬鍩虹鏍峰紡 ==================== */
|
||||
:root {
|
||||
--sidebar-width: 180px;
|
||||
--sidebar-width-collapsed: 60px;
|
||||
|
|
@ -24,14 +98,9 @@ body.sidebar-collapsed {
|
|||
--sidebar-current-width: var(--sidebar-width-collapsed);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family:
|
||||
Lato, 'Helvetica Neue', Arial, Helvetica, 'Microsoft YaHei', sans-serif;
|
||||
color: var(--semi-color-text-0);
|
||||
background-color: var(--semi-color-bg-0);
|
||||
}
|
||||
|
||||
/* 桌面端禁止 body 纵向滚动 - 防止 VChart tooltip 触发页面滚动条 */
|
||||
|
||||
/* 妗岄潰绔姝?body 绾靛悜婊氬姩 - 闃叉 VChart tooltip 瑙﹀彂椤甸潰婊氬姩鏉?*/
|
||||
@media (min-width: 768px) {
|
||||
body {
|
||||
overflow-y: hidden;
|
||||
|
|
@ -49,15 +118,13 @@ body {
|
|||
}
|
||||
|
||||
/*
|
||||
* 操练场 /console/playground:iOS Safari 点击后地址栏收起会使 100vh 变大,底部区域被「顶高」。
|
||||
* 使用 svh(小视口)为主高度,避免随 UI 变化跳变;无 svh 的浏览器回退 100vh。
|
||||
*/
|
||||
* 鎿嶇粌鍦?/console/playground锛歩OS Safari 鐐瑰嚮鍚庡湴鍧€鏍忔敹璧蜂細浣?100vh 鍙樺ぇ锛屽簳閮ㄥ尯鍩熻銆岄《楂樸€嶃€? * 浣跨敤 svh锛堝皬瑙嗗彛锛変负涓婚珮搴︼紝閬垮厤闅?UI 鍙樺寲璺冲彉锛涙棤 svh 鐨勬祻瑙堝櫒鍥為€€ 100vh銆? */
|
||||
.playground-shell-h {
|
||||
height: calc(100vh - 66px);
|
||||
height: calc(100svh - 66px);
|
||||
}
|
||||
|
||||
/* iOS:输入框字号小于 16px 时聚焦会强制缩放页面 */
|
||||
/* iOS锛氳緭鍏ユ瀛楀彿灏忎簬 16px 鏃惰仛鐒︿細寮哄埗缂╂斁椤甸潰 */
|
||||
@media (max-width: 767px) {
|
||||
.playground-shell-h textarea,
|
||||
.playground-shell-h input:not([type='checkbox']):not([type='radio']):not(
|
||||
|
|
@ -72,7 +139,7 @@ code {
|
|||
source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* ==================== 布局相关样式 ==================== */
|
||||
/* ==================== 甯冨眬鐩稿叧鏍峰紡 ==================== */
|
||||
.semi-layout::-webkit-scrollbar,
|
||||
.semi-layout-content::-webkit-scrollbar,
|
||||
.semi-sider::-webkit-scrollbar {
|
||||
|
|
@ -88,7 +155,7 @@ code {
|
|||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
/* ==================== 导航和侧边栏样式 ==================== */
|
||||
/* ==================== 瀵艰埅鍜屼晶杈规爮鏍峰紡 ==================== */
|
||||
.semi-navigation-item {
|
||||
margin-bottom: 4px !important;
|
||||
padding: 4px 12px !important;
|
||||
|
|
@ -138,7 +205,7 @@ code {
|
|||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
/* 自定义侧边栏样式 */
|
||||
/* 鑷畾涔変晶杈规爮鏍峰紡 */
|
||||
.sidebar-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
|
@ -166,7 +233,7 @@ code {
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* 侧边栏导航项样式 */
|
||||
/* 渚ц竟鏍忓鑸」鏍峰紡 */
|
||||
.sidebar-nav-item {
|
||||
border-radius: 6px;
|
||||
margin: 3px 8px;
|
||||
|
|
@ -185,7 +252,7 @@ code {
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 图标容器样式 */
|
||||
/* 鍥炬爣瀹瑰櫒鏍峰紡 */
|
||||
.sidebar-icon-container {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
|
|
@ -207,13 +274,13 @@ code {
|
|||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* 分割线样式 */
|
||||
/* 鍒嗗壊绾挎牱寮?*/
|
||||
.sidebar-divider {
|
||||
margin: 4px 8px;
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
/* 分组标签样式 */
|
||||
/* 鍒嗙粍鏍囩鏍峰紡 */
|
||||
.sidebar-group-label {
|
||||
padding: 4px 15px 8px;
|
||||
color: var(--semi-color-text-2);
|
||||
|
|
@ -223,7 +290,7 @@ code {
|
|||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 底部折叠按钮 */
|
||||
/* 搴曢儴鎶樺彔鎸夐挳 */
|
||||
.sidebar-collapse-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
|
@ -256,7 +323,7 @@ code {
|
|||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
/* 侧边栏区域容器 */
|
||||
/* 渚ц竟鏍忓尯鍩熷鍣?*/
|
||||
.sidebar-section {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
|
@ -277,7 +344,7 @@ code {
|
|||
}
|
||||
}
|
||||
|
||||
/* ==================== 聊天界面样式 ==================== */
|
||||
/* ==================== 鑱婂ぉ鐣岄潰鏍峰紡 ==================== */
|
||||
.semi-chat {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
|
|
@ -304,7 +371,7 @@ code {
|
|||
overflow-wrap: break-word !important;
|
||||
}
|
||||
|
||||
/* playground 聊天气泡宽度限制,避免媒体内容把外层容器撑满 */
|
||||
/* playground 鑱婂ぉ姘旀场瀹藉害闄愬埗锛岄伩鍏嶅獟浣撳唴瀹规妸澶栧眰瀹瑰櫒鎾戞弧 */
|
||||
.semi-chat .semi-chat-chatBox-content {
|
||||
max-width: min(100%, 780px) !important;
|
||||
}
|
||||
|
|
@ -336,7 +403,7 @@ code {
|
|||
font-size: 20px !important;
|
||||
}
|
||||
|
||||
/* 隐藏所有聊天相关区域的滚动条 */
|
||||
/* 闅愯棌鎵€鏈夎亰澶╃浉鍏冲尯鍩熺殑婊氬姩鏉?*/
|
||||
.semi-chat::-webkit-scrollbar,
|
||||
.semi-chat-chatBox::-webkit-scrollbar,
|
||||
.semi-chat-chatBox-wrap::-webkit-scrollbar,
|
||||
|
|
@ -358,7 +425,7 @@ code {
|
|||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
/* ==================== 组件特定样式 ==================== */
|
||||
/* ==================== 缁勪欢鐗瑰畾鏍峰紡 ==================== */
|
||||
/* SelectableButtonGroup */
|
||||
.sbg-button .semi-button-content {
|
||||
min-width: 0 !important;
|
||||
|
|
@ -478,7 +545,7 @@ html.dark .sbg-variant-green {
|
|||
--semi-color-primary-light-active: rgba(52, 211, 153, 0.3);
|
||||
}
|
||||
|
||||
/* Tabs组件样式 */
|
||||
/* Tabs缁勪欢鏍峰紡 */
|
||||
.semi-tabs-content {
|
||||
padding: 0 !important;
|
||||
height: calc(100% - 40px) !important;
|
||||
|
|
@ -494,7 +561,7 @@ html.dark .sbg-variant-green {
|
|||
height: 100% !important;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
/* 琛ㄦ牸鏍峰紡 */
|
||||
.tableShow {
|
||||
display: revert;
|
||||
}
|
||||
|
|
@ -503,7 +570,7 @@ html.dark .sbg-variant-green {
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
/* 页脚样式 */
|
||||
/* 椤佃剼鏍峰紡 */
|
||||
.custom-footer {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
|
@ -511,7 +578,7 @@ html.dark .sbg-variant-green {
|
|||
position: relative;
|
||||
}
|
||||
|
||||
/* 卡片内容容器通用样式 */
|
||||
/* 鍗$墖鍐呭瀹瑰櫒閫氱敤鏍峰紡 */
|
||||
.card-content-container {
|
||||
position: relative;
|
||||
}
|
||||
|
|
@ -529,7 +596,7 @@ html.dark .sbg-variant-green {
|
|||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
/* ==================== 调试面板特定样式 ==================== */
|
||||
/* ==================== 璋冭瘯闈㈡澘鐗瑰畾鏍峰紡 ==================== */
|
||||
.debug-panel .semi-tabs {
|
||||
height: 100% !important;
|
||||
display: flex !important;
|
||||
|
|
@ -545,8 +612,8 @@ html.dark .sbg-variant-green {
|
|||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* ==================== 滚动条样式统一管理 ==================== */
|
||||
/* 通用隐藏滚动条工具类 */
|
||||
/* ==================== 婊氬姩鏉℃牱寮忕粺涓€绠$悊 ==================== */
|
||||
/* 閫氱敤闅愯棌婊氬姩鏉″伐鍏风被 */
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
/* IE and Edge */
|
||||
|
|
@ -561,7 +628,7 @@ html.dark .sbg-variant-green {
|
|||
/* Chrome, Safari, Opera */
|
||||
}
|
||||
|
||||
/* 表格滚动条样式 */
|
||||
/* 琛ㄦ牸婊氬姩鏉℃牱寮?*/
|
||||
.semi-table-body::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
|
|
@ -580,7 +647,7 @@ html.dark .sbg-variant-green {
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
/* 侧边抽屉滚动条样式 */
|
||||
/* 渚ц竟鎶藉眽婊氬姩鏉℃牱寮?*/
|
||||
.semi-sidesheet-body::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
|
|
@ -599,7 +666,7 @@ html.dark .sbg-variant-green {
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
/* 隐藏内容区域滚动条 */
|
||||
/* 闅愯棌鍐呭鍖哄煙婊氬姩鏉?*/
|
||||
.pricing-scroll-hide,
|
||||
.model-test-scroll,
|
||||
.card-content-scroll,
|
||||
|
|
@ -623,7 +690,7 @@ html.dark .sbg-variant-green {
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* 图片列表滚动条样式 */
|
||||
/* 鍥剧墖鍒楄〃婊氬姩鏉℃牱寮?*/
|
||||
.image-list-scroll::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
|
|
@ -642,7 +709,7 @@ html.dark .sbg-variant-green {
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
/* ==================== 同步倍率 - 渠道选择器 ==================== */
|
||||
/* ==================== 鍚屾鍊嶇巼 - 娓犻亾閫夋嫨鍣?==================== */
|
||||
|
||||
.components-transfer-source-item,
|
||||
.components-transfer-selected-item {
|
||||
|
|
@ -710,7 +777,7 @@ html.dark .sbg-variant-green {
|
|||
color: var(--semi-color-text-0);
|
||||
}
|
||||
|
||||
/* ==================== 未读通知闪光效果 ==================== */
|
||||
/* ==================== 鏈閫氱煡闂厜鏁堟灉 ==================== */
|
||||
@keyframes sweep-shine {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
|
|
@ -752,7 +819,7 @@ html.dark .sbg-variant-green {
|
|||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
/* ==================== ScrollList 定制样式 ==================== */
|
||||
/* ==================== ScrollList 瀹氬埗鏍峰紡 ==================== */
|
||||
.semi-scrolllist,
|
||||
.semi-scrolllist * {
|
||||
-ms-overflow-style: none;
|
||||
|
|
@ -777,7 +844,7 @@ html.dark .sbg-variant-green {
|
|||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
/* ==================== Banner 背景模糊球 ==================== */
|
||||
/* ==================== Banner 鑳屾櫙妯$硦鐞?==================== */
|
||||
.blur-ball {
|
||||
position: absolute;
|
||||
width: 360px;
|
||||
|
|
@ -805,7 +872,7 @@ html.dark .sbg-variant-green {
|
|||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* 浅色主题下让模糊球更柔和 */
|
||||
/* 娴呰壊涓婚涓嬭妯$硦鐞冩洿鏌斿拰 */
|
||||
html:not(.dark) .blur-ball-indigo {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
|
@ -820,12 +887,12 @@ html:not(.dark) .blur-ball-teal {
|
|||
background: linear-gradient(180deg, #e8f0fe 0%, #ffffff 50%, #f8f9fa 100%);
|
||||
}
|
||||
|
||||
/* 浅色主题 - 清晰的渐变效果 */
|
||||
/* 娴呰壊涓婚 - 娓呮櫚鐨勬笎鍙樻晥鏋?*/
|
||||
html:not(.dark) .home-banner-bg {
|
||||
background: linear-gradient(180deg, #e8f0fe 0%, #ffffff 50%, #f8f9fa 100%);
|
||||
}
|
||||
|
||||
/* 暗色主题 */
|
||||
/* 鏆楄壊涓婚 */
|
||||
html.dark .home-banner-bg {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
|
|
@ -835,7 +902,7 @@ html.dark .home-banner-bg {
|
|||
);
|
||||
}
|
||||
|
||||
/* 首页「申请成为代理」:扁平主色 + 轻微外圈光(无厚投影/内高光) */
|
||||
/* 棣栭〉銆岀敵璇锋垚涓轰唬鐞嗐€嶏細鎵佸钩涓昏壊 + 杞诲井澶栧湀鍏夛紙鏃犲帤鎶曞奖/鍐呴珮鍏夛級 */
|
||||
@keyframes home-distributor-cta-glow {
|
||||
0%,
|
||||
100% {
|
||||
|
|
@ -883,7 +950,7 @@ html.dark .home-distributor-cta-btn {
|
|||
}
|
||||
}
|
||||
|
||||
/* 首页分销招募条:轻量入场 + 悬停高亮 */
|
||||
/* 棣栭〉鍒嗛攢鎷涘嫙鏉★細杞婚噺鍏ュ満 + 鎮仠楂樹寒 */
|
||||
@keyframes home-distributor-recruit-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
|
@ -944,7 +1011,7 @@ html.dark .home-distributor-cta-btn {
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 代理申请页:标题区背景光斑 + 文案入场 */
|
||||
/* 浠g悊鐢宠椤碉細鏍囬鍖鸿儗鏅厜鏂?+ 鏂囨鍏ュ満 */
|
||||
@keyframes distributor-apply-blob {
|
||||
0%,
|
||||
100% {
|
||||
|
|
@ -987,7 +1054,7 @@ html.dark .home-distributor-cta-btn {
|
|||
isolation: isolate;
|
||||
}
|
||||
|
||||
/* 代理申请主卡片:整体渐变底 + 淡边线 + 轻立体光(上沿高光 / 下沿浅影) */
|
||||
/* 浠g悊鐢宠涓诲崱鐗囷細鏁翠綋娓愬彉搴?+ 娣¤竟绾?+ 杞荤珛浣撳厜锛堜笂娌块珮鍏?/ 涓嬫部娴呭奖锛?*/
|
||||
.distributor-apply-main-card.semi-card {
|
||||
background: linear-gradient(
|
||||
152deg,
|
||||
|
|
@ -1016,7 +1083,7 @@ html.dark .home-distributor-cta-btn {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 分销申请 - 细滚动条(低对比、淡色):说明区独立滚动 或 窄屏整卡单列滚动 */
|
||||
/* 鍒嗛攢鐢宠 - 缁嗘粴鍔ㄦ潯锛堜綆瀵规瘮銆佹贰鑹诧級锛氳鏄庡尯鐙珛婊氬姩 鎴?绐勫睆鏁村崱鍗曞垪婊氬姩 */
|
||||
.distributor-apply-intro-scroll,
|
||||
.distributor-apply-card-stacked-scroll {
|
||||
scrollbar-gutter: stable;
|
||||
|
|
@ -1069,7 +1136,7 @@ html.dark .home-distributor-cta-btn {
|
|||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
/* 代理申请页:固定主区域、避免外层滚动,仅卡片内部滚动 */
|
||||
/* 浠g悊鐢宠椤碉細鍥哄畾涓诲尯鍩熴€侀伩鍏嶅灞傛粴鍔紝浠呭崱鐗囧唴閮ㄦ粴鍔?*/
|
||||
.distributor-apply-page-root {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
|
|
@ -1174,7 +1241,7 @@ html.dark .distributor-apply-hero .distributor-apply-hero-icon {
|
|||
) !important;
|
||||
}
|
||||
|
||||
/* 左/上:说明 与 右/下:表单 之间分隔线(高混入底色,比默认 border 更淡) */
|
||||
/* 宸?涓婏細璇存槑 涓?鍙?涓嬶細琛ㄥ崟 涔嬮棿鍒嗛殧绾匡紙楂樻贩鍏ュ簳鑹诧紝姣旈粯璁?border 鏇存贰锛?*/
|
||||
.distributor-apply-panels-divider {
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
|
|
@ -1214,20 +1281,20 @@ html.dark .distributor-apply-hero-orb--b {
|
|||
}
|
||||
}
|
||||
|
||||
/* ==================== 卡片马卡龙模糊球(类封装) ==================== */
|
||||
/* 使用方式:给容器加上 with-pastel-balls 类即可,无需在 JSX 中插入额外节点 */
|
||||
/* ==================== 鍗$墖椹崱榫欐ā绯婄悆锛堢被灏佽锛?==================== */
|
||||
/* 浣跨敤鏂瑰紡锛氱粰瀹瑰櫒鍔犱笂 with-pastel-balls 绫诲嵆鍙紝鏃犻渶鍦?JSX 涓彃鍏ラ澶栬妭鐐?*/
|
||||
.with-pastel-balls {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
/* 默认变量(明亮模式) */
|
||||
/* 榛樿鍙橀噺锛堟槑浜ā寮忥級 */
|
||||
--pb1: #ffd1dc;
|
||||
/* 粉 */
|
||||
/* 绮?*/
|
||||
--pb2: #e5d4ff;
|
||||
/* 薰衣草 */
|
||||
/* 钖拌。鑽?*/
|
||||
--pb3: #d1fff6;
|
||||
/* 薄荷 */
|
||||
/* 钖勮嵎 */
|
||||
--pb4: #ffe5d9;
|
||||
/* 桃 */
|
||||
/* 妗?*/
|
||||
--pb-opacity: 0.55;
|
||||
--pb-blur: 60px;
|
||||
}
|
||||
|
|
@ -1248,27 +1315,27 @@ html.dark .distributor-apply-hero-orb--b {
|
|||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
/* 暗黑模式下更柔和的色彩和透明度 */
|
||||
/* 鏆楅粦妯″紡涓嬫洿鏌斿拰鐨勮壊褰╁拰閫忔槑搴?*/
|
||||
html.dark .with-pastel-balls {
|
||||
/* 使用与明亮模式一致的“刚才那组”马卡龙色,但整体更柔和 */
|
||||
/* 浣跨敤涓庢槑浜ā寮忎竴鑷寸殑鈥滃垰鎵嶉偅缁勨€濋┈鍗¢緳鑹诧紝浣嗘暣浣撴洿鏌斿拰 */
|
||||
--pb1: #ffd1dc;
|
||||
/* 粉 */
|
||||
/* 绮?*/
|
||||
--pb2: #e5d4ff;
|
||||
/* 薰衣草 */
|
||||
/* 钖拌。鑽?*/
|
||||
--pb3: #d1fff6;
|
||||
/* 薄荷 */
|
||||
/* 钖勮嵎 */
|
||||
--pb4: #ffe5d9;
|
||||
/* 桃 */
|
||||
/* 妗?*/
|
||||
--pb-opacity: 0.36;
|
||||
--pb-blur: 65px;
|
||||
}
|
||||
|
||||
/* 暗黑模式下用更柔和的混合模式避免突兀的高亮 */
|
||||
/* 鏆楅粦妯″紡涓嬬敤鏇存煍鍜岀殑娣峰悎妯″紡閬垮厤绐佸厐鐨勯珮浜?*/
|
||||
html.dark .with-pastel-balls::before {
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
|
||||
/* ==================== 表格卡片滚动设置 ==================== */
|
||||
/* ==================== 琛ㄦ牸鍗$墖婊氬姩璁剧疆 ==================== */
|
||||
.table-scroll-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -1306,14 +1373,12 @@ html.dark .with-pastel-balls::before {
|
|||
}
|
||||
|
||||
/*
|
||||
* 代理管理页:Tabs 标签栏与下方 Card 的间距。
|
||||
* 全局 `.semi-tabs-content { padding: 0 !important; }` 会盖掉子元素 margin,此处用更高优先级覆盖上内边距。
|
||||
*/
|
||||
* 浠g悊绠$悊椤碉細Tabs 鏍囩鏍忎笌涓嬫柟 Card 鐨勯棿璺濄€? * 鍏ㄥ眬 `.semi-tabs-content { padding: 0 !important; }` 浼氱洊鎺夊瓙鍏冪礌 margin锛屾澶勭敤鏇撮珮浼樺厛绾ц鐩栦笂鍐呰竟璺濄€? */
|
||||
.distributor-admin-tabs.semi-tabs .semi-tabs-content {
|
||||
padding-top: 0.75rem !important;
|
||||
}
|
||||
|
||||
/* ==================== 模型定价页面布局 ==================== */
|
||||
/* ==================== 妯″瀷瀹氫环椤甸潰甯冨眬 ==================== */
|
||||
.pricing-layout {
|
||||
height: calc(100vh - 60px);
|
||||
overflow: hidden;
|
||||
|
|
@ -1367,13 +1432,13 @@ html.dark .with-pastel-balls::before {
|
|||
min-height: 0;
|
||||
}
|
||||
|
||||
/* ==================== semi-ui 组件自定义样式 ==================== */
|
||||
/* ==================== semi-ui 缁勪欢鑷畾涔夋牱寮?==================== */
|
||||
.semi-card-header,
|
||||
.semi-card-body {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
/* ==================== 使用日志: channel affinity tag ==================== */
|
||||
/* ==================== 浣跨敤鏃ュ織: channel affinity tag ==================== */
|
||||
.semi-tag.channel-affinity-tag {
|
||||
border: 1px solid rgba(var(--semi-cyan-5), 0.35);
|
||||
background-color: rgba(var(--semi-cyan-5), 0.15);
|
||||
|
|
@ -1401,7 +1466,7 @@ html.dark .with-pastel-balls::before {
|
|||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
/* ==================== 自定义圆角样式 ==================== */
|
||||
/* ==================== 鑷畾涔夊渾瑙掓牱寮?==================== */
|
||||
.semi-radio,
|
||||
.semi-tagInput,
|
||||
.semi-input-textarea-wrapper,
|
||||
|
|
@ -1444,3 +1509,54 @@ html.dark .with-pastel-balls::before {
|
|||
.ec-dbcd0a3c01b55203 {
|
||||
forced-color-adjust: auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* ==================== TokenDance Home Page Styles ==================== */
|
||||
.home-page {
|
||||
font-family: "Source Serif 4", "Noto Serif SC", Georgia, serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.home-page ::selection {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@keyframes homeFadeIn {
|
||||
0% { opacity: 0; transform: translateY(40px); }
|
||||
100% { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.home-fade-in {
|
||||
animation: homeFadeIn 0.6s ease-out both;
|
||||
}
|
||||
|
||||
.home-fade-in-delay-1 {
|
||||
animation: homeFadeIn 0.6s ease-out 0.2s both;
|
||||
}
|
||||
|
||||
.home-fade-in-delay-2 {
|
||||
animation: homeFadeIn 0.6s ease-out 0.4s both;
|
||||
}
|
||||
|
||||
@keyframes marquee {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
.home-marquee {
|
||||
animation: marquee 20s linear infinite;
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.font-heading {
|
||||
font-family: "Source Serif 4", "Noto Serif SC", Georgia, serif;
|
||||
}
|
||||
|
||||
.font-body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,422 @@
|
|||
/*
|
||||
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.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { API } from '../../helpers';
|
||||
import {
|
||||
Brain,
|
||||
Zap,
|
||||
DollarSign,
|
||||
Clock,
|
||||
Maximize2,
|
||||
Search,
|
||||
ArrowUpDown,
|
||||
Trophy,
|
||||
TrendingUp,
|
||||
Gauge,
|
||||
Sparkles,
|
||||
} from 'lucide-react';
|
||||
|
||||
const COLORS = {
|
||||
intelligence: '#8B5CF6',
|
||||
speed: '#06B6D4',
|
||||
price: '#10B981',
|
||||
latency: '#F59E0B',
|
||||
context: '#EC4899',
|
||||
};
|
||||
|
||||
const CATEGORIES = [
|
||||
{ key: 'all', label: '全部', icon: Sparkles },
|
||||
{ key: 'language', label: '语言模型', icon: Brain },
|
||||
{ key: 'image', label: '图像模型', icon: Zap },
|
||||
{ key: 'video', label: '视频模型', icon: Zap },
|
||||
];
|
||||
|
||||
const Benchmarks = () => {
|
||||
const { t } = useTranslation();
|
||||
const [models, setModels] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [sortKey, setSortKey] = useState('intelligence');
|
||||
const [sortDesc, setSortDesc] = useState(true);
|
||||
const [activeCategory, setActiveCategory] = useState('all');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const res = await API.get('/api/option/');
|
||||
if (res.data.success && res.data.data) {
|
||||
const option = res.data.data.find(
|
||||
(o) => o.key === 'benchmark_data.models',
|
||||
);
|
||||
if (option && option.value) {
|
||||
setModels(JSON.parse(option.value));
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// fallback: use embeded default data
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const filteredModels = useMemo(() => {
|
||||
let list = [...models];
|
||||
if (activeCategory !== 'all') {
|
||||
list = list.filter((m) => m.category === activeCategory);
|
||||
}
|
||||
if (searchTerm) {
|
||||
const term = searchTerm.toLowerCase();
|
||||
list = list.filter(
|
||||
(m) =>
|
||||
m.name?.toLowerCase().includes(term) ||
|
||||
m.provider?.toLowerCase().includes(term),
|
||||
);
|
||||
}
|
||||
list.sort((a, b) => {
|
||||
const aVal = a[sortKey] ?? 0;
|
||||
const bVal = b[sortKey] ?? 0;
|
||||
if (typeof aVal === 'string') return sortDesc ? bVal.localeCompare(aVal) : aVal.localeCompare(bVal);
|
||||
return sortDesc ? bVal - aVal : aVal - bVal;
|
||||
});
|
||||
return list;
|
||||
}, [models, searchTerm, sortKey, sortDesc, activeCategory]);
|
||||
|
||||
const toggleSort = (key) => {
|
||||
if (sortKey === key) {
|
||||
setSortDesc(!sortDesc);
|
||||
} else {
|
||||
setSortKey(key);
|
||||
setSortDesc(true);
|
||||
}
|
||||
};
|
||||
|
||||
const topIntel = useMemo(
|
||||
() => [...models].sort((a, b) => b.intelligence - a.intelligence).slice(0, 3),
|
||||
[models],
|
||||
);
|
||||
const fastest = useMemo(
|
||||
() => [...models].sort((a, b) => b.speed - a.speed)[0],
|
||||
[models],
|
||||
);
|
||||
const cheapest = useMemo(
|
||||
() => [...models].sort((a, b) => a.price - b.price)[0],
|
||||
[models],
|
||||
);
|
||||
const lowestLatency = useMemo(
|
||||
() => [...models].sort((a, b) => a.latency - b.latency)[0],
|
||||
[models],
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-950 flex items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-white/30" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const SortHeader = ({ label, sortField, icon: Icon }) => (
|
||||
<th
|
||||
className="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider cursor-pointer hover:text-white/70 transition-colors select-none"
|
||||
onClick={() => toggleSort(sortField)}
|
||||
>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{Icon && <Icon size={13} className="text-gray-500" />}
|
||||
<span>{label}</span>
|
||||
{sortKey === sortField && (
|
||||
<ArrowUpDown
|
||||
size={12}
|
||||
className={`transition-transform ${sortDesc ? '' : 'rotate-180'}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-950 text-white">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 sm:py-12">
|
||||
{/* Header */}
|
||||
<div className="mb-8 sm:mb-12">
|
||||
<h1 className="text-3xl sm:text-4xl font-bold font-heading mb-3">
|
||||
{t('AI 模型评测排行榜')}
|
||||
</h1>
|
||||
<p className="text-gray-400 text-sm sm:text-base font-body max-w-2xl">
|
||||
{t('基于 Artificial Analysis 数据,从智能指数、输出速度、价格成本、延迟等维度对主流 AI 模型进行多维度对比评测')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Highlight Cards */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4 mb-8 sm:mb-10">
|
||||
{topIntel[0] && (
|
||||
<div className="bg-gradient-to-br from-purple-900/30 to-purple-950/30 border border-purple-800/30 rounded-xl p-4 sm:p-5">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Trophy size={18} className="text-purple-400" />
|
||||
<span className="text-xs font-semibold text-purple-300 uppercase tracking-wider">
|
||||
{t('智能冠军')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-lg sm:text-xl font-bold text-white mb-1">
|
||||
{topIntel[0].name}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400 mb-2">{topIntel[0].provider}</div>
|
||||
<div className="flex items-baseline gap-1">
|
||||
<span className="text-2xl sm:text-3xl font-bold text-purple-400">
|
||||
{topIntel[0].intelligence}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">/ 60</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{fastest && (
|
||||
<div className="bg-gradient-to-br from-cyan-900/30 to-cyan-950/30 border border-cyan-800/30 rounded-xl p-4 sm:p-5">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Gauge size={18} className="text-cyan-400" />
|
||||
<span className="text-xs font-semibold text-cyan-300 uppercase tracking-wider">
|
||||
{t('速度之王')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-lg sm:text-xl font-bold text-white mb-1">
|
||||
{fastest.name}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400 mb-2">{fastest.provider}</div>
|
||||
<div className="flex items-baseline gap-1">
|
||||
<span className="text-2xl sm:text-3xl font-bold text-cyan-400">
|
||||
{fastest.speed?.toLocaleString()}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">tok/s</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{cheapest && (
|
||||
<div className="bg-gradient-to-br from-emerald-900/30 to-emerald-950/30 border border-emerald-800/30 rounded-xl p-4 sm:p-5">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<DollarSign size={18} className="text-emerald-400" />
|
||||
<span className="text-xs font-semibold text-emerald-300 uppercase tracking-wider">
|
||||
{t('性价比首选')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-lg sm:text-xl font-bold text-white mb-1">
|
||||
{cheapest.name}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400 mb-2">{cheapest.provider}</div>
|
||||
<div className="flex items-baseline gap-1">
|
||||
<span className="text-2xl sm:text-3xl font-bold text-emerald-400">
|
||||
${cheapest.price?.toFixed(2)}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">/M tokens</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{lowestLatency && (
|
||||
<div className="bg-gradient-to-br from-amber-900/30 to-amber-950/30 border border-amber-800/30 rounded-xl p-4 sm:p-5">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Clock size={18} className="text-amber-400" />
|
||||
<span className="text-xs font-semibold text-amber-300 uppercase tracking-wider">
|
||||
{t('低延迟标杆')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-lg sm:text-xl font-bold text-white mb-1">
|
||||
{lowestLatency.name}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400 mb-2">{lowestLatency.provider}</div>
|
||||
<div className="flex items-baseline gap-1">
|
||||
<span className="text-2xl sm:text-3xl font-bold text-amber-400">
|
||||
{lowestLatency.latency?.toFixed(2)}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">{t('秒')}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Filters and Search */}
|
||||
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4 mb-6 items-start sm:items-center justify-between">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{CATEGORIES.map((cat) => {
|
||||
const Icon = cat.icon;
|
||||
const isActive = activeCategory === cat.key;
|
||||
return (
|
||||
<button
|
||||
key={cat.key}
|
||||
onClick={() => setActiveCategory(cat.key)}
|
||||
className={`flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg text-xs font-medium transition-all duration-150 ${
|
||||
isActive
|
||||
? 'bg-white/10 text-white border border-white/20'
|
||||
: 'bg-white/5 text-gray-400 border border-transparent hover:bg-white/10 hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
<Icon size={13} />
|
||||
<span>{t(cat.label)}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="relative w-full sm:w-64">
|
||||
<Search
|
||||
size={15}
|
||||
className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={t('搜索模型名称或供应商...')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full bg-white/5 border border-white/10 rounded-lg pl-9 pr-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-white/30 focus:bg-white/10 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Model Count */}
|
||||
<div className="text-xs text-gray-500 mb-3">
|
||||
{t('共 {count} 个模型', { count: filteredModels.length })}
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div className="overflow-x-auto rounded-xl border border-white/10">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-white/5 border-b border-white/10">
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider w-10">
|
||||
#
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Brain size={13} className="text-gray-500" />
|
||||
<span>{t('模型名称')}</span>
|
||||
</div>
|
||||
</th>
|
||||
<SortHeader
|
||||
label="智能指数"
|
||||
sortField="intelligence"
|
||||
icon={TrendingUp}
|
||||
/>
|
||||
<SortHeader label="速度" sortField="speed" icon={Zap} />
|
||||
<SortHeader
|
||||
label="价格"
|
||||
sortField="price"
|
||||
icon={DollarSign}
|
||||
/>
|
||||
<SortHeader label="延迟" sortField="latency" icon={Clock} />
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Maximize2 size={13} className="text-gray-500" />
|
||||
<span>{t('上下文')}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th className="px-4 py-3 text-center text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
||||
{t('类型')}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-white/5">
|
||||
{filteredModels.map((model, idx) => (
|
||||
<tr
|
||||
key={model.name + model.provider}
|
||||
className="hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<td className="px-4 py-3.5 text-gray-500 text-xs font-mono">
|
||||
{idx + 1}
|
||||
</td>
|
||||
<td className="px-4 py-3.5">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium text-white text-sm">
|
||||
{model.name}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
{model.provider}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-20 sm:w-24 h-2 bg-white/10 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full transition-all"
|
||||
style={{
|
||||
width: `${(model.intelligence / 60) * 100}%`,
|
||||
backgroundColor: COLORS.intelligence,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xs font-mono text-gray-300 w-8 text-right">
|
||||
{model.intelligence}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-16 sm:w-20 h-2 bg-white/10 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full transition-all"
|
||||
style={{
|
||||
width: `${Math.min((model.speed / 900) * 100, 100)}%`,
|
||||
backgroundColor: COLORS.speed,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xs font-mono text-gray-300 whitespace-nowrap">
|
||||
{model.speed?.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3.5">
|
||||
<span className="text-xs font-mono text-gray-300">
|
||||
${model.price?.toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3.5">
|
||||
<span className="text-xs font-mono text-gray-300">
|
||||
{model.latency?.toFixed(2)}s
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3.5">
|
||||
<span className="text-xs text-gray-400">
|
||||
{model.context_window || '-'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3.5 text-center">
|
||||
<span
|
||||
className={`inline-block px-2 py-0.5 rounded text-[10px] font-medium ${
|
||||
model.type === 'reasoning'
|
||||
? 'bg-purple-900/50 text-purple-300 border border-purple-700/50'
|
||||
: 'bg-blue-900/50 text-blue-300 border border-blue-700/50'
|
||||
}`}
|
||||
>
|
||||
{model.type === 'reasoning'
|
||||
? t('推理')
|
||||
: t('通用')}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{filteredModels.length === 0 && (
|
||||
<div className="text-center py-12 text-gray-500 text-sm">
|
||||
{t('暂未找到匹配的模型')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="mt-6 text-center text-xs text-gray-600">
|
||||
{t('数据来源: Artificial Analysis | 数据持续更新中')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Benchmarks;
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -45,14 +45,14 @@ export default function SettingsHeaderNavModules(props) {
|
|||
blurPricing: false,
|
||||
showCostPrice: false,
|
||||
},
|
||||
console: true,
|
||||
pricing: {
|
||||
enabled: true,
|
||||
requireAuth: false,
|
||||
blurPricing: false,
|
||||
},
|
||||
console: true,
|
||||
benchmark: true,
|
||||
docs: true,
|
||||
about: true,
|
||||
});
|
||||
|
||||
// 处理顶栏模块配置变更
|
||||
|
|
@ -175,14 +175,14 @@ export default function SettingsHeaderNavModules(props) {
|
|||
blurPricing: false,
|
||||
showCostPrice: false,
|
||||
},
|
||||
console: true,
|
||||
pricing: {
|
||||
enabled: true,
|
||||
requireAuth: false,
|
||||
blurPricing: false,
|
||||
},
|
||||
console: true,
|
||||
benchmark: true,
|
||||
docs: true,
|
||||
about: true,
|
||||
};
|
||||
setHeaderNavModules(defaultModules);
|
||||
showSuccess(t('已重置为默认配置'));
|
||||
|
|
@ -267,14 +267,14 @@ export default function SettingsHeaderNavModules(props) {
|
|||
blurPricing: false,
|
||||
showCostPrice: false,
|
||||
},
|
||||
console: true,
|
||||
pricing: {
|
||||
enabled: true,
|
||||
requireAuth: false,
|
||||
blurPricing: false,
|
||||
},
|
||||
console: true,
|
||||
benchmark: true,
|
||||
docs: true,
|
||||
about: true,
|
||||
};
|
||||
setHeaderNavModules(defaultModules);
|
||||
}
|
||||
|
|
@ -299,28 +299,28 @@ export default function SettingsHeaderNavModules(props) {
|
|||
hasBlurPricing: true,
|
||||
hasCostPrice: true,
|
||||
},
|
||||
{
|
||||
key: 'console',
|
||||
title: t('控制台'),
|
||||
description: t('用户控制面板,管理账户'),
|
||||
},
|
||||
{
|
||||
key: 'pricing',
|
||||
title: t('模型广场'),
|
||||
title: t('模型'),
|
||||
description: t('模型定价,需要登录访问'),
|
||||
hasSubConfig: true,
|
||||
hasBlurPricing: true,
|
||||
},
|
||||
{
|
||||
key: 'console',
|
||||
title: t('控制台'),
|
||||
description: t('用户控制面板,管理账户'),
|
||||
},
|
||||
{
|
||||
key: 'benchmark',
|
||||
title: t('评测'),
|
||||
description: t('AI 模型评测与排行'),
|
||||
},
|
||||
{
|
||||
key: 'docs',
|
||||
title: t('文档'),
|
||||
description: t('系统文档和帮助信息'),
|
||||
},
|
||||
{
|
||||
key: 'about',
|
||||
title: t('关于'),
|
||||
description: t('关于系统的详细信息'),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -41,11 +41,11 @@ const sectionConfigs = [
|
|||
{
|
||||
key: 'chat',
|
||||
title_key: '聊天区域',
|
||||
desc_key: '操练场和聊天功能',
|
||||
desc_key: '体验馆和聊天功能',
|
||||
modules: [
|
||||
{
|
||||
key: 'playground',
|
||||
title_key: '操练场',
|
||||
title_key: '体验馆',
|
||||
desc_key: 'AI模型测试环境',
|
||||
},
|
||||
{ key: 'chat', title_key: '聊天', desc_key: '聊天会话管理' },
|
||||
|
|
@ -56,8 +56,9 @@ const sectionConfigs = [
|
|||
title_key: '控制台区域',
|
||||
desc_key: '数据管理和日志查看',
|
||||
modules: [
|
||||
{ key: 'benchmarks', title_key: '评测', desc_key: 'AI 模型评测排行' },
|
||||
{ key: 'detail', title_key: '数据看板', desc_key: '系统数据统计' },
|
||||
{ key: 'token', title_key: '令牌管理', desc_key: 'API令牌管理' },
|
||||
{ key: 'token', title_key: 'API Key', desc_key: 'APIAPI Key' },
|
||||
{ key: 'log', title_key: '使用日志', desc_key: 'API使用记录' },
|
||||
{
|
||||
key: 'midjourney',
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import {
|
|||
Server,
|
||||
Activity,
|
||||
Shield,
|
||||
BarChart3,
|
||||
} from 'lucide-react';
|
||||
|
||||
import SystemSetting from '../../components/settings/SystemSetting';
|
||||
|
|
@ -51,6 +52,7 @@ import PaymentSetting from '../../components/settings/PaymentSetting';
|
|||
import ModelDeploymentSetting from '../../components/settings/ModelDeploymentSetting';
|
||||
import PerformanceSetting from '../../components/settings/PerformanceSetting';
|
||||
import ApiRateLimitSetting from '../../components/settings/ApiRateLimitSetting';
|
||||
import BenchmarkDataManager from '../../components/settings/BenchmarkDataManager';
|
||||
|
||||
const Setting = () => {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -110,6 +112,16 @@ const Setting = () => {
|
|||
content: <PaymentSetting />,
|
||||
itemKey: 'payment',
|
||||
});
|
||||
panes.push({
|
||||
tab: (
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||||
<BarChart3 size={18} />
|
||||
{t('评测数据管理')}
|
||||
</span>
|
||||
),
|
||||
content: <BenchmarkDataManager />,
|
||||
itemKey: 'benchmark',
|
||||
});
|
||||
panes.push({
|
||||
tab: (
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -17,42 +17,69 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
/** 与 ThemeProvider 一致:实际主题由 document.documentElement 的 `dark` 类控制,不能用默认的 media 策略 */
|
||||
/** `darkMode: 'class'` 与 ThemeProvider 一致:实际主题由 document.documentElement 的 `dark` 类控制 */
|
||||
export default {
|
||||
darkMode: 'class',
|
||||
content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'],
|
||||
theme: {
|
||||
colors: {
|
||||
transparent: 'transparent',
|
||||
current: 'currentColor',
|
||||
black: '#000',
|
||||
white: '#fff',
|
||||
// ---- shadcn/ui CSS variable tokens (TokenDance 风格) ----
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
card: 'hsl(var(--card))',
|
||||
'card-foreground': 'hsl(var(--card-foreground))',
|
||||
popover: 'hsl(var(--popover))',
|
||||
'popover-foreground': 'hsl(var(--popover-foreground))',
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))',
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))',
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))',
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
},
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
// ---- Semi Design CSS variable tokens (向后兼容) ----
|
||||
'semi-color-white': 'var(--semi-color-white)',
|
||||
'semi-color-black': 'var(--semi-color-black)',
|
||||
'semi-color-primary': 'var(--semi-color-primary)',
|
||||
'semi-color-primary-hover': 'var(--semi-color-primary-hover)',
|
||||
'semi-color-primary-active': 'var(--semi-color-primary-active)',
|
||||
'semi-color-primary-disabled': 'var(--semi-color-primary-disabled)',
|
||||
'semi-color-primary-light-default':
|
||||
'var(--semi-color-primary-light-default)',
|
||||
'semi-color-primary-light-default': 'var(--semi-color-primary-light-default)',
|
||||
'semi-color-primary-light-hover': 'var(--semi-color-primary-light-hover)',
|
||||
'semi-color-primary-light-active':
|
||||
'var(--semi-color-primary-light-active)',
|
||||
'semi-color-primary-light-active': 'var(--semi-color-primary-light-active)',
|
||||
'semi-color-secondary': 'var(--semi-color-secondary)',
|
||||
'semi-color-secondary-hover': 'var(--semi-color-secondary-hover)',
|
||||
'semi-color-secondary-active': 'var(--semi-color-secondary-active)',
|
||||
'semi-color-secondary-disabled': 'var(--semi-color-secondary-disabled)',
|
||||
'semi-color-secondary-light-default':
|
||||
'var(--semi-color-secondary-light-default)',
|
||||
'semi-color-secondary-light-hover':
|
||||
'var(--semi-color-secondary-light-hover)',
|
||||
'semi-color-secondary-light-active':
|
||||
'var(--semi-color-secondary-light-active)',
|
||||
'semi-color-secondary-light-default': 'var(--semi-color-secondary-light-default)',
|
||||
'semi-color-secondary-light-hover': 'var(--semi-color-secondary-light-hover)',
|
||||
'semi-color-secondary-light-active': 'var(--semi-color-secondary-light-active)',
|
||||
'semi-color-tertiary': 'var(--semi-color-tertiary)',
|
||||
'semi-color-tertiary-hover': 'var(--semi-color-tertiary-hover)',
|
||||
'semi-color-tertiary-active': 'var(--semi-color-tertiary-active)',
|
||||
'semi-color-tertiary-light-default':
|
||||
'var(--semi-color-tertiary-light-default)',
|
||||
'semi-color-tertiary-light-hover':
|
||||
'var(--semi-color-tertiary-light-hover)',
|
||||
'semi-color-tertiary-light-active':
|
||||
'var(--semi-color-tertiary-light-active)',
|
||||
'semi-color-tertiary-light-default': 'var(--semi-color-tertiary-light-default)',
|
||||
'semi-color-tertiary-light-hover': 'var(--semi-color-tertiary-light-hover)',
|
||||
'semi-color-tertiary-light-active': 'var(--semi-color-tertiary-light-active)',
|
||||
'semi-color-default': 'var(--semi-color-default)',
|
||||
'semi-color-default-hover': 'var(--semi-color-default-hover)',
|
||||
'semi-color-default-active': 'var(--semi-color-default-active)',
|
||||
|
|
@ -67,26 +94,21 @@ export default {
|
|||
'semi-color-success-hover': 'var(--semi-color-success-hover)',
|
||||
'semi-color-success-active': 'var(--semi-color-success-active)',
|
||||
'semi-color-success-disabled': 'var(--semi-color-success-disabled)',
|
||||
'semi-color-success-light-default':
|
||||
'var(--semi-color-success-light-default)',
|
||||
'semi-color-success-light-default': 'var(--semi-color-success-light-default)',
|
||||
'semi-color-success-light-hover': 'var(--semi-color-success-light-hover)',
|
||||
'semi-color-success-light-active':
|
||||
'var(--semi-color-success-light-active)',
|
||||
'semi-color-success-light-active': 'var(--semi-color-success-light-active)',
|
||||
'semi-color-danger': 'var(--semi-color-danger)',
|
||||
'semi-color-danger-hover': 'var(--semi-color-danger-hover)',
|
||||
'semi-color-danger-active': 'var(--semi-color-danger-active)',
|
||||
'semi-color-danger-light-default':
|
||||
'var(--semi-color-danger-light-default)',
|
||||
'semi-color-danger-light-default': 'var(--semi-color-danger-light-default)',
|
||||
'semi-color-danger-light-hover': 'var(--semi-color-danger-light-hover)',
|
||||
'semi-color-danger-light-active': 'var(--semi-color-danger-light-active)',
|
||||
'semi-color-warning': 'var(--semi-color-warning)',
|
||||
'semi-color-warning-hover': 'var(--semi-color-warning-hover)',
|
||||
'semi-color-warning-active': 'var(--semi-color-warning-active)',
|
||||
'semi-color-warning-light-default':
|
||||
'var(--semi-color-warning-light-default)',
|
||||
'semi-color-warning-light-default': 'var(--semi-color-warning-light-default)',
|
||||
'semi-color-warning-light-hover': 'var(--semi-color-warning-light-hover)',
|
||||
'semi-color-warning-light-active':
|
||||
'var(--semi-color-warning-light-active)',
|
||||
'semi-color-warning-light-active': 'var(--semi-color-warning-light-active)',
|
||||
'semi-color-focus-border': 'var(--semi-color-focus-border)',
|
||||
'semi-color-disabled-text': 'var(--semi-color-disabled-text)',
|
||||
'semi-color-disabled-border': 'var(--semi-color-disabled-border)',
|
||||
|
|
@ -136,16 +158,73 @@ export default {
|
|||
'semi-color-data-19': 'var(--semi-color-data-19)',
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
heading: ['"Source Serif 4"', '"Noto Serif SC"', 'Georgia', 'serif'],
|
||||
serif: ['"Playfair Display"', '"Noto Serif SC"', 'serif'],
|
||||
mono: ['"JetBrains Mono"', 'monospace'],
|
||||
display: ['"Bebas Neue"', 'sans-serif'],
|
||||
body: ['system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', 'sans-serif'],
|
||||
},
|
||||
borderRadius: {
|
||||
'semi-border-radius-extra-small':
|
||||
'var(--semi-border-radius-extra-small)',
|
||||
'semi-border-radius-extra-small': 'var(--semi-border-radius-extra-small)',
|
||||
'semi-border-radius-small': 'var(--semi-border-radius-small)',
|
||||
'semi-border-radius-medium': 'var(--semi-border-radius-medium)',
|
||||
'semi-border-radius-large': 'var(--semi-border-radius-large)',
|
||||
'semi-border-radius-circle': 'var(--semi-border-radius-circle)',
|
||||
'semi-border-radius-full': 'var(--semi-border-radius-full)',
|
||||
// shadcn/ui radius token
|
||||
DEFAULT: 'var(--radius)',
|
||||
sm: 'calc(var(--radius) - 2px)',
|
||||
md: 'calc(var(--radius) + 2px)',
|
||||
lg: 'calc(var(--radius) + 4px)',
|
||||
xl: 'calc(var(--radius) + 8px)',
|
||||
},
|
||||
borderColor: {
|
||||
DEFAULT: 'hsl(var(--border))',
|
||||
},
|
||||
ringColor: {
|
||||
DEFAULT: 'hsl(var(--ring))',
|
||||
},
|
||||
ringOffsetColor: {
|
||||
DEFAULT: 'hsl(var(--background))',
|
||||
},
|
||||
backgroundColor: {
|
||||
DEFAULT: 'hsl(var(--background))',
|
||||
},
|
||||
textColor: {
|
||||
DEFAULT: 'hsl(var(--foreground))',
|
||||
},
|
||||
boxShadow: {
|
||||
'td-sm': '0 1px 2px 0 rgb(0 0 0 / 0.05)',
|
||||
'td-md': '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
|
||||
'td-lg': '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
|
||||
},
|
||||
animation: {
|
||||
'fade-in': 'fadeIn 0.2s ease-in-out',
|
||||
'slide-up': 'slideUp 0.2s ease-out',
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
'0%': { opacity: '0' },
|
||||
'100%': { opacity: '1' },
|
||||
},
|
||||
slideUp: {
|
||||
'0%': { opacity: '0', transform: 'translateY(8px)' },
|
||||
'100%': { opacity: '1', transform: 'translateY(0)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
safelist: [
|
||||
'home-fade-in',
|
||||
'home-fade-in-delay-1',
|
||||
'home-fade-in-delay-2',
|
||||
'home-marquee',
|
||||
'font-heading',
|
||||
'font-body',
|
||||
'home-page',
|
||||
],
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue