diff --git a/web/deploy.py b/web/deploy.py index 8642189..c79de17 100644 --- a/web/deploy.py +++ b/web/deploy.py @@ -5,7 +5,7 @@ import sys host = "26.0.12.13" user = "root" password = "root@1234" -local_dist = r"C:\Users\wangxj\tokenFactory\web\dist" +local_dist = r"D:\tuling_new\web\dist" remote_dist = "/root/tokenFactory/web/dist" print("Connecting...") diff --git a/web/src/App.jsx b/web/src/App.jsx index 8743d54..f59e05d 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -95,6 +95,11 @@ import LuckyBag from './pages/LuckyBag'; import Drawing from './pages/Drawing'; import ProfitDashboard from './pages/ProfitDashboard'; import SettingsAntiAbuse from './pages/Setting/Operation/SettingsAntiAbuse'; +import Contact from './pages/Contact'; +import About from './pages/About'; +import UserAgreement from './pages/UserAgreement'; +import PrivacyPolicy from './pages/PrivacyPolicy'; +import Docs from './pages/Docs'; const Home = lazy(() => import('./pages/Home')); const Dashboard = lazy(() => import('./pages/Dashboard')); @@ -102,10 +107,6 @@ 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')); -const Docs = lazy(() => import('./pages/Docs')); function DynamicOAuth2Callback() { const { provider } = useParams(); @@ -545,6 +546,14 @@ function App() { } /> + } key={location.pathname}> + + + } + /> { let navigate = useNavigate(); const { t } = useTranslation(); @@ -576,426 +576,493 @@ const LoginForm = () => { }; const renderOAuthOptions = () => { + const OauthButton = ({ icon, children, onClick, loading, disabled, brandColor }) => ( + + ); + return ( -
-
-
- Logo - - {systemName} - +
+
+

+ {t('欢迎回来')} +

+

+ {t('选择登录方式继续')} +

