fix: auth buttons, dropdown ring, redesign homepage
Docker Build / Build and Push Docker Image (push) Successful in 4m33s

This commit is contained in:
2026-06-14 16:49:59 +08:00
parent 2723bd66ad
commit 2d8bdc1b7c
12 changed files with 100 additions and 168 deletions
+1 -1
View File
@@ -62,7 +62,7 @@ function DropdownMenuContent({
<MenuPrimitive.Popup
data-slot='dropdown-menu-content'
className={cn(
'bg-popover text-popover-foreground ring-foreground/10 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg p-1 shadow-md ring-1 duration-100 outline-none data-closed:overflow-hidden',
'bg-popover text-popover-foreground ring-border data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg p-1 shadow-md ring-1 duration-100 outline-none data-closed:overflow-hidden',
className
)}
{...props}
+1 -1
View File
@@ -64,7 +64,7 @@ function PopoverContent({
<PopoverPrimitive.Popup
data-slot='popover-content'
className={cn(
'bg-popover text-popover-foreground ring-foreground/10 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg p-2.5 text-sm shadow-md ring-1 outline-hidden duration-100',
'bg-popover text-popover-foreground ring-border data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg p-2.5 text-sm shadow-md ring-1 outline-hidden duration-100',
className
)}
{...props}
+1 -1
View File
@@ -113,7 +113,7 @@ function SelectContent({
data-slot='select-content'
data-align-trigger={alignItemWithTrigger}
className={cn(
'bg-popover text-popover-foreground ring-foreground/10 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg shadow-md ring-1 duration-100 data-[align-trigger=true]:animate-none',
'bg-popover text-popover-foreground ring-border data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg shadow-md ring-1 duration-100 data-[align-trigger=true]:animate-none',
className
)}
{...props}
@@ -112,7 +112,7 @@ export function ForgotPasswordForm({
<Button
type='submit'
className='mt-2'
className='mt-2 bg-[#00D2FF] text-[#0A0E14] hover:bg-[#00B8E6]'
disabled={isLoading || isActive || !turnstileReady}
>
{isActive
+1 -1
View File
@@ -203,7 +203,7 @@ export function OtpForm({ className, ...props }: OtpFormProps) {
<Button
type='submit'
className='mt-2 w-full'
className='mt-2 w-full bg-[#00D2FF] text-[#0A0E14] hover:bg-[#00B8E6]'
disabled={!isFormValid || isLoading}
>
{isLoading ? <Loader2 className='h-4 w-4 animate-spin' /> : null}
@@ -374,7 +374,7 @@ export function UserAuthForm({
{/* Submit Button */}
<Button
type='submit'
className='mt-2 w-full justify-center gap-2'
className='mt-2 w-full justify-center gap-2 bg-[#00D2FF] text-[#0A0E14] hover:bg-[#00B8E6]'
disabled={isLoading || (requiresLegalConsent && !agreedToLegal)}
>
{isLoading ? <Loader2 className='animate-spin' /> : <LogIn />}
+1 -1
View File
@@ -32,7 +32,7 @@ export function SignIn() {
<AuthLayout>
<div className='w-full space-y-8'>
<div className='space-y-2'>
<h2 className='text-center text-2xl font-semibold tracking-tight sm:text-left'>
<h2 className='text-center text-2xl font-semibold tracking-tight text-white sm:text-left'>
{t('Sign in')}
</h2>
{!status?.self_use_mode_enabled &&
@@ -354,7 +354,7 @@ export function SignUpForm({
{/* Submit Button */}
<Button
type='submit'
className='mt-2 w-full justify-center gap-2'
className='mt-2 w-full justify-center gap-2 bg-[#00D2FF] text-[#0A0E14] hover:bg-[#00B8E6]'
disabled={
isLoading ||
(requiresLegalConsent && !agreedToLegal) ||
+1 -1
View File
@@ -31,7 +31,7 @@ export function SignUp() {
<AuthLayout>
<div className='w-full space-y-8'>
<div className='space-y-2'>
<h2 className='text-center text-2xl font-semibold tracking-tight sm:text-left'>
<h2 className='text-center text-2xl font-semibold tracking-tight text-white sm:text-left'>
{t('Create an account')}
</h2>
<p className='text-muted-foreground text-left text-sm sm:text-base'>
+87 -159
View File
@@ -16,7 +16,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact admin@modelstoken.com
*/
import type React from 'react'
import { Link } from '@tanstack/react-router'
import { ArrowRight, Zap, Shield, Layers, BarChart3, Key, GitBranch } from 'lucide-react'
import { useTranslation } from 'react-i18next'
@@ -28,7 +27,7 @@ interface HeroProps {
isAuthenticated?: boolean
}
/* Termius-dark palette: black bg, cyan accent */
/* Palette: black bg, cyan accent, gray layers */
const T = {
bg: '#0A0E14',
surface: '#0F1419',
@@ -44,12 +43,12 @@ const T = {
}
const features = [
{ icon: Zap, title: 'Multi-model Routing', desc: 'Intelligent routing across providers with automatic failover and load balancing', code: `// Auto failover when provider is down\nrouter.route("gpt-4o", {\n fallback: ["claude-3.5", "gemini-pro"],\n strategy: "latency-first"\n});` },
{ icon: Key, title: 'Key Management', desc: 'Centralized API key lifecycle management with usage tracking and rotation', code: `// Rotate keys without downtime\nkeyManager.rotate("sk-prod-xxx", {\n gracePeriod: "24h",\n notify: ["admin@team.io"]\n});` },
{ icon: Shield, title: 'Access Control', desc: 'Fine-grained permissions, rate limiting, and token-level access policies', code: `// Per-user rate limits\npolicy.set("user-123", {\n rpm: 60,\n tokensPerDay: 100000,\n allowedModels: ["gpt-4o", "claude-3.5"]\n});` },
{ icon: BarChart3, title: 'Real-time Monitoring', desc: 'Live dashboards with request latency, token usage, and cost analytics', code: `// Query usage metrics\nconst metrics = await monitor.query({\n period: "24h",\n groupBy: "model",\n fields: ["latency_p50", "tokens", "cost"]\n});` },
{ icon: Layers, title: 'OpenAI Compatible', desc: 'Drop-in replacement for OpenAI API — no code changes required', code: `// Just change the base URL, that's it\nconst openai = new OpenAI({\n baseURL: "https://your-gateway/v1",\n apiKey: "sk-your-key"\n});` },
{ icon: GitBranch, title: 'Model Mapping', desc: 'Map model names, override parameters, and customize per-channel behavior', code: `// Map internal names to providers\nmapper.add("fast-chat", {\n provider: "anthropic",\n model: "claude-3.5-haiku",\n params: { max_tokens: 4096 }\n});` },
{ icon: Zap, title: 'Multi-model Routing', desc: 'Intelligent routing with automatic failover and load balancing across providers' },
{ icon: Key, title: 'Key Management', desc: 'Centralized API key lifecycle management with usage tracking and rotation' },
{ icon: Shield, title: 'Access Control', desc: 'Fine-grained permissions, rate limiting, and token-level access policies' },
{ icon: BarChart3, title: 'Real-time Monitoring', desc: 'Live dashboards with request latency, token usage, and cost analytics' },
{ icon: Layers, title: 'OpenAI Compatible', desc: 'Drop-in replacement for OpenAI API — no code changes required' },
{ icon: GitBranch, title: 'Model Mapping', desc: 'Map model names, override parameters, and customize per-channel behavior' },
]
export function Hero(props: HeroProps) {
@@ -67,28 +66,38 @@ export function Hero(props: HeroProps) {
className='pointer-events-none absolute inset-0'
style={{
background:
'radial-gradient(ellipse 60% 35% at 50% -5%, rgba(0,210,255,0.06) 0%, transparent 70%)',
'radial-gradient(ellipse 50% 30% at 50% 0%, rgba(0,210,255,0.07) 0%, transparent 70%)',
}}
/>
<div className='relative mx-auto max-w-6xl px-6'>
<div className='relative mx-auto max-w-5xl px-6'>
{/* ── Hero ── */}
<div className='flex min-h-[calc(100svh-3rem)] flex-col items-center justify-center py-20 text-center'>
{/* Eyebrow */}
<div className='mb-8 inline-flex items-center gap-2 rounded-full border px-3.5 py-1 text-xs' style={{ borderColor: T.border, backgroundColor: T.surface, color: T.grayLight }}>
<span className='inline-block size-1.5 rounded-full' style={{ backgroundColor: T.accent }} />
{t('Compatible with OpenAI API')}
</div>
{/* Headline */}
<h1
className='max-w-3xl text-[clamp(2rem,5.5vw,3.5rem)] leading-[1.1] font-bold tracking-[-0.025em]'
style={{ color: T.accent }}
className='max-w-2xl text-[clamp(2.2rem,6vw,4rem)] leading-[1.05] font-bold tracking-[-0.03em]'
style={{ color: T.white }}
>
{t('Unified LLM Gateway')}
{t('Unified LLM')}{' '}
<span style={{ color: T.accent }}>{t('Gateway')}</span>
</h1>
<p className='mt-5 max-w-lg text-base leading-relaxed sm:text-lg' style={{ color: T.grayLight }}>
{/* Subtitle */}
<p className='mt-6 max-w-md text-base leading-relaxed' style={{ color: T.grayLight }}>
{t('One endpoint for all models. OpenAI-compatible, switch and go.')}
</p>
<div className='mt-8 flex flex-wrap items-center justify-center gap-3'>
{/* CTA */}
<div className='mt-10 flex flex-wrap items-center justify-center gap-3'>
{props.isAuthenticated ? (
<Button
className='group h-11 rounded-lg px-6 text-sm font-semibold hover:opacity-90'
className='group h-11 rounded-lg px-7 text-sm font-semibold hover:opacity-90'
style={{ backgroundColor: T.accent, color: T.bg }}
render={<Link to='/dashboard' />}
>
@@ -98,7 +107,7 @@ export function Hero(props: HeroProps) {
) : (
<>
<Button
className='group h-11 rounded-lg px-6 text-sm font-semibold hover:opacity-90'
className='group h-11 rounded-lg px-7 text-sm font-semibold hover:opacity-90'
style={{ backgroundColor: T.accent, color: T.bg }}
render={<Link to='/sign-up' />}
>
@@ -107,7 +116,7 @@ export function Hero(props: HeroProps) {
</Button>
<Button
variant='outline'
className='h-11 rounded-lg px-6 text-sm font-medium hover:opacity-90'
className='h-11 rounded-lg px-7 text-sm font-medium hover:opacity-90'
style={{ borderColor: T.border, backgroundColor: 'transparent', color: T.white }}
render={<Link to='/pricing' />}
>
@@ -116,45 +125,48 @@ export function Hero(props: HeroProps) {
</>
)}
</div>
</div>
{/* Terminal demo */}
<div className='mt-16 w-full max-w-2xl'>
<div
className='overflow-hidden rounded-lg border shadow-[0_8px_40px_-12px_rgba(0,0,0,0.6)]'
style={{ borderColor: T.border, backgroundColor: T.surface }}
>
<div className='flex items-center gap-2 border-b px-4 py-3' style={{ borderColor: T.border }}>
<div className='flex gap-1.5'>
<div className='size-[9px] rounded-full' style={{ backgroundColor: T.red }} />
<div className='size-[9px] rounded-full' style={{ backgroundColor: T.yellow }} />
<div className='size-[9px] rounded-full' style={{ backgroundColor: T.green }} />
</div>
<span className='ml-1 text-[11px] font-medium' style={{ color: T.gray }}>
bash
</span>
{/* ── Terminal demo ── */}
<div className='-mt-8 pb-20'>
<div
className='mx-auto w-full max-w-2xl overflow-hidden rounded-xl border shadow-[0_16px_64px_-16px_rgba(0,0,0,0.5)]'
style={{ borderColor: T.border, backgroundColor: T.surface }}
>
{/* Terminal header */}
<div className='flex items-center gap-2 border-b px-4 py-2.5' style={{ borderColor: T.border }}>
<div className='flex gap-1.5'>
<div className='size-2.5 rounded-full' style={{ backgroundColor: T.red }} />
<div className='size-2.5 rounded-full' style={{ backgroundColor: T.yellow }} />
<div className='size-2.5 rounded-full' style={{ backgroundColor: T.green }} />
</div>
<div className='p-5 font-mono text-[13px] leading-[1.8]'>
<div>
<span style={{ color: T.green }}>$</span>{' '}
<span style={{ color: T.white }}>curl</span>{' '}
<span style={{ color: T.accent }}>{serverAddress}/v1/chat/completions</span>{' '}
<span style={{ color: T.gray }}>\</span>
</div>
<div className='pl-4'>
<span style={{ color: T.gray }}>-H</span>{' '}
<span style={{ color: T.yellow }}>"Authorization: Bearer sk-..."</span>{' '}
<span style={{ color: T.gray }}>\</span>
</div>
<div className='pl-4'>
<span style={{ color: T.gray }}>-d</span>{' '}
<span style={{ color: T.yellow }}>{"'{ \"model\": \"gpt-4o\", \"messages\": [...] }'"}</span>
</div>
<div className='mt-4 flex items-center gap-2 border-t pt-4' style={{ borderColor: T.border }}>
<span className='ml-2 text-[11px]' style={{ color: T.gray }}>bash</span>
</div>
{/* Terminal content */}
<div className='p-5 font-mono text-[13px] leading-[1.9]'>
<div>
<span style={{ color: T.accent }}>$</span>{' '}
<span style={{ color: T.white }}>curl</span>{' '}
<span style={{ color: T.accent }}>{serverAddress}/v1/chat/completions</span>{' '}
<span style={{ color: T.gray }}>\</span>
</div>
<div className='pl-5'>
<span style={{ color: T.gray }}>-H</span>{' '}
<span style={{ color: T.yellow }}>"Authorization: Bearer sk-..."</span>{' '}
<span style={{ color: T.gray }}>\</span>
</div>
<div className='pl-5'>
<span style={{ color: T.gray }}>-d</span>{' '}
<span style={{ color: T.yellow }}>{"'{ \"model\": \"gpt-4o\", \"messages\": [...] }'"}</span>
</div>
<div className='mt-5 border-t pt-4' style={{ borderColor: T.border }}>
<div className='flex items-center gap-2 mb-3'>
<span className='inline-block size-1.5 rounded-full' style={{ backgroundColor: T.green }} />
<span className='text-[11px] font-medium' style={{ color: T.gray }}>200 OK</span>
<span className='text-[11px]' style={{ color: T.gray }}>-- 312ms</span>
<span className='text-[11px]' style={{ color: T.gray }}>312ms</span>
</div>
<div className='mt-2'>
<div>
<span style={{ color: T.gray }}>{"{"}</span>{' '}
<span style={{ color: T.accent }}>"model"</span>
<span style={{ color: T.gray }}>:</span>{' '}
@@ -162,68 +174,59 @@ export function Hero(props: HeroProps) {
<span style={{ color: T.gray }}>,</span>{' '}
<span style={{ color: T.accent }}>"usage"</span>
<span style={{ color: T.gray }}>:</span>{' '}
<span style={{ color: T.gray }}>{"{...}"}</span>
<span style={{ color: T.gray }}>{"{ \"prompt_tokens\": 12, \"completion_tokens\": 47 }"}</span>
<span style={{ color: T.gray }}>{"}"}</span>
</div>
<div className='mt-3'>
<span style={{ color: T.green }}>$</span>
<span className='ml-1 inline-block h-3.5 w-[7px] animate-pulse align-middle' style={{ backgroundColor: T.green }} />
</div>
</div>
<div className='mt-3'>
<span style={{ color: T.accent }}>$</span>
<span className='ml-1 inline-block h-3.5 w-[7px] animate-pulse align-middle' style={{ backgroundColor: T.accent }} />
</div>
</div>
</div>
</div>
{/* ── Features ── */}
<div className='pb-24'>
<div className='mb-16 text-center'>
<h2 className='text-xl font-semibold tracking-tight sm:text-2xl' style={{ color: T.white }}>
<div className='pb-28'>
<div className='mb-14 text-center'>
<h2 className='text-2xl font-semibold tracking-tight' style={{ color: T.white }}>
{t('Full-featured gateway')}
</h2>
<p className='mt-2 text-sm' style={{ color: T.grayLight }}>
<p className='mt-3 text-sm' style={{ color: T.grayLight }}>
{t('Everything you need to manage, route, and monitor LLM API traffic')}
</p>
</div>
<div className='space-y-6'>
{features.map((feature, i) => (
<div className='grid gap-5 sm:grid-cols-2 lg:grid-cols-3'>
{features.map((feature) => (
<div
key={feature.title}
className='grid items-center gap-6 rounded-lg border p-6 transition-colors duration-200 lg:grid-cols-2 lg:gap-10'
className='group rounded-xl border p-6 transition-all duration-200'
style={{
borderColor: T.border,
backgroundColor: T.surface,
}}
onMouseEnter={(e) => {
e.currentTarget.style.borderColor = T.accentDim
e.currentTarget.style.transform = 'translateY(-2px)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = T.border
e.currentTarget.style.transform = 'translateY(0)'
}}
>
{/* Text side */}
<div className={i % 2 === 1 ? 'lg:order-2' : ''}>
<div
className='mb-3 flex size-9 items-center justify-center rounded-md'
style={{ backgroundColor: `${T.accent}15` }}
>
<feature.icon className='size-[18px]' style={{ color: T.accent }} />
</div>
<h3 className='text-base font-semibold' style={{ color: T.white }}>
{t(feature.title)}
</h3>
<p className='mt-1.5 text-sm leading-relaxed' style={{ color: T.grayLight }}>
{t(feature.desc)}
</p>
</div>
{/* Code side */}
<div
className={`overflow-hidden rounded-md border p-4 font-mono text-[12px] leading-[1.7] ${i % 2 === 1 ? 'lg:order-1' : ''}`}
style={{ borderColor: T.border, backgroundColor: T.bg }}
className='mb-4 flex size-10 items-center justify-center rounded-lg'
style={{ backgroundColor: `${T.accent}12` }}
>
<CodeBlock code={feature.code} />
<feature.icon className='size-5' style={{ color: T.accent }} />
</div>
<h3 className='text-sm font-semibold' style={{ color: T.white }}>
{t(feature.title)}
</h3>
<p className='mt-2 text-[13px] leading-relaxed' style={{ color: T.gray }}>
{t(feature.desc)}
</p>
</div>
))}
</div>
@@ -232,78 +235,3 @@ export function Hero(props: HeroProps) {
</section>
)
}
/** Simple syntax-highlighted code block */
function CodeBlock({ code }: { code: string }) {
const keywords = new Set(['const', 'let', 'var', 'function', 'return', 'new', 'await', 'async', 'import', 'from', 'export', 'default'])
const strings = /"([^"]*)"|'([^']*)'/
return (
<>
{code.split('\n').map((line, i) => (
<div key={i}>
{tokenize(line, keywords, strings)}
</div>
))}
</>
)
}
function tokenize(line: string, keywords: Set<string>, _stringRe: RegExp) {
// Comment line
if (line.trimStart().startsWith('//')) {
return <span style={{ color: T.gray }}>{line}</span>
}
// Split into: strings | keywords | punctuation | rest
const tokens: React.ReactNode[] = []
let remaining = line
let key = 0
while (remaining.length > 0) {
// Try string match
const strMatch = remaining.match(/^"([^"]*)"|^'([^']*)'/)
if (strMatch) {
const full = strMatch[0]
tokens.push(<span key={key++} style={{ color: T.yellow }}>{full}</span>)
remaining = remaining.slice(full.length)
continue
}
// Try word
const wordMatch = remaining.match(/^([a-zA-Z_$][\w$]*)/)
if (wordMatch) {
const word = wordMatch[1]
if (keywords.has(word)) {
tokens.push(<span key={key++} style={{ color: T.accent }}>{word}</span>)
} else if (/^[A-Z]/.test(word)) {
tokens.push(<span key={key++} style={{ color: T.accent }}>{word}</span>)
} else {
tokens.push(<span key={key++} style={{ color: T.white }}>{word}</span>)
}
remaining = remaining.slice(word.length)
continue
}
// Try number
const numMatch = remaining.match(/^\d+/)
if (numMatch) {
tokens.push(<span key={key++} style={{ color: T.yellow }}>{numMatch[0]}</span>)
remaining = remaining.slice(numMatch[0].length)
continue
}
// Punctuation / operators
const ch = remaining[0]
if ('(){}[].:;,'.includes(ch)) {
tokens.push(<span key={key++} style={{ color: T.grayLight }}>{ch}</span>)
} else if ('=<>!+-*/%&|?'.includes(ch)) {
tokens.push(<span key={key++} style={{ color: T.accent }}>{ch}</span>)
} else {
tokens.push(<span key={key++} style={{ color: T.white }}>{ch}</span>)
}
remaining = remaining.slice(1)
}
return <>{tokens}</>
}
+2
View File
@@ -959,6 +959,8 @@
"Route requests across OpenAI, Claude, Gemini, and 30+ providers through a single endpoint.": "Route requests across OpenAI, Claude, Gemini, and 30+ providers through a single endpoint.",
"Track usage, costs, and performance with live analytics dashboards.": "Track usage, costs, and performance with live analytics dashboards.",
"Unified LLM Gateway": "Unified LLM Gateway",
"Unified LLM": "Unified LLM",
"Gateway": "Gateway",
"Copied {{count}} key(s)": "Copied {{count}} key(s)",
"Copied to clipboard": "Copied to clipboard",
"Copied: {{model}}": "Copied: {{model}}",
+2
View File
@@ -959,6 +959,8 @@
"Route requests across OpenAI, Claude, Gemini, and 30+ providers through a single endpoint.": "通过单一端点将请求路由至 OpenAI、Claude、Gemini 等 30+ 供应商。",
"Track usage, costs, and performance with live analytics dashboards.": "通过实时分析仪表板追踪用量、成本和性能。",
"Unified LLM Gateway": "统一大模型网关",
"Unified LLM": "统一大模型",
"Gateway": "网关",
"Copied {{count}} key(s)": "已复制 {{count}} 个密钥",
"Copied to clipboard": "已复制到剪贴板",
"Copied: {{model}}": "已复制: {{model}}",