refactor: homepage - remove demo section, adapt for token relay station, move providers to bottom
Docker Build / Build and Push Docker Image (push) Successful in 4m17s
Docker Build / Build and Push Docker Image (push) Successful in 4m17s
This commit is contained in:
+29
-210
@@ -21,11 +21,8 @@ import { Link } from '@tanstack/react-router'
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowRight,
|
||||
BarChart3,
|
||||
Check,
|
||||
Copy,
|
||||
Shield,
|
||||
Zap,
|
||||
} from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -39,7 +36,7 @@ interface HeroProps {
|
||||
|
||||
const providers = [
|
||||
'OpenAI', 'Claude', 'Gemini', 'Mistral', 'DeepSeek',
|
||||
'Llama', 'Qwen', 'Cohere', 'Groq', 'Perplexity',
|
||||
'Codex', 'Qwen', 'Cohere', 'Groq', 'Perplexity',
|
||||
]
|
||||
|
||||
const jsCode = `import OpenAI from 'openai';
|
||||
@@ -64,7 +61,7 @@ client = OpenAI(
|
||||
)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-4o",
|
||||
model="claude-sonnet-4-20250514",
|
||||
messages=[
|
||||
{"role": "user", "content": "Hello!"}
|
||||
]
|
||||
@@ -74,7 +71,7 @@ const curlCode = `curl https://your-gateway.com/v1/chat/completions \\
|
||||
-H "Authorization: Bearer sk-your-api-key" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"model": "gpt-4o",
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "Hello!"}]
|
||||
}'`
|
||||
|
||||
@@ -87,17 +84,10 @@ export function Hero(props: HeroProps) {
|
||||
const [activeSection, setActiveSection] = useState(0)
|
||||
const [activeTab, setActiveTab] = useState<TabKey>('javascript')
|
||||
const [copied, setCopied] = useState(false)
|
||||
const [demoState, setDemoState] = useState<'idle' | 'running' | 'done'>('idle')
|
||||
const [demoStep, setDemoStep] = useState(0)
|
||||
|
||||
const serverAddress =
|
||||
(status?.server_address as string | undefined) ||
|
||||
`${window.location.origin}`
|
||||
|
||||
const sections = [
|
||||
{ id: 'hero', ref: useRef<HTMLDivElement>(null) },
|
||||
{ id: 'code', ref: useRef<HTMLDivElement>(null) },
|
||||
{ id: 'demo', ref: useRef<HTMLDivElement>(null) },
|
||||
]
|
||||
|
||||
// Scroll spy
|
||||
@@ -136,22 +126,6 @@ export function Hero(props: HeroProps) {
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}, [activeTab])
|
||||
|
||||
const handleDemo = useCallback(() => {
|
||||
if (demoState === 'running') return
|
||||
setDemoState('running')
|
||||
setDemoStep(0)
|
||||
const steps = [
|
||||
{ step: 1, delay: 600 },
|
||||
{ step: 2, delay: 1200 },
|
||||
{ step: 3, delay: 2000 },
|
||||
{ step: 4, delay: 2800 },
|
||||
]
|
||||
steps.forEach(({ step, delay }) => {
|
||||
setTimeout(() => setDemoStep(step), delay)
|
||||
})
|
||||
setTimeout(() => setDemoState('done'), 3200)
|
||||
}, [demoState])
|
||||
|
||||
const tabCodeMap: Record<TabKey, string> = {
|
||||
javascript: jsCode,
|
||||
python: pythonCode,
|
||||
@@ -164,13 +138,6 @@ export function Hero(props: HeroProps) {
|
||||
{ key: 'curl', label: 'cURL' },
|
||||
]
|
||||
|
||||
const stepLabels = [
|
||||
t('Connecting'),
|
||||
t('Authenticating'),
|
||||
t('Processing'),
|
||||
t('Complete'),
|
||||
]
|
||||
|
||||
return (
|
||||
<section className='relative overflow-hidden bg-background' ref={containerRef}>
|
||||
{/* Subtle dot grid */}
|
||||
@@ -196,7 +163,7 @@ export function Hero(props: HeroProps) {
|
||||
|
||||
{/* Section navigation dots (desktop only) */}
|
||||
<div className='fixed right-6 top-1/2 z-30 hidden -translate-y-1/2 flex-col gap-3 lg:flex'>
|
||||
{[0, 1, 2].map((i) => (
|
||||
{[0, 1].map((i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => scrollToSection(i)}
|
||||
@@ -223,7 +190,7 @@ export function Hero(props: HeroProps) {
|
||||
</h1>
|
||||
|
||||
<p className='mx-auto mt-4 max-w-xl text-[15px] leading-relaxed text-muted-foreground'>
|
||||
{t('Route, monitor, and manage LLM traffic through a single gateway. Switch providers in seconds.')}
|
||||
{t('Token relay station supporting Codex, Claude, GPT, DeepSeek and more. Unified API, unified billing, unified management.')}
|
||||
</p>
|
||||
|
||||
{/* CTA */}
|
||||
@@ -267,28 +234,6 @@ export function Hero(props: HeroProps) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* ── Provider logos marquee ── */}
|
||||
<div className='relative py-12'>
|
||||
<p className='mb-4 text-center text-xs font-medium uppercase tracking-widest text-muted-foreground/60'>
|
||||
{t('Supported providers')}
|
||||
</p>
|
||||
<div className='relative overflow-hidden'>
|
||||
<div className='flex animate-[marquee_30s_linear_infinite] gap-8'>
|
||||
{[...providers, ...providers].map((name, i) => (
|
||||
<span
|
||||
key={`${name}-${i}`}
|
||||
className='shrink-0 text-sm font-semibold text-muted-foreground/40'
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{/* Fade edges */}
|
||||
<div className='pointer-events-none absolute inset-y-0 left-0 w-16 bg-gradient-to-r from-background to-transparent' />
|
||||
<div className='pointer-events-none absolute inset-y-0 right-0 w-16 bg-gradient-to-l from-background to-transparent' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Section 2: Code Demo ── */}
|
||||
<div
|
||||
ref={sections[1].ref}
|
||||
@@ -350,20 +295,20 @@ export function Hero(props: HeroProps) {
|
||||
{/* Right: Text content */}
|
||||
<div>
|
||||
<h2 className='text-[clamp(1.5rem,3vw,2rem)] leading-tight font-bold tracking-[-0.02em] text-foreground'>
|
||||
{t('Quick Integration')}
|
||||
{t('Token Relay Station')}
|
||||
</h2>
|
||||
<p className='mt-1 bg-gradient-to-r from-primary to-primary/60 bg-clip-text text-[clamp(1.5rem,3vw,2rem)] leading-tight font-bold tracking-[-0.02em] text-transparent'>
|
||||
{t('Drop-in Replacement')}
|
||||
{t('Multi-model Access')}
|
||||
</p>
|
||||
<p className='mt-4 max-w-md text-[15px] leading-relaxed text-muted-foreground'>
|
||||
{t('Route, monitor, and manage LLM traffic through a single gateway. Switch providers in seconds.')}
|
||||
{t('Supports Codex, Claude, GPT, DeepSeek, Gemini, Qwen and more. One API key to access all models, with automatic failover and load balancing.')}
|
||||
</p>
|
||||
<ul className='mt-6 space-y-3'>
|
||||
{[
|
||||
t('OpenAI-compatible API interface'),
|
||||
t('Zero code changes required'),
|
||||
t('Automatic model routing'),
|
||||
t('Real-time request monitoring'),
|
||||
t('Supports Codex, Claude, GPT, DeepSeek and more'),
|
||||
t('Unified API, zero code changes required'),
|
||||
t('Automatic model routing and failover'),
|
||||
t('Real-time usage tracking and cost control'),
|
||||
].map((feature) => (
|
||||
<li key={feature} className='flex items-center gap-2.5 text-sm text-foreground'>
|
||||
<div className='flex size-5 shrink-0 items-center justify-center rounded-full bg-primary/10'>
|
||||
@@ -377,151 +322,25 @@ export function Hero(props: HeroProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Section 3: Interactive Demo ── */}
|
||||
<div
|
||||
ref={sections[2].ref}
|
||||
className='relative flex min-h-svh items-center px-6 py-20'
|
||||
>
|
||||
<div className='mx-auto grid w-full max-w-6xl gap-12 lg:grid-cols-2 lg:items-center'>
|
||||
{/* Left: Text content */}
|
||||
<div>
|
||||
<h2 className='text-[clamp(1.5rem,3vw,2rem)] leading-tight font-bold tracking-[-0.02em] text-foreground'>
|
||||
{t('Try it Live')}
|
||||
</h2>
|
||||
<p className='mt-1 bg-gradient-to-r from-primary to-primary/60 bg-clip-text text-[clamp(1.5rem,3vw,2rem)] leading-tight font-bold tracking-[-0.02em] text-transparent'>
|
||||
{t('See it in Action')}
|
||||
</p>
|
||||
<p className='mt-4 max-w-md text-[15px] leading-relaxed text-muted-foreground'>
|
||||
{t('Route, monitor, and manage LLM traffic through a single gateway. Switch providers in seconds.')}
|
||||
</p>
|
||||
<ul className='mt-6 space-y-3'>
|
||||
{[
|
||||
{ icon: Zap, text: t('Automatic model routing') },
|
||||
{ icon: Shield, text: t('OpenAI-compatible API interface') },
|
||||
{ icon: BarChart3, text: t('Real-time request monitoring') },
|
||||
].map(({ icon: Icon, text }) => (
|
||||
<li key={text} className='flex items-center gap-2.5 text-sm text-foreground'>
|
||||
<div className='flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary/10'>
|
||||
<Icon className='size-4 text-primary' />
|
||||
</div>
|
||||
{text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Right: Interactive card */}
|
||||
<div className='overflow-hidden rounded-lg border border-border bg-card shadow-sm'>
|
||||
<div className='border-b border-border px-5 py-3'>
|
||||
<h3 className='text-sm font-semibold text-foreground'>API Request</h3>
|
||||
<p className='text-xs text-muted-foreground'>Simulated gateway call</p>
|
||||
</div>
|
||||
<div className='p-5'>
|
||||
{/* Input area */}
|
||||
<div className='space-y-3'>
|
||||
<div>
|
||||
<label className='mb-1 block text-xs font-medium text-muted-foreground'>
|
||||
API Key
|
||||
</label>
|
||||
<div className='flex h-9 items-center rounded-md border border-border bg-muted/50 px-3 font-mono text-xs text-foreground/70'>
|
||||
sk-••••••••••••••••••••••••••••••
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-xs font-medium text-muted-foreground'>
|
||||
Endpoint
|
||||
</label>
|
||||
<div className='flex h-9 items-center rounded-md border border-border bg-muted/50 px-3 font-mono text-xs text-foreground/70'>
|
||||
{serverAddress}/v1/chat/completions
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Send button */}
|
||||
<Button
|
||||
className='mt-4 w-full'
|
||||
onClick={handleDemo}
|
||||
disabled={demoState === 'running'}
|
||||
{/* ── Provider logos marquee (bottom) ── */}
|
||||
<div className='relative py-16'>
|
||||
<p className='mb-4 text-center text-xs font-medium uppercase tracking-widest text-muted-foreground/60'>
|
||||
{t('Supported providers')}
|
||||
</p>
|
||||
<div className='relative overflow-hidden'>
|
||||
<div className='flex animate-[marquee_30s_linear_infinite] gap-8'>
|
||||
{[...providers, ...providers].map((name, i) => (
|
||||
<span
|
||||
key={`${name}-${i}`}
|
||||
className='shrink-0 text-sm font-semibold text-muted-foreground/40'
|
||||
>
|
||||
{demoState === 'running' ? '...' : t('Send Request')}
|
||||
</Button>
|
||||
|
||||
{/* Animated steps */}
|
||||
{(demoState === 'running' || demoState === 'done') && (
|
||||
<div className='mt-4 space-y-2'>
|
||||
{stepLabels.map((label, i) => {
|
||||
const stepNum = i + 1
|
||||
const isActive = demoStep >= stepNum
|
||||
const isCurrent = demoStep === stepNum && demoState === 'running'
|
||||
return (
|
||||
<div
|
||||
key={label}
|
||||
className={cn(
|
||||
'flex items-center gap-2.5 rounded-md px-3 py-2 text-xs transition-all duration-500',
|
||||
isActive
|
||||
? 'bg-primary/5 text-foreground'
|
||||
: 'text-muted-foreground/50',
|
||||
isCurrent && 'bg-primary/10'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex size-5 shrink-0 items-center justify-center rounded-full transition-all duration-300',
|
||||
isActive
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-muted text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{isActive ? (
|
||||
stepNum === 4 ? (
|
||||
<Check className='size-3' />
|
||||
) : (
|
||||
<span className='text-[10px] font-bold'>{stepNum}</span>
|
||||
)
|
||||
) : (
|
||||
<span className='text-[10px] font-bold'>{stepNum}</span>
|
||||
)}
|
||||
</div>
|
||||
<span className={cn('font-medium', isCurrent && 'animate-pulse')}>
|
||||
{label}
|
||||
</span>
|
||||
{isCurrent && (
|
||||
<div className='ml-auto'>
|
||||
<div className='size-1.5 animate-pulse rounded-full bg-primary' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Result */}
|
||||
{demoState === 'done' && (
|
||||
<div className='mt-4 rounded-md border border-border bg-muted/30 p-3'>
|
||||
<div className='mb-2 flex items-center gap-2'>
|
||||
<span className='inline-block size-1.5 rounded-full bg-green-500' />
|
||||
<span className='text-[11px] font-medium text-muted-foreground'>200 OK</span>
|
||||
<span className='text-[11px] text-muted-foreground/60'>142ms</span>
|
||||
</div>
|
||||
<div className='font-mono text-xs text-foreground/70'>
|
||||
<div>{'{'}</div>
|
||||
<div className='pl-3'>
|
||||
<span className='text-primary'>"model"</span>
|
||||
{': '}
|
||||
<span>"gpt-4o"</span>,
|
||||
</div>
|
||||
<div className='pl-3'>
|
||||
<span className='text-primary'>"usage"</span>
|
||||
{': '}
|
||||
<span>{'{ "prompt_tokens": 12, "completion_tokens": 47 }'}</span>
|
||||
</div>
|
||||
<div>{'}'}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{/* Fade edges */}
|
||||
<div className='pointer-events-none absolute inset-y-0 left-0 w-16 bg-gradient-to-r from-background to-transparent' />
|
||||
<div className='pointer-events-none absolute inset-y-0 right-0 w-16 bg-gradient-to-l from-background to-transparent' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Vendored
+8
@@ -4595,6 +4595,14 @@
|
||||
"OpenAI-compatible API interface": "OpenAI-compatible API interface",
|
||||
"Processing": "Processing",
|
||||
"Quick Integration": "Quick Integration",
|
||||
"Token Relay Station": "Token Relay Station",
|
||||
"Multi-model Access": "Multi-model Access",
|
||||
"Token relay station supporting Codex, Claude, GPT, DeepSeek and more. Unified API, unified billing, unified management.": "Token relay station supporting Codex, Claude, GPT, DeepSeek and more. Unified API, unified billing, unified management.",
|
||||
"Supports Codex, Claude, GPT, DeepSeek, Gemini, Qwen and more. One API key to access all models, with automatic failover and load balancing.": "Supports Codex, Claude, GPT, DeepSeek, Gemini, Qwen and more. One API key to access all models, with automatic failover and load balancing.",
|
||||
"Supports Codex, Claude, GPT, DeepSeek and more": "Supports Codex, Claude, GPT, DeepSeek and more",
|
||||
"Unified API, zero code changes required": "Unified API, zero code changes required",
|
||||
"Automatic model routing and failover": "Automatic model routing and failover",
|
||||
"Real-time usage tracking and cost control": "Real-time usage tracking and cost control",
|
||||
"Real-time request monitoring": "Real-time request monitoring",
|
||||
"Search documents...": "Search documents...",
|
||||
"Select a document to start reading": "Select a document to start reading",
|
||||
|
||||
Vendored
+8
@@ -4595,6 +4595,14 @@
|
||||
"OpenAI-compatible API interface": "兼容 OpenAI 的 API 接口",
|
||||
"Processing": "处理中",
|
||||
"Quick Integration": "快速集成",
|
||||
"Token Relay Station": "Token 中转站",
|
||||
"Multi-model Access": "多模型接入",
|
||||
"Token relay station supporting Codex, Claude, GPT, DeepSeek and more. Unified API, unified billing, unified management.": "Token 中转站,支持 Codex、Claude、GPT、DeepSeek 等模型接入。统一接口,统一计费,统一管理。",
|
||||
"Supports Codex, Claude, GPT, DeepSeek, Gemini, Qwen and more. One API key to access all models, with automatic failover and load balancing.": "支持 Codex、Claude、GPT、DeepSeek、Gemini、通义千问等模型。一个 API Key 访问所有模型,自动故障转移与负载均衡。",
|
||||
"Supports Codex, Claude, GPT, DeepSeek and more": "支持 Codex、Claude、GPT、DeepSeek 等接入",
|
||||
"Unified API, zero code changes required": "统一接口,零代码改动即可使用",
|
||||
"Automatic model routing and failover": "自动模型路由与故障转移",
|
||||
"Real-time usage tracking and cost control": "实时用量追踪与成本控制",
|
||||
"Real-time request monitoring": "实时请求监控",
|
||||
"Search documents...": "搜索文档...",
|
||||
"Select a document to start reading": "选择文档开始阅读",
|
||||
|
||||
Reference in New Issue
Block a user