Compare commits

...

2 Commits

Author SHA1 Message Date
wangxiaoji 00b7755fe1 chore: 更新 deploy.py 路径指向 D:\tuling_new\web\dist 2026-07-03 16:45:45 +08:00
wangxiaoji b47ea0dd3a 首页重构为 TokenDance 设计系统 + Contact 页面
- Home/index.jsx: 替换为 TokenDance 设计(紫色点缀、白色主题、TuringToken 品牌)
- Contact/index.jsx: 新增联系销售页面
- EmptyState.jsx: 新增通用空状态组件
- i18n/locales: 中英文 i18n 资源同步新增
- i18n.js, utils.jsx: 配合新页面更新工具函数
- App.jsx, AdminInitialSetupModal.jsx, User context: 注册联系页路由及配套调整
2026-07-03 16:40:56 +08:00
12 changed files with 749 additions and 663 deletions

2
web/deploy.py vendored
View File

@ -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...")

17
web/src/App.jsx vendored
View File

@ -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() {
</Suspense>
}
/>
<Route
path='/contact'
element={
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
<Contact />
</Suspense>
}
/>
<Route
path='/docs'
element={

View File

@ -15,16 +15,16 @@ import React, {
useRef,
useState,
} from 'react';
import { Modal, Form, Typography, Space, Button } from '@douyinfe/semi-ui';
import { IconLock } from '@douyinfe/semi-icons';
import { useTranslation } from 'react-i18next';
import { UserContext } from '../../context/User';
import { Lock as IconLock } from 'lucide-react';
import { Modal, Form, Typography, Space, Button } from '@douyinfe/semi-ui';
import {
API,
showError,
showSuccess,
mergeSelfResponseIntoLocalUser,
} from '../../helpers';
import { UserContext } from '../../context/User';
import { buildAdminUserPhoneFieldRules } from '../table/users/modals/userPhoneFormRules';
const { Text, Title } = Typography;

View File

@ -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';
/**

View File

@ -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 <https://www.gnu.org/licenses/>.
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 (
<div
className='flex flex-col items-center justify-center py-12 text-center'
style={style}
>
<div
className='inline-flex h-12 w-12 items-center justify-center rounded-2xl'
style={{ background: ACC_SOFT, color: ACC }}
>
<Icon size={22} />
</div>
{titleText && (
<p className='mt-4 text-sm font-semibold' style={{ color: INK }}>
{titleText}
</p>
)}
{descText && (
<p className='mt-1 text-sm' style={{ color: DIM }}>
{descText}
</p>
)}
{!titleText && !descText && (
<p className='mt-4 text-sm' style={{ color: DIM }}>
暂无数据
</p>
)}
{children && <div className='mt-4'>{children}</div>}
</div>
);
};
export default EmptyState;

View File

@ -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({

View File

@ -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;
}

22
web/src/i18n/i18n.js vendored
View File

@ -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',

View File

@ -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",
"调用 AI 像调用函": "Call AI like calling a",
"函数": "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"
}
}

View File

@ -5554,6 +5554,21 @@
"主题切换": "主题切换",
"顶栏显示主题切换按钮,允许用户切换亮色/暗色模式": "顶栏显示主题切换按钮,允许用户切换亮色/暗色模式",
"语言选择器": "语言选择器",
"顶栏显示语言选择器,允许用户切换界面语言": "顶栏显示语言选择器,允许用户切换界面语言"
"顶栏显示语言选择器,允许用户切换界面语言": "顶栏显示语言选择器,允许用户切换界面语言",
"All systems normal": "All systems normal",
"调用 AI 像调用函": "调用 AI 像调用函",
"函数": "函数",
"一样轻。": "一样轻。",
"把 OpenAI、Claude、Gemini、DeepSeek 等几十个模型收进同一个 base_url切换模型、改价格、改延迟不必动客户端。": "把 OpenAI、Claude、Gemini、DeepSeek 等几十个模型收进同一个 base_url切换模型、改价格、改延迟不必动客户端。",
"免费注册": "免费注册",
"联系销售": "联系销售",
"注册即送 ¥10": "注册即送 ¥10",
"无月费": "无月费",
"比开放透明": "比开放透明",
"你的客户端只需一句": "你的客户端只需一句",
",其余的事——路由、价格、容灾——都替你做了。": ",其余的事——路由、价格、容灾——都替你做了。",
"立即接入。": "立即接入。",
"注册送 ¥10 试用金,分键跑完一组 benchmark一拍压测。": "注册送 ¥10 试用金,分键跑完一组 benchmark一拍压测。",
"定价": "定价"
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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 (
<div>
<div className="mb-2 text-xs" style={{ color: DIM }}>
{label}{required && <span style={{ color: ACC }}> *</span>}
</div>
<input
type={type}
value={value}
onChange={onChange}
required={required}
placeholder={placeholder}
className="w-full rounded-lg border bg-white px-3.5 py-2.5 text-sm outline-none transition-colors focus:border-[#635BFF]"
style={{ borderColor: LINE, color: INK }}
/>
</div>
);
}
function InfoCard({ icon, title, primary, sub }) {
return (
<div
className="rounded-xl border bg-white p-5 transition-all hover:-translate-y-0.5 hover:shadow-sm"
style={{ borderColor: LINE }}
>
<div className="flex items-center gap-2.5">
<div
className="inline-flex h-8 w-8 items-center justify-center rounded-lg shrink-0"
style={{ background: ACC_SOFT, color: ACC }}
>
{icon}
</div>
<div className="text-sm font-semibold tracking-tight" style={{ color: INK }}>{title}</div>
</div>
<div className="mt-3.5 pl-[42px]">
<div className="text-[15px] font-medium" style={{ color: ACC }}>{primary}</div>
<div className="mt-1 text-xs leading-[1.6]" style={{ color: DIM }}>{sub}</div>
</div>
</div>
);
}
function MiniTopBar() {
return (
<header className="sticky top-0 z-50 border-b border-[#E3E8EE] bg-white/85 backdrop-blur-md">
<div className="flex h-16 items-center px-6 lg:px-10 max-w-[1200px] mx-auto">
<Link to="/" className="flex items-center gap-2.5">
<img src="/logo.jpg" alt="logo" className="h-7 w-7 rounded-full" />
<span className="font-heading text-[15px] font-semibold tracking-tight" style={{ color: INK }}>TuringToken</span>
</Link>
<div className="hidden md:flex items-center gap-2 ml-6 rounded-full px-2.5 py-1 text-[11px]" style={{ background: '#F6F9FC', color: DIM }}>
<span className="h-1.5 w-1.5 rounded-full bg-emerald-400" />
<span>服务正常 · 99.95%</span>
</div>
<div className="flex-1" />
<Link
to="/console"
className="inline-flex h-9 items-center rounded-full px-3.5 text-sm hover:bg-[#0A254006]"
style={{ color: SUB }}
>
控制台
</Link>
<Link
to="/register"
className="ml-2 inline-flex h-9 items-center gap-1.5 rounded-full px-4 text-sm font-medium text-white hover:opacity-90"
style={{ background: INK }}
>
免费注册 <ArrowRight size={12} />
</Link>
</div>
</header>
);
}
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 (
<div className="min-h-screen" style={{ background: BG, color: INK }}>
<MiniTopBar />
{/* Hero */}
<section className="relative overflow-hidden" style={{ background: BG }}>
<div
className="pointer-events-none absolute inset-x-0 top-0 h-[600px]"
style={{ background: 'radial-gradient(80% 60% at 50% 0%, #EEEBFF 0%, transparent 70%)' }}
/>
<div className="relative max-w-[1200px] mx-auto px-6 lg:px-10 pt-20 pb-14 lg:pt-28 lg:pb-20 text-center">
<div className="inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-xs" style={{ borderColor: LINE, color: DIM }}>
<span className="h-1.5 w-1.5 rounded-full" style={{ background: ACC }} />
<span>contact sales</span>
</div>
<h1
className="mt-6 font-heading font-semibold leading-[1.04] tracking-[-0.025em]"
style={{ fontSize: 'clamp(40px, 5.4vw, 72px)', color: INK }}
>
告诉我们<br />
你的 <span style={{ color: ACC }}>AI 场景</span>
</h1>
<p className="mt-6 mx-auto max-w-2xl text-base leading-[1.65]" style={{ color: SUB }}>
大客户定制路由长期合约技术对接 都从这里开始
</p>
<div className="mt-8 flex flex-wrap items-center justify-center gap-6 text-xs" style={{ color: DIM }}>
<span className="inline-flex items-center gap-1.5">
<Check size={12} style={{ color: ACC }} /> 24 小时内回复
</span>
<span className="inline-flex items-center gap-1.5">
<Check size={12} style={{ color: ACC }} /> 可签 NDA
</span>
<span className="inline-flex items-center gap-1.5">
<Check size={12} style={{ color: ACC }} /> 无电销骚扰
</span>
</div>
</div>
</section>
{/* Form + Info */}
<section style={{ background: BG }}>
<div className="max-w-[1200px] mx-auto px-6 lg:px-10 pb-24">
<div className="grid gap-8 lg:grid-cols-[1.4fr_1fr]">
{/* Form */}
<form
onSubmit={submit}
className="rounded-2xl border bg-white p-8 lg:p-10 shadow-sm"
style={{ borderColor: LINE }}
>
<div className="font-mono text-[11px] uppercase tracking-[0.25em]" style={{ color: DIM }}>get in touch</div>
<h2
className="mt-4 font-heading text-2xl font-semibold leading-[1.2] tracking-tight lg:text-[32px]"
style={{ color: INK }}
>
填一下<br />我们 24h 内联系你
</h2>
<div className="mt-8 grid gap-5 md:grid-cols-2">
<Field label="公司名" value={form.company} onChange={update('company')} required />
<Field label="你的姓名" value={form.name} onChange={update('name')} required />
</div>
<div className="mt-5">
<Field
label="工作邮箱"
type="email"
value={form.email}
onChange={update('email')}
required
placeholder="you@company.com"
/>
</div>
<div className="mt-5">
<div className="mb-2 text-xs" style={{ color: DIM }}>月调用量</div>
<div className="relative">
<select
value={form.volume}
onChange={update('volume')}
className="w-full appearance-none rounded-lg border bg-white pl-3.5 pr-9 py-2.5 text-sm outline-none transition-colors focus:border-[#635BFF]"
style={{ borderColor: LINE, color: form.volume ? INK : DIM }}
>
<option value="">选择大致量级</option>
<option>10 万以下</option>
<option>10 100 </option>
<option>100 1000 </option>
<option>1000 万以上</option>
<option>暂未确定</option>
</select>
<span className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-xs" style={{ color: DIM }}></span>
</div>
</div>
<div className="mt-5">
<div className="mb-2 text-xs" style={{ color: DIM }}>你的场景</div>
<textarea
value={form.message}
onChange={update('message')}
rows={4}
placeholder="例如:做企业知识库客服,需要稳定的 Claude + 国产模型双备援,月调用量约 300 万..."
className="w-full rounded-lg border bg-white px-3.5 py-2.5 text-sm outline-none transition-colors focus:border-[#635BFF] resize-y"
style={{ borderColor: LINE, color: INK }}
/>
</div>
<div className="mt-7 flex flex-wrap items-center gap-4">
<button
type="submit"
className="inline-flex h-11 items-center gap-2 rounded-full px-6 text-sm font-semibold text-white hover:opacity-90"
style={{ background: INK }}
>
发送 <ArrowRight size={13} />
</button>
<span className="text-xs" style={{ color: DIM }}>
提交即同意我们通过邮箱回访仅一次
</span>
</div>
</form>
{/* Info side */}
<div className="space-y-4">
<InfoCard
icon={<Mail size={16} />}
title="销售咨询"
primary="sales@turingtoken.ai"
sub="工作日 24h 内回复;加急请电话"
/>
<InfoCard
icon={<MessageSquare size={16} />}
title="技术支持"
primary="support@turingtoken.ai"
sub="已签合约客户优先P0 故障 1h 响应"
/>
<InfoCard
icon={<ShieldCheck size={16} />}
title="法务 / NDA"
primary="legal@turingtoken.ai"
sub="签前可先发 NDA 模板,含双向保密"
/>
<InfoCard
icon={<Building2 size={16} />}
title="办公室"
primary="上海 · 北京 · 深圳"
sub="仅预约接待,不接受 walk-in"
/>
</div>
</div>
</div>
</section>
{/* FAQ */}
<section style={{ background: SOFT }}>
<div className="max-w-[1100px] mx-auto px-6 lg:px-10 py-24 lg:py-32">
<div className="text-center">
<div className="font-mono text-[11px] uppercase tracking-[0.25em]" style={{ color: DIM }}>frequently asked</div>
<h2
className="mt-4 font-heading text-3xl font-semibold tracking-tight lg:text-4xl"
style={{ color: INK }}
>
常见问题
</h2>
</div>
<div className="mt-14 grid gap-5 md:grid-cols-2">
{[
{
q: '支持私有化部署吗?',
a: '支持。签约后 24 周内完成含多模型路由、监控面板、计费、权限管理、SSO。',
},
{
q: '大客户有专属价格吗?',
a: '月调用量 100 万以上可谈。多数客户能拿到比 OpenAI 直连便宜 30%+ 的合约价。',
},
{
q: '能签长期合约吗?',
a: '可以。年付 / 季付都支持,附 SLA 99.95% 兜底,故障按比例退款。',
},
{
q: '数据合规怎么做?',
a: '全链路 TLS 1.3;境内流量走国内供应商;日志 30 天滚动删除;支持 GDPR 与个保法。',
},
].map((f, i) => (
<div
key={i}
className="rounded-xl border bg-white p-6"
style={{ borderColor: LINE }}
>
<h3 className="font-heading text-lg font-semibold tracking-tight" style={{ color: INK }}>{f.q}</h3>
<p className="mt-3 text-sm leading-[1.7]" style={{ color: SUB }}>{f.a}</p>
</div>
))}
</div>
</div>
</section>
{/* Closing */}
<section className="relative overflow-hidden" style={{ background: BG }}>
<div
className="pointer-events-none absolute inset-x-0 bottom-0 h-[400px]"
style={{ background: 'radial-gradient(60% 60% at 50% 100%, #EEEBFF 0%, transparent 70%)' }}
/>
<div className="relative mx-auto max-w-3xl px-6 lg:px-10 py-24 lg:py-32 text-center">
<h2
className="font-heading text-3xl font-semibold leading-tight tracking-[-0.02em] lg:text-[44px]"
style={{ color: INK }}
>
先用起来再聊
</h2>
<p className="mt-5 text-base leading-[1.7]" style={{ color: SUB }}>
注册送 ¥10 试跑金足够跑完一组 benchmark一轮压测聊完再决定也不迟
</p>
<div className="mt-9 flex flex-wrap items-center justify-center gap-3">
<Link
to="/register"
className="inline-flex h-11 items-center gap-2 rounded-full px-6 text-sm font-semibold text-white hover:opacity-90"
style={{ background: INK }}
>
免费注册 <ArrowRight size={13} />
</Link>
<Link
to="/"
className="inline-flex h-11 items-center gap-1.5 rounded-full border px-6 text-sm hover:bg-[#0A254006]"
style={{ borderColor: INK, color: INK }}
>
先看首页
</Link>
</div>
</div>
</section>
{/* Footer */}
<footer className="border-t" style={{ borderColor: LINE, background: BG }}>
<div className="max-w-[1200px] mx-auto px-6 lg:px-10 py-12">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<Link to="/" className="inline-flex items-center gap-2.5">
<img src="/logo.jpg" alt="logo" className="h-7 w-7 rounded-full" />
<span className="font-heading text-base font-semibold tracking-tight" style={{ color: INK }}>TuringToken</span>
</Link>
<span className="text-xs" style={{ color: DIM }}>© 2026 TuringToken · Made for developers.</span>
</div>
</div>
</footer>
</div>
);
}

File diff suppressed because it is too large Load Diff