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 {
|
import {
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
BarChart3,
|
|
||||||
Check,
|
Check,
|
||||||
Copy,
|
Copy,
|
||||||
Shield,
|
|
||||||
Zap,
|
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
@@ -39,7 +36,7 @@ interface HeroProps {
|
|||||||
|
|
||||||
const providers = [
|
const providers = [
|
||||||
'OpenAI', 'Claude', 'Gemini', 'Mistral', 'DeepSeek',
|
'OpenAI', 'Claude', 'Gemini', 'Mistral', 'DeepSeek',
|
||||||
'Llama', 'Qwen', 'Cohere', 'Groq', 'Perplexity',
|
'Codex', 'Qwen', 'Cohere', 'Groq', 'Perplexity',
|
||||||
]
|
]
|
||||||
|
|
||||||
const jsCode = `import OpenAI from 'openai';
|
const jsCode = `import OpenAI from 'openai';
|
||||||
@@ -64,7 +61,7 @@ client = OpenAI(
|
|||||||
)
|
)
|
||||||
|
|
||||||
response = client.chat.completions.create(
|
response = client.chat.completions.create(
|
||||||
model="gpt-4o",
|
model="claude-sonnet-4-20250514",
|
||||||
messages=[
|
messages=[
|
||||||
{"role": "user", "content": "Hello!"}
|
{"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 "Authorization: Bearer sk-your-api-key" \\
|
||||||
-H "Content-Type: application/json" \\
|
-H "Content-Type: application/json" \\
|
||||||
-d '{
|
-d '{
|
||||||
"model": "gpt-4o",
|
"model": "deepseek-chat",
|
||||||
"messages": [{"role": "user", "content": "Hello!"}]
|
"messages": [{"role": "user", "content": "Hello!"}]
|
||||||
}'`
|
}'`
|
||||||
|
|
||||||
@@ -87,17 +84,10 @@ export function Hero(props: HeroProps) {
|
|||||||
const [activeSection, setActiveSection] = useState(0)
|
const [activeSection, setActiveSection] = useState(0)
|
||||||
const [activeTab, setActiveTab] = useState<TabKey>('javascript')
|
const [activeTab, setActiveTab] = useState<TabKey>('javascript')
|
||||||
const [copied, setCopied] = useState(false)
|
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 = [
|
const sections = [
|
||||||
{ id: 'hero', ref: useRef<HTMLDivElement>(null) },
|
{ id: 'hero', ref: useRef<HTMLDivElement>(null) },
|
||||||
{ id: 'code', ref: useRef<HTMLDivElement>(null) },
|
{ id: 'code', ref: useRef<HTMLDivElement>(null) },
|
||||||
{ id: 'demo', ref: useRef<HTMLDivElement>(null) },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// Scroll spy
|
// Scroll spy
|
||||||
@@ -136,22 +126,6 @@ export function Hero(props: HeroProps) {
|
|||||||
setTimeout(() => setCopied(false), 2000)
|
setTimeout(() => setCopied(false), 2000)
|
||||||
}, [activeTab])
|
}, [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> = {
|
const tabCodeMap: Record<TabKey, string> = {
|
||||||
javascript: jsCode,
|
javascript: jsCode,
|
||||||
python: pythonCode,
|
python: pythonCode,
|
||||||
@@ -164,13 +138,6 @@ export function Hero(props: HeroProps) {
|
|||||||
{ key: 'curl', label: 'cURL' },
|
{ key: 'curl', label: 'cURL' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const stepLabels = [
|
|
||||||
t('Connecting'),
|
|
||||||
t('Authenticating'),
|
|
||||||
t('Processing'),
|
|
||||||
t('Complete'),
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className='relative overflow-hidden bg-background' ref={containerRef}>
|
<section className='relative overflow-hidden bg-background' ref={containerRef}>
|
||||||
{/* Subtle dot grid */}
|
{/* Subtle dot grid */}
|
||||||
@@ -196,7 +163,7 @@ export function Hero(props: HeroProps) {
|
|||||||
|
|
||||||
{/* Section navigation dots (desktop only) */}
|
{/* 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'>
|
<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
|
<button
|
||||||
key={i}
|
key={i}
|
||||||
onClick={() => scrollToSection(i)}
|
onClick={() => scrollToSection(i)}
|
||||||
@@ -223,7 +190,7 @@ export function Hero(props: HeroProps) {
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className='mx-auto mt-4 max-w-xl text-[15px] leading-relaxed text-muted-foreground'>
|
<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>
|
</p>
|
||||||
|
|
||||||
{/* CTA */}
|
{/* CTA */}
|
||||||
@@ -267,28 +234,6 @@ export function Hero(props: HeroProps) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</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 ── */}
|
{/* ── Section 2: Code Demo ── */}
|
||||||
<div
|
<div
|
||||||
ref={sections[1].ref}
|
ref={sections[1].ref}
|
||||||
@@ -350,20 +295,20 @@ export function Hero(props: HeroProps) {
|
|||||||
{/* Right: Text content */}
|
{/* Right: Text content */}
|
||||||
<div>
|
<div>
|
||||||
<h2 className='text-[clamp(1.5rem,3vw,2rem)] leading-tight font-bold tracking-[-0.02em] text-foreground'>
|
<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>
|
</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'>
|
<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>
|
||||||
<p className='mt-4 max-w-md text-[15px] leading-relaxed text-muted-foreground'>
|
<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>
|
</p>
|
||||||
<ul className='mt-6 space-y-3'>
|
<ul className='mt-6 space-y-3'>
|
||||||
{[
|
{[
|
||||||
t('OpenAI-compatible API interface'),
|
t('Supports Codex, Claude, GPT, DeepSeek and more'),
|
||||||
t('Zero code changes required'),
|
t('Unified API, zero code changes required'),
|
||||||
t('Automatic model routing'),
|
t('Automatic model routing and failover'),
|
||||||
t('Real-time request monitoring'),
|
t('Real-time usage tracking and cost control'),
|
||||||
].map((feature) => (
|
].map((feature) => (
|
||||||
<li key={feature} className='flex items-center gap-2.5 text-sm text-foreground'>
|
<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'>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── Section 3: Interactive Demo ── */}
|
{/* ── Provider logos marquee (bottom) ── */}
|
||||||
<div
|
<div className='relative py-16'>
|
||||||
ref={sections[2].ref}
|
<p className='mb-4 text-center text-xs font-medium uppercase tracking-widest text-muted-foreground/60'>
|
||||||
className='relative flex min-h-svh items-center px-6 py-20'
|
{t('Supported providers')}
|
||||||
>
|
</p>
|
||||||
<div className='mx-auto grid w-full max-w-6xl gap-12 lg:grid-cols-2 lg:items-center'>
|
<div className='relative overflow-hidden'>
|
||||||
{/* Left: Text content */}
|
<div className='flex animate-[marquee_30s_linear_infinite] gap-8'>
|
||||||
<div>
|
{[...providers, ...providers].map((name, i) => (
|
||||||
<h2 className='text-[clamp(1.5rem,3vw,2rem)] leading-tight font-bold tracking-[-0.02em] text-foreground'>
|
<span
|
||||||
{t('Try it Live')}
|
key={`${name}-${i}`}
|
||||||
</h2>
|
className='shrink-0 text-sm font-semibold text-muted-foreground/40'
|
||||||
<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'}
|
|
||||||
>
|
>
|
||||||
{demoState === 'running' ? '...' : t('Send Request')}
|
{name}
|
||||||
</Button>
|
</span>
|
||||||
|
))}
|
||||||
{/* 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>
|
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Vendored
+8
@@ -4595,6 +4595,14 @@
|
|||||||
"OpenAI-compatible API interface": "OpenAI-compatible API interface",
|
"OpenAI-compatible API interface": "OpenAI-compatible API interface",
|
||||||
"Processing": "Processing",
|
"Processing": "Processing",
|
||||||
"Quick Integration": "Quick Integration",
|
"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",
|
"Real-time request monitoring": "Real-time request monitoring",
|
||||||
"Search documents...": "Search documents...",
|
"Search documents...": "Search documents...",
|
||||||
"Select a document to start reading": "Select a document to start reading",
|
"Select a document to start reading": "Select a document to start reading",
|
||||||
|
|||||||
Vendored
+8
@@ -4595,6 +4595,14 @@
|
|||||||
"OpenAI-compatible API interface": "兼容 OpenAI 的 API 接口",
|
"OpenAI-compatible API interface": "兼容 OpenAI 的 API 接口",
|
||||||
"Processing": "处理中",
|
"Processing": "处理中",
|
||||||
"Quick Integration": "快速集成",
|
"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": "实时请求监控",
|
"Real-time request monitoring": "实时请求监控",
|
||||||
"Search documents...": "搜索文档...",
|
"Search documents...": "搜索文档...",
|
||||||
"Select a document to start reading": "选择文档开始阅读",
|
"Select a document to start reading": "选择文档开始阅读",
|
||||||
|
|||||||
Reference in New Issue
Block a user