+ +
+ {status.wechat_login && ( + } + onClick={onWeChatLoginClicked} + loading={wechatLoading} + > + {t('使用 微信 继续')} + + )} + + {status.github_oauth && ( + } + onClick={handleGitHubClick} + loading={githubLoading} + disabled={githubButtonDisabled} + > + {githubButtonText} + + )} + + {status.discord_oauth && ( + + } + onClick={handleDiscordClick} + loading={discordLoading} + > + {t('使用 Discord 继续')} + + )} + + {status.oidc_enabled && ( + } + onClick={handleOIDCClick} + loading={oidcLoading} + > + {t('使用 OIDC 继续')} + + )} + + {status.linuxdo_oauth && ( + + } + onClick={handleLinuxDOClick} + loading={linuxdoLoading} + > + {t('使用 LinuxDO 继续')} + + )} + + {status.custom_oauth_providers && + status.custom_oauth_providers.map((provider) => ( + handleCustomOAuthClick(provider)} + loading={customOAuthLoading[provider.slug]} + > + {t('使用 {{name}} 继续', { name: provider.name })} + + ))} + + {status.telegram_oauth && ( +
+ +
+ )} + + {status.passkey_login && passkeySupported && ( + } + onClick={handlePasskeyLogin} + loading={passkeyLoading} + > + {t('使用 Passkey 登录')} + + )}
- -
- - {t('登 录')} - -
-
-
- {status.wechat_login && ( - - )} +
+
+ + {t('或')} + +
+
- {status.github_oauth && ( - - )} + - {status.discord_oauth && ( - - )} - - {status.oidc_enabled && ( - - )} - - {status.linuxdo_oauth && ( - - )} - - {status.custom_oauth_providers && - status.custom_oauth_providers.map((provider) => ( - - ))} - - {status.telegram_oauth && ( -
- -
- )} - - {status.passkey_login && passkeySupported && ( - - )} - - - {t('或')} - - - -
- - {(hasUserAgreement || hasPrivacyPolicy) && ( -
- setAgreedToTerms(e.target.checked)} + {agreedToTerms && ( + + + + )} + + + + {t('我已阅读并同意')} + {hasUserAgreement && ( + - - {t('我已阅读并同意')} - {hasUserAgreement && ( - <> - - {t('用户协议')} - - - )} - {hasUserAgreement && hasPrivacyPolicy && t('和')} - {hasPrivacyPolicy && ( - <> - - {t('隐私政策')} - - - )} - - -
- )} - - {!status.self_use_mode_enabled && ( -
- - {t('没有账户?')}{' '} - - {t('注册')} - - -
- )} -
- + {t('用户协议')} + + )} + {hasUserAgreement && hasPrivacyPolicy && t('和')} + {hasPrivacyPolicy && ( + + {t('隐私政策')} + + )} + + + )}
+ + {!status.self_use_mode_enabled && ( +
+ {t('没有账户?')}{' '} + + {t('注册')} + +
+ )}
); }; const renderEmailLoginForm = () => { + const inputBaseClass = + 'w-full h-11 px-3 rounded-lg border bg-white text-[13.5px] outline-none transition-colors focus:border-[#635BFF] focus:ring-2 focus:ring-[#635BFF]/15'; + const inputStyle = { + borderColor: LINE, + color: INK, + }; + const labelClass = 'text-[12.5px] font-medium mb-1.5 block'; + return ( -
-
-
- Logo - {systemName} +
+
+

+ {t('登 录')} +

+

+ {t('使用 邮箱或用户名 继续')} +

+ + {status.passkey_login && passkeySupported && ( + + )} + +
+
+ +
+ + + + handleChange('username', e.target.value)} + className={inputBaseClass + ' pl-10'} + style={inputStyle} + autoComplete='username' + /> +
+
+ +
+ +
+ + + + handleChange('password', e.target.value)} + className={inputBaseClass + ' pl-10'} + style={inputStyle} + autoComplete='current-password' + /> +
+
+ + {captchaEnabled && ( +
+ +
+ setCaptchaCode(e.target.value)} + className={inputBaseClass + ' flex-1'} + style={inputStyle} + /> +
+ {captchaImage ? ( + 验证码 + ) : ( + + {captchaLoading ? '...' : t('点击获取')} + + )} +
+
+
+ )} + + {(hasUserAgreement || hasPrivacyPolicy) && ( + + )} + +
+ + + +
- -
- - {t('登 录')} - -
-
- {status.passkey_login && passkeySupported && ( - - )} -
- handleChange('username', value)} - prefix={} - /> + {t('或')} + +
+
- handleChange('password', value)} - prefix={} - /> - - {captchaEnabled && ( -
-
- {t('验证码')} -
-
-
- setCaptchaCode(value)} - /> -
-
- {captchaImage ? ( - 验证码 - ) : ( - - {captchaLoading ? '...' : t('点击获取')} - - )} -
-
-
- )} - - {(hasUserAgreement || hasPrivacyPolicy) && ( -
- setAgreedToTerms(e.target.checked)} - > - - {t('我已阅读并同意')} - {hasUserAgreement && ( - <> - - {t('用户协议')} - - - )} - {hasUserAgreement && hasPrivacyPolicy && t('和')} - {hasPrivacyPolicy && ( - <> - - {t('隐私政策')} - - - )} - - -
- )} - -
- - - -
- - - {hasOAuthLoginOptions && ( - <> - - {t('或')} - - -
- -
- - )} - - {!status.self_use_mode_enabled && ( -
- - {t('没有账户?')}{' '} - - {t('注册')} - - -
- )} -
-
+ + + )}
+ + {!status.self_use_mode_enabled && ( +
+ {t('没有账户?')}{' '} + + {t('注册')} + +
+ )}
); }; @@ -1025,17 +1092,24 @@ const LoginForm = () => {

-
- + + - handleChange('wechat_verification_code', value) + onChange={(e) => + handleChange('wechat_verification_code', e.target.value) } + className='w-full h-11 px-3 rounded-lg border bg-white text-[13.5px] outline-none transition-colors focus:border-[#635BFF] focus:ring-2 focus:ring-[#635BFF]/15' + style={{ borderColor: LINE, color: INK }} /> - +
); }; @@ -1077,40 +1151,67 @@ const LoginForm = () => { ); }; - const brandTagline = t('统一 AI 模型网关,聚合全球顶尖大模型,一站接入即刻使用'); + const brandTagline = t('统一 AI 模型接口,几十个模型,一个 base_url。'); return ( -
- {/* 左侧品牌展示区 */} -
+
+ {/* 左侧品牌展示区 — 浅紫渐变,呼应首页 hero */} +
-
- Logo -

{systemName}

-

{brandTagline}

-
-
-
-
-
+ Logo +
+ + {t('All systems normal')} +
+

+ {systemName} +

+

+ {brandTagline} +

+
+ + POST /v1/chat/completions · 312ms
{/* 右侧登录表单区 */} -
+
{/* 移动端显示 logo */}
Logo - {systemName} + + {systemName} +
{showEmailLogin || !hasOAuthLoginOptions ? renderEmailLoginForm() diff --git a/web/src/components/common/ui/CompactModeToggle.jsx b/web/src/components/common/ui/CompactModeToggle.jsx index 40da0ab..999a1b7 100644 --- a/web/src/components/common/ui/CompactModeToggle.jsx +++ b/web/src/components/common/ui/CompactModeToggle.jsx @@ -18,8 +18,8 @@ For commercial licensing, please contact support@quantumnous.com */ import React from 'react'; -import { Button } from '@douyinfe/semi-ui'; import PropTypes from 'prop-types'; +import { Button } from '@douyinfe/semi-ui'; import { useIsMobile } from '../../../hooks/common/useIsMobile'; /** diff --git a/web/src/components/common/ui/EmptyState.jsx b/web/src/components/common/ui/EmptyState.jsx new file mode 100644 index 0000000..8d8aaf8 --- /dev/null +++ b/web/src/components/common/ui/EmptyState.jsx @@ -0,0 +1,69 @@ +/* +Copyright (C) 2025 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ + +import React from 'react'; +import { Inbox } from 'lucide-react'; + +const ACC = '#635BFF'; +const ACC_SOFT = '#EEEBFF'; +const INK = '#0A2540'; +const SUB = '#425466'; +const DIM = '#697386'; + +const toText = (v) => + typeof v === 'string' + ? v + : v?.props?.children || ''; + +const EmptyState = ({ icon, title, description, children, style }) => { + const Icon = icon || Inbox; + const titleText = toText(title); + const descText = toText(description); + return ( +
+
+ +
+ {titleText && ( +

+ {titleText} +

+ )} + {descText && ( +

+ {descText} +

+ )} + {!titleText && !descText && ( +

+ 暂无数据 +

+ )} + {children &&
{children}
} +
+ ); +}; + +export default EmptyState; diff --git a/web/src/context/User/index.jsx b/web/src/context/User/index.jsx index befdf8d..55ecd25 100644 --- a/web/src/context/User/index.jsx +++ b/web/src/context/User/index.jsx @@ -19,9 +19,10 @@ For commercial licensing, please contact support@quantumnous.com import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { reducer, initialState } from './reducer'; +import { API } from '../../helpers/api'; +import { initialState, reducer } from './reducer'; +import { mergeSelfResponseIntoLocalUser } from '../../helpers/data'; import { normalizeLanguage } from '../../i18n/language'; -import { API, mergeSelfResponseIntoLocalUser } from '../../helpers'; import AdminInitialSetupModal from '../../components/auth/AdminInitialSetupModal'; export const UserContext = React.createContext({ diff --git a/web/src/helpers/utils.jsx b/web/src/helpers/utils.jsx index 431a36c..e431323 100644 --- a/web/src/helpers/utils.jsx +++ b/web/src/helpers/utils.jsx @@ -18,15 +18,12 @@ For commercial licensing, please contact support@quantumnous.com */ import { Toast, Pagination } from '@douyinfe/semi-ui'; -import { toastConstants } from '../constants'; -import React from 'react'; -import { toast } from 'react-toastify'; import { THINK_TAG_REGEX, MESSAGE_ROLES, } from '../constants/playground.constants'; import { TABLE_COMPACT_MODES_KEY } from '../constants'; -import { USER_ROLES } from '../constants/user.constants'; +import { toastConstants } from '../constants/toast.constants'; import { MOBILE_BREAKPOINT } from '../hooks/common/useIsMobile'; const HTMLToastContent = ({ htmlContent }) => { @@ -144,6 +141,15 @@ export function parseCommissionPercentStringToBps(s) { export function getSystemName() { let system_name = localStorage.getItem('system_name'); if (!system_name) return 'TuringToken'; + // 旧品牌名缓存迁移:清掉 TokenFactory / TokenFactoryOpen / 开放词元工厂 等历史默认,避免登录页/控制台标题闪烁。 + if ( + system_name === 'TokenFactory' || + system_name === 'TokenFactoryOpen' || + system_name === '开放词元工厂' + ) { + localStorage.setItem('system_name', 'TuringToken'); + return 'TuringToken'; + } return system_name; } diff --git a/web/src/i18n/i18n.js b/web/src/i18n/i18n.js index beb7e00..b50e1af 100644 --- a/web/src/i18n/i18n.js +++ b/web/src/i18n/i18n.js @@ -39,17 +39,17 @@ i18n load: 'currentOnly', supportedLngs: supportedLanguages, resources: { - en: enTranslation, - 'zh-CN': zhCNTranslation, - 'zh-TW': zhTWTranslation, - fr: frTranslation, - ru: ruTranslation, - ja: jaTranslation, - vi: viTranslation, - id: idTranslation, - ms: msTranslation, - th: thTranslation, - sw: swTranslation, + en: { translation: enTranslation }, + 'zh-CN': { translation: zhCNTranslation }, + 'zh-TW': { translation: zhTWTranslation }, + fr: { translation: frTranslation }, + ru: { translation: ruTranslation }, + ja: { translation: jaTranslation }, + vi: { translation: viTranslation }, + id: { translation: idTranslation }, + ms: { translation: msTranslation }, + th: { translation: thTranslation }, + sw: { translation: swTranslation }, }, fallbackLng: 'zh-CN', lng: 'zh-CN', diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 057d9c3..5bb8add 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -5695,6 +5695,21 @@ "主题切换": "Theme Toggle", "顶栏显示主题切换按钮,允许用户切换亮色/暗色模式": "Show theme toggle in header bar, allowing users to switch between light/dark mode", "语言选择器": "Language Selector", - "顶栏显示语言选择器,允许用户切换界面语言": "Show language selector in header bar, allowing users to switch interface language" + "顶栏显示语言选择器,允许用户切换界面语言": "Show language selector in header bar, allowing users to switch interface language", + "All systems normal": "All systems normal", + "函数": "function", + "一样轻。": "— that light.", + "把 OpenAI、Claude、Gemini、DeepSeek 等几十个模型收进同一个 base_url,切换模型、改价格、改延迟,不必动客户端。": "Bring OpenAI, Claude, Gemini, DeepSeek and dozens of other models behind a single base_url. Switch models, pricing, latency — no client changes.", + "免费注册": "Sign up free", + "联系销售": "Contact sales", + "注册即送 ¥10": "¥10 free credits on signup", + "无月费": "No monthly fee", + "比开放透明": "Transparent pricing", + "你的客户端只需一句": "Your client only needs one", + ",其余的事——路由、价格、容灾——都替你做了。": ", and we handle routing, pricing, and failover for you.", + "立即接入。": "Integrate now.", + "注册送 ¥10 试用金,分键跑完一组 benchmark,一拍压测。": "Get ¥10 trial credit on signup. Run a benchmark, then a load test.", + "定价": "Pricing", + "调用 AI 像调用": "Calling AI is as light as calling" } } \ No newline at end of file diff --git a/web/src/i18n/locales/zh-CN.json b/web/src/i18n/locales/zh-CN.json index ae8ed1d..ec177e2 100644 --- a/web/src/i18n/locales/zh-CN.json +++ b/web/src/i18n/locales/zh-CN.json @@ -5554,6 +5554,21 @@ "主题切换": "主题切换", "顶栏显示主题切换按钮,允许用户切换亮色/暗色模式": "顶栏显示主题切换按钮,允许用户切换亮色/暗色模式", "语言选择器": "语言选择器", - "顶栏显示语言选择器,允许用户切换界面语言": "顶栏显示语言选择器,允许用户切换界面语言" + "顶栏显示语言选择器,允许用户切换界面语言": "顶栏显示语言选择器,允许用户切换界面语言", + "All systems normal": "All systems normal", + "函数": "函数", + "一样轻。": "一样轻。", + "把 OpenAI、Claude、Gemini、DeepSeek 等几十个模型收进同一个 base_url,切换模型、改价格、改延迟,不必动客户端。": "把 OpenAI、Claude、Gemini、DeepSeek 等几十个模型收进同一个 base_url,切换模型、改价格、改延迟,不必动客户端。", + "免费注册": "免费注册", + "联系销售": "联系销售", + "注册即送 ¥10": "注册即送 ¥10", + "无月费": "无月费", + "比开放透明": "比开放透明", + "你的客户端只需一句": "你的客户端只需一句", + ",其余的事——路由、价格、容灾——都替你做了。": ",其余的事——路由、价格、容灾——都替你做了。", + "立即接入。": "立即接入。", + "注册送 ¥10 试用金,分键跑完一组 benchmark,一拍压测。": "注册送 ¥10 试用金,分键跑完一组 benchmark,一拍压测。", + "定价": "定价", + "调用 AI 像调用": "调用 AI 像调用" } } \ No newline at end of file diff --git a/web/src/pages/Contact/index.jsx b/web/src/pages/Contact/index.jsx new file mode 100644 index 0000000..e08e004 --- /dev/null +++ b/web/src/pages/Contact/index.jsx @@ -0,0 +1,364 @@ +/* +Copyright (C) 2025 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ + +import React, { useState } from 'react'; +import { ArrowRight, Mail, MessageSquare, Building2, ShieldCheck, Check } from 'lucide-react'; +import { Link } from 'react-router-dom'; + +const ACC = '#635BFF'; +const ACC_SOFT = '#EEEBFF'; +const BG = '#FFFFFF'; +const SOFT = '#FBFAF7'; +const TINT = '#F6F4FF'; +const INK = '#0A2540'; +const SUB = '#425466'; +const DIM = '#697386'; +const LINE = '#E3E8EE'; + +function Field({ label, value, onChange, type = 'text', required = false, placeholder = '' }) { + return ( +
+
+ {label}{required && *} +
+ +
+ ); +} + +function InfoCard({ icon, title, primary, sub }) { + return ( +
+
+
+ {icon} +
+
{title}
+
+
+
{primary}
+
{sub}
+
+
+ ); +} + +function MiniTopBar() { + return ( +
+
+ + logo + TuringToken + +
+ + 服务正常 · 99.95% +
+
+ + 控制台 + + + 免费注册 + +
+
+ ); +} + +export default function Contact() { + const [form, setForm] = useState({ + company: '', + name: '', + email: '', + volume: '', + message: '', + }); + const update = (k) => (e) => setForm({ ...form, [k]: e.target.value }); + const submit = (e) => { + e.preventDefault(); + alert('已收到,我们会在 24 小时内联系你。'); + }; + + return ( +
+ + + {/* Hero */} +
+
+
+
+ + contact sales +
+

+ 告诉我们,
+ 你的 AI 场景。 +

+

+ 大客户、定制路由、长期合约、技术对接 — 都从这里开始。 +

+
+ + 24 小时内回复 + + + 可签 NDA + + + 无电销骚扰 + +
+
+
+ + {/* Form + Info */} +
+
+
+ {/* Form */} +
+
get in touch
+

+ 填一下,
我们 24h 内联系你。 +

+ +
+ + +
+
+ +
+ +
+
月调用量
+
+ + +
+
+ +
+
你的场景
+