Commit 3909a33b authored by 陈曦's avatar 陈曦
Browse files

1.去除自动migration 2.前端home、login改变界面风格 3.调试pay

parent ef5c8e68
......@@ -11,8 +11,6 @@ import (
"github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/timezone"
"github.com/Wei-Shaw/sub2api/migrations"
"entgo.io/ent/dialect"
entsql "entgo.io/ent/dialect/sql"
_ "github.com/lib/pq" // PostgreSQL 驱动,通过副作用导入注册驱动
......@@ -57,17 +55,19 @@ func InitEnt(cfg *config.Config) (*ent.Client, *sql.DB, error) {
// 确保数据库 schema 已准备就绪。
// SQL 迁移文件是 schema 的权威来源(source of truth)。
// 这种方式比 Ent 的自动迁移更可控,支持复杂的迁移场景。
migrationCtx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
if err := applyMigrationsFS(migrationCtx, drv.DB(), migrations.FS); err != nil {
_ = drv.Close() // 迁移失败时关闭驱动,避免资源泄露
return nil, nil, err
}
// migrationCtx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
// defer cancel()
// if err := applyMigrationsFS(migrationCtx, drv.DB(), migrations.FS); err != nil {
// _ = drv.Close() // 迁移失败时关闭驱动,避免资源泄露
// return nil, nil, err
// }
// 创建 Ent 客户端,绑定到已配置的数据库驱动。
client := ent.NewClient(ent.Driver(drv))
// 启动阶段:从配置或数据库中确保系统密钥可用。
migrationCtx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
if err := ensureBootstrapSecrets(migrationCtx, client, cfg); err != nil {
_ = client.Close()
return nil, nil, err
......
......@@ -14,7 +14,6 @@ import (
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/Wei-Shaw/sub2api/internal/repository"
"github.com/Wei-Shaw/sub2api/internal/service"
_ "github.com/lib/pq"
......@@ -345,9 +344,10 @@ func initializeDatabase(cfg *SetupConfig) error {
}
}()
migrationCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
return repository.ApplyMigrations(migrationCtx, db)
// migrationCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
// defer cancel()
// return repository.ApplyMigrations(migrationCtx, db)
return nil
}
func createAdminUser(cfg *SetupConfig) (bool, string, error) {
......
<template>
<div class="relative flex min-h-screen items-center justify-center overflow-hidden p-4">
<!-- Background -->
<div
class="absolute inset-0 bg-gradient-to-br from-gray-50 via-primary-50/30 to-gray-100 dark:from-dark-950 dark:via-dark-900 dark:to-dark-950"
></div>
<div class="auth-root relative flex min-h-screen items-center justify-center overflow-hidden p-4">
<!-- Decorative Elements -->
<!-- Background -->
<div class="pointer-events-none absolute inset-0 overflow-hidden">
<!-- Gradient Orbs -->
<div
class="absolute -right-40 -top-40 h-80 w-80 rounded-full bg-primary-400/20 blur-3xl"
></div>
<div
class="absolute -bottom-40 -left-40 h-80 w-80 rounded-full bg-primary-500/15 blur-3xl"
></div>
<div
class="absolute left-1/2 top-1/2 h-96 w-96 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-300/10 blur-3xl"
></div>
<!-- Grid Pattern -->
<div
class="absolute inset-0 bg-[linear-gradient(rgba(20,184,166,0.03)_1px,transparent_1px),linear-gradient(90deg,rgba(20,184,166,0.03)_1px,transparent_1px)] bg-[size:64px_64px]"
></div>
<div class="auth-orb auth-orb-tr"></div>
<div class="auth-orb auth-orb-bl"></div>
<div class="auth-grid"></div>
<div class="auth-scan"></div>
</div>
<!-- Content Container -->
<!-- Content -->
<div class="relative z-10 w-full max-w-md">
<!-- Logo/Brand -->
<!-- Brand -->
<div class="mb-8 text-center">
<!-- Custom Logo or Default Logo -->
<template v-if="settingsLoaded">
<div
class="mb-4 inline-flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl shadow-lg shadow-primary-500/30"
>
<!-- Logo -->
<div class="auth-logo-wrap mb-5 inline-flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl">
<img :src="siteLogo || '/logo.png'" alt="Logo" class="h-full w-full object-contain" />
</div>
<h1 class="text-gradient mb-2 text-3xl font-bold">
<!-- Site name -->
<h1 class="auth-site-name mb-2 text-3xl font-black tracking-tight">
{{ siteName }}
</h1>
<p class="text-sm text-gray-500 dark:text-dark-400">
{{ siteSubtitle }}
<!-- Subtitle -->
<p class="auth-subtitle font-mono text-xs uppercase tracking-widest">
// {{ siteSubtitle }}
</p>
</template>
</div>
<!-- Card Container -->
<div class="card-glass rounded-2xl p-8 shadow-glass">
<!-- Card -->
<div class="auth-card rounded-2xl p-8">
<slot />
</div>
<!-- Footer Links -->
<!-- Footer links -->
<div class="mt-6 text-center text-sm">
<slot name="footer" />
</div>
<!-- Copyright -->
<div class="mt-8 text-center text-xs text-gray-400 dark:text-dark-500">
<div class="mt-8 text-center font-mono text-xs text-slate-600">
&copy; {{ currentYear }} {{ siteName }}. All rights reserved.
</div>
</div>
......@@ -69,9 +57,9 @@ import { sanitizeUrl } from '@/utils/url'
const appStore = useAppStore()
const siteName = computed(() => appStore.siteName || 'Sub2API')
const siteName = computed(() => appStore.siteName || 'TrafficAPI')
const siteLogo = computed(() => sanitizeUrl(appStore.siteLogo || '', { allowRelative: true, allowDataUrl: true }))
const siteSubtitle = computed(() => appStore.cachedPublicSettings?.site_subtitle || 'Subscription to API Conversion Platform')
const siteSubtitle = computed(() => appStore.cachedPublicSettings?.site_subtitle || 'Intelligent AI Traffic Routing & Management')
const settingsLoaded = computed(() => appStore.publicSettingsLoaded)
const currentYear = computed(() => new Date().getFullYear())
......@@ -82,7 +70,108 @@ onMounted(() => {
</script>
<style scoped>
.text-gradient {
@apply bg-gradient-to-r from-primary-600 to-primary-500 bg-clip-text text-transparent;
/* ── Root ────────────────────────────────── */
.auth-root {
background: #182a40;
color: white;
}
/* ── Background layers ───────────────────── */
.auth-orb {
position: absolute;
border-radius: 50%;
filter: blur(90px);
pointer-events: none;
}
.auth-orb-tr {
top: -10%;
right: -10%;
width: 420px;
height: 420px;
background: radial-gradient(ellipse, rgba(20,184,166,0.18) 0%, transparent 70%);
}
.auth-orb-bl {
bottom: -10%;
left: -10%;
width: 380px;
height: 380px;
background: radial-gradient(ellipse, rgba(6,182,212,0.13) 0%, transparent 70%);
}
.auth-grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(20,184,166,0.07) 1px, transparent 1px),
linear-gradient(90deg, rgba(20,184,166,0.07) 1px, transparent 1px);
background-size: 48px 48px;
}
.auth-scan {
position: absolute;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent 0%, rgba(20,184,166,0.45) 30%, rgba(20,184,166,0.7) 50%, rgba(20,184,166,0.45) 70%, transparent 100%);
animation: auth-scan 12s linear infinite;
box-shadow: 0 0 8px rgba(20,184,166,0.35);
}
@keyframes auth-scan {
0% { top: 0%; opacity: 0; }
4% { opacity: 1; }
96% { opacity: 0.4; }
100% { top: 100%; opacity: 0; }
}
/* ── Logo ────────────────────────────────── */
.auth-logo-wrap {
box-shadow:
0 0 0 1px rgba(20,184,166,0.3),
0 0 20px rgba(20,184,166,0.18),
0 8px 24px rgba(0,0,0,0.4);
}
/* ── Brand text ──────────────────────────── */
.auth-site-name {
background: linear-gradient(135deg, #ffffff 0%, #7dd3c8 45%, #14b8a6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.auth-subtitle {
color: rgba(20,184,166,0.5);
}
/* ── Card ────────────────────────────────── */
.auth-card {
background: rgba(26,46,72,0.72);
border: 1px solid rgba(255,255,255,0.09);
backdrop-filter: blur(18px);
box-shadow:
0 0 0 1px rgba(20,184,166,0.08),
0 24px 60px rgba(0,0,0,0.45),
0 0 50px rgba(20,184,166,0.05);
}
/* ── Slot overrides: make child inputs/links readable ── */
:deep(.input-label) {
color: #94a3b8;
}
:deep(.input) {
background: rgba(15,28,48,0.7);
border-color: rgba(255,255,255,0.1);
color: white;
}
:deep(.input:focus) {
border-color: rgba(20,184,166,0.5);
box-shadow: 0 0 0 3px rgba(20,184,166,0.12);
}
:deep(.input::placeholder) {
color: #475569;
}
:deep(h2) {
color: white !important;
}
:deep(.text-gray-500),
:deep(.dark\:text-dark-400) {
color: #64748b !important;
}
</style>
......@@ -10,89 +10,89 @@ export default {
login: 'Login',
getStarted: 'Get Started',
goToDashboard: 'Go to Dashboard',
// User-focused value proposition
heroSubtitle: 'One Key, All AI Models',
heroDescription: 'No need to manage multiple subscriptions. Access Claude, GPT, Gemini and more with a single API key',
// TrafficAPI value proposition
heroSubtitle: 'Route Smarter. Scale Faster.',
heroDescription: 'TrafficAPI is the intelligent AI API gateway that routes your requests across Claude, GPT, Gemini and more with automatic load balancing, zero downtime, and real-time traffic analytics.',
tags: {
subscriptionToApi: 'Subscription to API',
stickySession: 'Session Persistence',
realtimeBilling: 'Pay As You Go'
subscriptionToApi: 'Intelligent Routing',
stickySession: 'Auto Load Balancing',
realtimeBilling: 'Real-time Analytics'
},
// Pain points section
painPoints: {
title: 'Sound Familiar?',
items: {
expensive: {
title: 'High Subscription Costs',
desc: 'Paying for multiple AI subscriptions that add up every month'
title: 'Fragmented AI Costs',
desc: 'Paying for multiple AI subscriptions that add up every month with no unified control'
},
complex: {
title: 'Account Chaos',
desc: 'Managing scattered accounts and API keys across different platforms'
title: 'Multi-Provider Chaos',
desc: 'Juggling different SDKs, credentials, and endpoints across Claude, GPT, Gemini and more'
},
unstable: {
title: 'Service Interruptions',
desc: 'Single accounts hitting rate limits and disrupting your workflow'
title: 'Rate Limit Downtime',
desc: 'A single upstream account hitting limits can bring your entire application offline'
},
noControl: {
title: 'No Usage Control',
desc: "Can't track where your money goes or limit team member usage"
title: 'Zero Traffic Visibility',
desc: "No insight into request volume, latency, or spend across providers and team members"
}
}
},
// Solutions section
solutions: {
title: 'We Solve These Problems',
subtitle: 'Three simple steps to stress-free AI access'
title: 'TrafficAPI Solves This',
subtitle: 'One gateway to route, balance, and monitor all your AI traffic'
},
features: {
unifiedGateway: 'One-Click Access',
unifiedGatewayDesc: 'Get a single API key to call all connected AI models. No separate applications needed.',
multiAccount: 'Always Reliable',
multiAccountDesc: 'Smart routing across multiple upstream accounts with automatic failover. Say goodbye to errors.',
balanceQuota: 'Pay What You Use',
balanceQuotaDesc: 'Usage-based billing with quota limits. Full visibility into team consumption.'
unifiedGateway: 'Unified API Endpoint',
unifiedGatewayDesc: 'One endpoint, one key — access every connected AI provider. No SDK changes, no credential juggling, zero migration friction.',
multiAccount: 'Smart Load Balancing',
multiAccountDesc: 'Traffic is distributed intelligently across multiple upstream accounts. Automatic failover ensures zero downtime even when upstream limits are hit.',
balanceQuota: 'Traffic Analytics',
balanceQuotaDesc: 'Monitor request volume, response latency, and per-model costs in real time. Set spending quotas per user or team with granular control.'
},
// Comparison section
comparison: {
title: 'Why Choose Us?',
title: 'Why TrafficAPI?',
headers: {
feature: 'Comparison',
official: 'Official Subscriptions',
us: 'Our Platform'
feature: 'Feature',
official: 'Direct Provider',
us: 'TrafficAPI'
},
items: {
pricing: {
feature: 'Pricing',
official: 'Fixed monthly fee, pay even if unused',
us: 'Pay only for what you use'
official: 'Fixed monthly fee per provider',
us: 'Pay per request, unified billing'
},
models: {
feature: 'Model Selection',
feature: 'Model Access',
official: 'Single provider only',
us: 'Switch between models freely'
us: 'Any model, one endpoint'
},
management: {
feature: 'Account Management',
official: 'Manage each service separately',
us: 'Unified key, one dashboard'
official: 'Each service managed separately',
us: 'Unified dashboard, one API key'
},
stability: {
feature: 'Stability',
official: 'Single account rate limits',
feature: 'Reliability',
official: 'Single account, no failover',
us: 'Multi-account pool, auto-failover'
},
control: {
feature: 'Usage Control',
feature: 'Traffic Control',
official: 'Not available',
us: 'Quotas & detailed analytics'
us: 'Quotas, rate limits & analytics'
}
}
},
providers: {
title: 'Supported AI Models',
description: 'One API, Multiple Choices',
supported: 'Supported',
title: 'Supported AI Providers',
description: 'One Gateway, Every Model',
supported: 'Live',
soon: 'Soon',
claude: 'Claude',
gemini: 'Gemini',
......@@ -101,9 +101,9 @@ export default {
},
// CTA section
cta: {
title: 'Ready to Get Started?',
description: 'Sign up now and get free trial credits to experience seamless AI access',
button: 'Sign Up Free'
title: 'Start Routing Your AI Traffic',
description: 'Join teams who use TrafficAPI to unify their AI access, eliminate downtime, and gain full visibility into usage.',
button: 'Get Started Free'
},
footer: {
allRightsReserved: 'All rights reserved.'
......@@ -182,8 +182,8 @@ export default {
// Setup Wizard
setup: {
title: 'Sub2API Setup',
description: 'Configure your Sub2API instance',
title: 'TrafficAPI Setup',
description: 'Configure your TrafficAPI instance',
database: {
title: 'Database Configuration',
description: 'Connect to your PostgreSQL database',
......@@ -357,8 +357,8 @@ export default {
// Auth
auth: {
welcomeBack: 'Welcome Back',
signInToAccount: 'Sign in to your account to continue',
welcomeBack: 'Welcome Back to TrafficAPI',
signInToAccount: 'Sign in to manage your AI traffic, keys, and analytics',
signIn: 'Sign In',
signingIn: 'Signing in...',
createAccount: 'Create Account',
......@@ -1085,7 +1085,7 @@ export default {
step1: {
title: 'Create an R2 Bucket',
line1: 'Log in to the Cloudflare Dashboard (dash.cloudflare.com), select "R2 Object Storage" from the sidebar',
line2: 'Click "Create bucket", enter a name (e.g. sub2api-backups), choose a region',
line2: 'Click "Create bucket", enter a name (e.g. trafficapi-backups), choose a region',
line3: 'Click create to finish'
},
step2: {
......@@ -1934,7 +1934,7 @@ export default {
antigravityOauth: 'Antigravity OAuth',
antigravityApikey: 'Connect via Base URL + API Key',
soraApiKey: 'API Key / Upstream',
soraApiKeyHint: 'Connect to another Sub2API or compatible API',
soraApiKeyHint: 'Connect to another TrafficAPI or compatible API',
soraBaseUrlRequired: 'Sora API Key account requires a Base URL',
soraBaseUrlInvalidScheme: 'Base URL must start with http:// or https://',
upstream: 'Upstream',
......@@ -2235,7 +2235,7 @@ export default {
poolMode: 'Pool Mode',
poolModeHint: 'Enable when upstream is an account pool; errors won\'t mark local account status',
poolModeInfo:
'When enabled, upstream 429/403/401 errors will auto-retry without marking the account as rate-limited or errored. Suitable for upstream pointing to another sub2api instance.',
'When enabled, upstream 429/403/401 errors will auto-retry without marking the account as rate-limited or errored. Suitable for upstream pointing to another trafficapi instance.',
poolModeRetryCount: 'Same-Account Retries',
poolModeRetryCountHint:
'Only applies in pool mode. Use 0 to disable in-place retry. Default {default}, maximum {max}.',
......@@ -2743,7 +2743,7 @@ export default {
geminiImageTestMode: 'Mode: Gemini image generation test',
geminiImagePreview: 'Generated images:',
geminiImageReceived: 'Received test image #{count}',
soraUpstreamBaseUrlHint: 'Upstream Sora service URL (another Sub2API instance or compatible API)',
soraUpstreamBaseUrlHint: 'Upstream Sora service URL (another TrafficAPI instance or compatible API)',
soraTestHint: 'Sora test runs connectivity and capability checks (/backend/me, subscription, Sora2 invite and remaining quota).',
soraTestTarget: 'Target: Sora account capability',
soraTestMode: 'Mode: Connectivity + Capability checks',
......@@ -4123,7 +4123,7 @@ export default {
secretKeyConfiguredHint: 'Secret key configured. Leave empty to keep the current value.' },
linuxdo: {
title: 'LinuxDo Connect Login',
description: 'Configure LinuxDo Connect OAuth for Sub2API end-user login',
description: 'Configure LinuxDo Connect OAuth for TrafficAPI end-user login',
enable: 'Enable LinuxDo Login',
enableHint: 'Show LinuxDo login on the login/register pages',
clientId: 'Client ID',
......@@ -4190,7 +4190,7 @@ export default {
backendModeDescription:
'Disables user registration, public site, and self-service features. Only admin can log in and manage the platform.',
siteName: 'Site Name',
siteNamePlaceholder: 'Sub2API',
siteNamePlaceholder: 'TrafficAPI',
siteNameHint: 'Displayed in emails and page titles',
siteSubtitle: 'Site Subtitle',
siteSubtitlePlaceholder: 'Subscription to API Conversion Platform',
......@@ -4290,7 +4290,7 @@ export default {
fromEmail: 'From Email',
fromEmailPlaceholder: "noreply{'@'}example.com",
fromName: 'From Name',
fromNamePlaceholder: 'Sub2API',
fromNamePlaceholder: 'TrafficAPI',
useTls: 'Use TLS',
useTlsHint: 'Enable TLS encryption for SMTP connection'
},
......@@ -4721,14 +4721,14 @@ export default {
// Admin tour steps
admin: {
welcome: {
title: '👋 Welcome to Sub2API',
description: '<div style="line-height: 1.8;"><p style="margin-bottom: 16px;">Sub2API is a powerful AI service gateway platform that helps you easily manage and distribute AI services.</p><p style="margin-bottom: 12px;"><b>🎯 Core Features:</b></p><ul style="margin-left: 20px; margin-bottom: 16px;"><li>📦 <b>Group Management</b> - Create service tiers (VIP, Free Trial, etc.)</li><li>🔗 <b>Account Pool</b> - Connect multiple upstream AI service accounts</li><li>🔑 <b>Key Distribution</b> - Generate independent API Keys for users</li><li>💰 <b>Billing Control</b> - Flexible rate and quota management</li></ul><p style="color: #10b981; font-weight: 600;">Let\'s complete the initial setup in 3 minutes →</p></div>',
title: '👋 Welcome to TrafficAPI',
description: '<div style="line-height: 1.8;"><p style="margin-bottom: 16px;">TrafficAPI is your intelligent AI traffic gateway — route, balance, and monitor requests across every major AI provider from a single control plane.</p><p style="margin-bottom: 12px;"><b>🎯 Core Capabilities:</b></p><ul style="margin-left: 20px; margin-bottom: 16px;"><li>📦 <b>Traffic Groups</b> - Define service tiers with routing policies (VIP, Free Trial, etc.)</li><li>🔗 <b>Account Pool</b> - Connect multiple upstream accounts for load balancing & failover</li><li>🔑 <b>API Key Distribution</b> - Issue independent keys per user with quota controls</li><li>📊 <b>Traffic Analytics</b> - Real-time visibility into requests, latency, and costs</li></ul><p style="color: #10b981; font-weight: 600;">Let\'s complete the initial setup in 3 minutes →</p></div>',
nextBtn: 'Start Setup 🚀',
prevBtn: 'Skip'
},
groupManage: {
title: '📦 Step 1: Group Management',
description: '<div style="line-height: 1.7;"><p style="margin-bottom: 12px;"><b>What is a Group?</b></p><p style="margin-bottom: 12px;">Groups are the core concept of Sub2API, like a "service package":</p><ul style="margin-left: 20px; margin-bottom: 12px; font-size: 13px;"><li>🎯 Each group can contain multiple upstream accounts</li><li>💰 Each group has independent billing multiplier</li><li>👥 Can be set as public or exclusive</li></ul><p style="margin-top: 12px; padding: 8px 12px; background: #f0fdf4; border-left: 3px solid #10b981; border-radius: 4px; font-size: 13px;"><b>💡 Example:</b> You can create "VIP Premium" (high rate) and "Free Trial" (low rate) groups</p><p style="margin-top: 16px; color: #10b981; font-weight: 600;">👉 Click "Group Management" on the left sidebar</p></div>'
description: '<div style="line-height: 1.7;"><p style="margin-bottom: 12px;"><b>What is a Group?</b></p><p style="margin-bottom: 12px;">Groups are the core concept of TrafficAPI, like a "service package":</p><ul style="margin-left: 20px; margin-bottom: 12px; font-size: 13px;"><li>🎯 Each group can contain multiple upstream accounts</li><li>💰 Each group has independent billing multiplier</li><li>👥 Can be set as public or exclusive</li></ul><p style="margin-top: 12px; padding: 8px 12px; background: #f0fdf4; border-left: 3px solid #10b981; border-radius: 4px; font-size: 13px;"><b>💡 Example:</b> You can create "VIP Premium" (high rate) and "Free Trial" (low rate) groups</p><p style="margin-top: 16px; color: #10b981; font-weight: 600;">👉 Click "Group Management" on the left sidebar</p></div>'
},
createGroup: {
title: '➕ Create New Group',
......@@ -4821,8 +4821,8 @@ export default {
// User tour steps
user: {
welcome: {
title: '👋 Welcome to Sub2API',
description: '<div style="line-height: 1.8;"><p style="margin-bottom: 16px;">Hello! Welcome to the Sub2API AI service platform.</p><p style="margin-bottom: 12px;"><b>🎯 Quick Start:</b></p><ul style="margin-left: 20px; margin-bottom: 16px;"><li>🔑 Create API Key</li><li>📋 Copy key to your application</li><li>🚀 Start using AI services</li></ul><p style="color: #10b981; font-weight: 600;">Just 1 minute, let\'s get started →</p></div>',
title: '👋 Welcome to TrafficAPI',
description: '<div style="line-height: 1.8;"><p style="margin-bottom: 16px;">Hello! Welcome to TrafficAPI — the intelligent gateway for routing your AI API traffic across Claude, GPT, Gemini and more.</p><p style="margin-bottom: 12px;"><b>🎯 Quick Start:</b></p><ul style="margin-left: 20px; margin-bottom: 16px;"><li>🔑 Create your API Key</li><li>📋 Use it with any OpenAI-compatible client</li><li>📊 Monitor traffic & usage in real time</li></ul><p style="color: #10b981; font-weight: 600;">Just 1 minute, let\'s get started →</p></div>',
nextBtn: 'Start 🚀',
prevBtn: 'Skip'
},
......
......@@ -10,90 +10,90 @@ export default {
login: '登录',
getStarted: '立即开始',
goToDashboard: '进入控制台',
// 新增:面向用户的价值主张
heroSubtitle: '一个密钥,畅用多个 AI 模型',
heroDescription: '无需管理多个订阅账号,一站式接入 Claude、GPT、Gemini 等主流 AI 服务',
// TrafficAPI 价值主张
heroSubtitle: '智能路由,极速扩展。',
heroDescription: 'TrafficAPI 是面向企业的智能 AI API 网关,自动将请求路由至 Claude、GPT、Gemini 等多个服务商,具备负载均衡、零停机故障转移与实时流量分析能力。',
tags: {
subscriptionToApi: '订阅转 API',
stickySession: '会话保持',
realtimeBilling: '按量计费'
subscriptionToApi: '智能请求路由',
stickySession: '自动负载均衡',
realtimeBilling: '实时流量分析'
},
// 用户痛点区块
painPoints: {
title: '你是否也遇到这些问题?',
items: {
expensive: {
title: '订阅费用高',
desc: '个 AI 服务都要单独订阅,每月支出越来越多'
title: 'AI 成本分散难控',
desc: '个 AI 服务各自订阅、各自付费,没有统一管控,月度支出节节攀升'
},
complex: {
title: '账号难管理',
desc: '不同平台的账号、密钥分散各处,管理起来很麻烦'
title: '服务商集成混乱',
desc: '不同平台的 SDK、凭证和接口地址各不相同,集成和维护成本极高'
},
unstable: {
title: '服务不稳定',
desc: '单一账号容易触发限制,影响正常使用'
title: '限流导致服务中断',
desc: '单一上游账号触发限流,便可导致整个应用离线,严重影响业务可用性'
},
noControl: {
title: '用量无法控制',
desc: '不知道钱花在哪了,也无法限制团队成员的使用'
title: '流量完全不透明',
desc: '无从了解各服务商的请求量、延迟分布与实际费用,团队用量更是无法管控'
}
}
},
// 解决方案区块
solutions: {
title: '我们帮你解决',
subtitle: '简单三步,开始省心使用 AI'
title: 'TrafficAPI 为你解决',
subtitle: '统一网关,路由、均衡、监控 AI 流量全掌握'
},
features: {
unifiedGateway: '一键接入',
unifiedGatewayDesc: '获取一个 API 密钥,即可调用所有已接入的 AI 模型,无需分别申请',
multiAccount: '稳定可靠',
multiAccountDesc: '智能调度多个上游账号,自动切换和负载均衡,告别频繁报错',
balanceQuota: '用多少付多少',
balanceQuotaDesc: '按实际使用量计费,支持设置配额上限,团队用量一目了然'
unifiedGateway: '统一 API 接入',
unifiedGatewayDesc: '一个接入地址,一个 API Key,即可调用所有已接入的 AI 服务商。无需更改 SDK,无需管理多套凭证,迁移零成本',
multiAccount: '智能负载均衡',
multiAccountDesc: '流量自动分发至多个上游账号,智能调度与自动故障转移确保服务 7×24 小时在线,彻底告别限流宕机',
balanceQuota: '流量分析与管控',
balanceQuotaDesc: '实时监控请求量、响应延迟与各模型费用,按用户或团队设置消费配额,AI 流量全链路可视化、可管控'
},
// 优势对比
comparison: {
title: '为什么选择我们',
title: '为什么选择 TrafficAPI',
headers: {
feature: '对比',
official: '官方订阅',
us: '本平台'
feature: '对比维度',
official: '直连服务商',
us: 'TrafficAPI'
},
items: {
pricing: {
feature: '费方式',
official: '固定月费,用不完也付',
us: '量付费,用多少付多少'
feature: '费方式',
official: '各服务商固定月费',
us: '请求计费,统一结算'
},
models: {
feature: '模型选择',
feature: '模型覆盖',
official: '单一服务商',
us: '多模型随意切换'
us: '任意模型,统一入口'
},
management: {
feature: '账号管理',
official: '每个服务单独管理',
us: '统一密钥,一站管理'
official: '各服务分散管理',
us: '统一控制台,一个 Key'
},
stability: {
feature: '服务稳定',
official: '单账号易触发限制',
us: '多账号池,自动切换'
feature: '服务可靠',
official: '单账号,无故障转移',
us: '多账号池,自动故障转移'
},
control: {
feature: '用量控制',
official: '无法限制',
us: '可设配额、查明细'
feature: '流量管控',
official: '不支持',
us: '配额限制 + 详细分析'
}
}
},
providers: {
title: '已支持的 AI 模型',
description: '一个 API,多种选择',
supported: '支持',
soon: '即将推出',
title: '已支持的 AI 服务商',
description: '一个网关,接入所有模型',
supported: '上线',
soon: '即将支持',
claude: 'Claude',
gemini: 'Gemini',
antigravity: 'Antigravity',
......@@ -101,9 +101,9 @@ export default {
},
// CTA 区块
cta: {
title: '准备好开始了吗?',
description: '注册即可获得免费试用额度,体验一站式 AI 服务',
button: '免费注册'
title: '开始路由你的 AI 流量',
description: '加入正在使用 TrafficAPI 的团队,统一 AI 接入、消除停机风险、获得完整的流量可见性。',
button: '免费开始使用'
},
footer: {
allRightsReserved: '保留所有权利。'
......@@ -182,8 +182,8 @@ export default {
// Setup Wizard
setup: {
title: 'Sub2API 安装向导',
description: '配置您的 Sub2API 实例',
title: 'TrafficAPI 安装向导',
description: '配置您的 TrafficAPI 实例',
database: {
title: '数据库配置',
description: '连接到您的 PostgreSQL 数据库',
......@@ -357,8 +357,8 @@ export default {
// Auth
auth: {
welcomeBack: '欢迎回',
signInToAccount: '登录您的账户以继续',
welcomeBack: '欢迎回到 TrafficAPI',
signInToAccount: '登录以管理您的 AI 流量、密钥与数据分析',
signIn: '登录',
signingIn: '登录中...',
createAccount: '创建账户',
......@@ -1107,7 +1107,7 @@ export default {
step1: {
title: '创建 R2 存储桶',
line1: '登录 Cloudflare Dashboard (dash.cloudflare.com),左侧菜单选择「R2 对象存储」',
line2: '点击「创建存储桶」,输入名称(如 sub2api-backups),选择区域',
line2: '点击「创建存储桶」,输入名称(如 trafficapi-backups),选择区域',
line3: '点击创建完成'
},
step2: {
......@@ -2115,7 +2115,7 @@ export default {
antigravityOauth: 'Antigravity OAuth',
antigravityApikey: '通过 Base URL + API Key 连接',
soraApiKey: 'API Key / 上游透传',
soraApiKeyHint: '连接另一个 Sub2API 或兼容 API',
soraApiKeyHint: '连接另一个 TrafficAPI 或兼容 API',
soraBaseUrlRequired: 'Sora apikey 账号必须设置上游地址(Base URL)',
soraBaseUrlInvalidScheme: 'Base URL 必须以 http:// 或 https:// 开头',
upstream: '对接上游',
......@@ -2382,7 +2382,7 @@ export default {
poolMode: '池模式',
poolModeHint: '上游为账号池时启用,错误不标记本地账号状态',
poolModeInfo:
'启用后,上游 429/403/401 错误将自动重试而不标记账号限流或错误,适用于上游指向另一个 sub2api 实例的场景。',
'启用后,上游 429/403/401 错误将自动重试而不标记账号限流或错误,适用于上游指向另一个 trafficapi 实例的场景。',
poolModeRetryCount: '同账号重试次数',
poolModeRetryCountHint: '仅在池模式下生效。0 表示不原地重试;默认 {default},最大 {max}。',
customErrorCodes: '自定义错误码',
......@@ -2874,7 +2874,7 @@ export default {
geminiImageTestMode: '模式:Gemini 生图测试',
geminiImagePreview: '生成结果:',
geminiImageReceived: '已收到第 {count} 张测试图片',
soraUpstreamBaseUrlHint: '上游 Sora 服务地址(另一个 Sub2API 实例或兼容 API)',
soraUpstreamBaseUrlHint: '上游 Sora 服务地址(另一个 TrafficAPI 实例或兼容 API)',
soraTestHint: 'Sora 测试将执行连通性与能力检测(/backend/me、订阅信息、Sora2 邀请码与剩余额度)。',
soraTestTarget: '检测目标:Sora 账号能力',
soraTestMode: '模式:连通性 + 能力探测',
......@@ -4290,7 +4290,7 @@ export default {
},
linuxdo: {
title: 'LinuxDo Connect 登录',
description: '配置 LinuxDo Connect OAuth,用于 Sub2API 用户登录',
description: '配置 LinuxDo Connect OAuth,用于 TrafficAPI 用户登录',
enable: '启用 LinuxDo 登录',
enableHint: '在登录/注册页面显示 LinuxDo 登录入口',
clientId: 'Client ID',
......@@ -4354,7 +4354,7 @@ export default {
'禁用用户注册、公开页面和自助服务功能。仅管理员可以登录和管理平台。',
siteName: '站点名称',
siteNameHint: '显示在邮件和页面标题中',
siteNamePlaceholder: 'Sub2API',
siteNamePlaceholder: 'TrafficAPI',
siteSubtitle: '站点副标题',
siteSubtitleHint: '显示在登录和注册页面',
siteSubtitlePlaceholder: '订阅转 API 转换平台',
......@@ -4455,7 +4455,7 @@ export default {
fromEmail: '发件人邮箱',
fromEmailPlaceholder: "noreply{'@'}example.com",
fromName: '发件人名称',
fromNamePlaceholder: 'Sub2API',
fromNamePlaceholder: 'TrafficAPI',
useTls: '使用 TLS',
useTlsHint: '为 SMTP 连接启用 TLS 加密'
},
......@@ -4883,16 +4883,16 @@ export default {
// Admin tour steps
admin: {
welcome: {
title: '👋 欢迎使用 Sub2API',
title: '👋 欢迎使用 TrafficAPI',
description:
'<div style="line-height: 1.8;"><p style="margin-bottom: 16px;">Sub2API 是一个强大的 AI 服务中转平台,让您轻松管理和分发 AI 服务。</p><p style="margin-bottom: 12px;"><b>🎯 核心能:</b></p><ul style="margin-left: 20px; margin-bottom: 16px;"><li>📦 <b>分组管理</b> - 创建不同的服务套餐(VIP、免费试用等)</li><li>🔗 <b>账号池</b> - 连接多个上游 AI 服务商账号</li><li>🔑 <b>密钥分发</b> - 为用户生成独立 API Key</li><li>💰 <b>计费管理</b> - 灵活的费率和配额控制</li></ul><p style="color: #10b981; font-weight: 600;">接下来,我们将用 3 分钟带您完成首次配置 →</p></div>',
'<div style="line-height: 1.8;"><p style="margin-bottom: 16px;">TrafficAPI 是智能 AI 流量网关,帮助您统一路由、负载均衡并实时监控多个 AI 服务商的请求流量。</p><p style="margin-bottom: 12px;"><b>🎯 核心能:</b></p><ul style="margin-left: 20px; margin-bottom: 16px;"><li>📦 <b>流量分组</b> - 按路由策略创建服务套餐(VIP、免费试用等)</li><li>🔗 <b>账号池</b> - 连接多个上游账号,实现负载均衡与故障转移</li><li>🔑 <b>密钥分发</b> - 为用户生成独立 API Key,支持配额管控</li><li>📊 <b>流量分析</b> - 实时查看请求量、响应延迟与费用</li></ul><p style="color: #10b981; font-weight: 600;">接下来,我们将用 3 分钟带您完成首次配置 →</p></div>',
nextBtn: '开始配置 🚀',
prevBtn: '跳过'
},
groupManage: {
title: '📦 第一步:分组管理',
description:
'<div style="line-height: 1.7;"><p style="margin-bottom: 12px;"><b>什么是分组?</b></p><p style="margin-bottom: 12px;">分组是 Sub2API 的核心概念,它就像一个"服务套餐":</p><ul style="margin-left: 20px; margin-bottom: 12px; font-size: 13px;"><li>🎯 每个分组可以包含多个上游账号</li><li>💰 每个分组有独立的计费倍率</li><li>👥 可以设置为公开或专属分组</li></ul><p style="margin-top: 12px; padding: 8px 12px; background: #f0fdf4; border-left: 3px solid #10b981; border-radius: 4px; font-size: 13px;"><b>💡 示例:</b>您可以创建"VIP专线"(高倍率)和"免费试用"(低倍率)两个分组</p><p style="margin-top: 16px; color: #10b981; font-weight: 600;">👉 点击左侧的"分组管理"开始</p></div>'
'<div style="line-height: 1.7;"><p style="margin-bottom: 12px;"><b>什么是分组?</b></p><p style="margin-bottom: 12px;">分组是 TrafficAPI 的核心流量管理单元,类似一个"服务套餐":</p><ul style="margin-left: 20px; margin-bottom: 12px; font-size: 13px;"><li>🎯 每个分组可以包含多个上游账号</li><li>💰 每个分组有独立的计费倍率</li><li>👥 可以设置为公开或专属分组</li></ul><p style="margin-top: 12px; padding: 8px 12px; background: #f0fdf4; border-left: 3px solid #10b981; border-radius: 4px; font-size: 13px;"><b>💡 示例:</b>您可以创建"VIP专线"(高倍率)和"免费试用"(低倍率)两个分组</p><p style="margin-top: 16px; color: #10b981; font-weight: 600;">👉 点击左侧的"分组管理"开始</p></div>'
},
createGroup: {
title: '➕ 创建新分组',
......@@ -5004,9 +5004,9 @@ export default {
// User tour steps
user: {
welcome: {
title: '👋 欢迎使用 Sub2API',
title: '👋 欢迎使用 TrafficAPI',
description:
'<div style="line-height: 1.8;"><p style="margin-bottom: 16px;">您好!欢迎来到 Sub2API AI 服务平台。</p><p style="margin-bottom: 12px;"><b>🎯 快速开始:</b></p><ul style="margin-left: 20px; margin-bottom: 16px;"><li>🔑 创建 API 密钥</li><li>📋 复制密钥到您的应用</li><li>🚀 开始使用 AI 服务</li></ul><p style="color: #10b981; font-weight: 600;">只需 1 分钟,让我们开始吧 →</p></div>',
'<div style="line-height: 1.8;"><p style="margin-bottom: 16px;">您好!欢迎来到 TrafficAPI —— 智能路由 Claude、GPT、Gemini 等多服务商 AI 请求的统一网关平台。</p><p style="margin-bottom: 12px;"><b>🎯 快速开始:</b></p><ul style="margin-left: 20px; margin-bottom: 16px;"><li>🔑 创建您的 API 密钥</li><li>📋 在任意兼容 OpenAI 的客户端中使用</li><li>📊 实时查看流量与使用情况</li></ul><p style="color: #10b981; font-weight: 600;">只需 1 分钟,让我们开始吧 →</p></div>',
nextBtn: '开始 🚀',
prevBtn: '跳过'
},
......
......@@ -28,7 +28,7 @@ async function bootstrap() {
appStore.initFromInjectedConfig()
// Set document title immediately after config is loaded
if (appStore.siteName && appStore.siteName !== 'Sub2API') {
if (appStore.siteName && appStore.siteName !== 'TrafficAPI') {
document.title = `${appStore.siteName} - AI API Gateway`
}
......
......@@ -11,8 +11,8 @@ describe('resolveDocumentTitle', () => {
})
it('站点名为空时,回退默认站点名', () => {
expect(resolveDocumentTitle('Dashboard', '')).toBe('Dashboard - Sub2API')
expect(resolveDocumentTitle(undefined, ' ')).toBe('Sub2API')
expect(resolveDocumentTitle('Dashboard', '')).toBe('Dashboard - TrafficAPI')
expect(resolveDocumentTitle(undefined, ' ')).toBe('TrafficAPI')
})
it('站点名变更时仅影响后续路由标题计算', () => {
......
......@@ -435,7 +435,7 @@ router.beforeEach((to, _from, next) => {
const menuItem = publicItems.find((item) => item.id === id)
?? (authStore.isAdmin ? adminSettingsStore.customMenuItems.find((item) => item.id === id) : undefined)
if (menuItem?.label) {
const siteName = appStore.siteName || 'Sub2API'
const siteName = appStore.siteName || 'TrafficAPI'
document.title = `${menuItem.label} - ${siteName}`
} else {
document.title = resolveDocumentTitle(to.meta.title, appStore.siteName, to.meta.titleKey as string)
......
......@@ -5,7 +5,7 @@ import { i18n } from '@/i18n'
* 优先使用 titleKey 通过 i18n 翻译,fallback 到静态 routeTitle。
*/
export function resolveDocumentTitle(routeTitle: unknown, siteName?: string, titleKey?: string): string {
const normalizedSiteName = typeof siteName === 'string' && siteName.trim() ? siteName.trim() : 'Sub2API'
const normalizedSiteName = typeof siteName === 'string' && siteName.trim() ? siteName.trim() : 'TrafficAPI'
if (typeof titleKey === 'string' && titleKey.trim()) {
const translated = i18n.global.t(titleKey)
......
......@@ -24,7 +24,7 @@ export const useAppStore = defineStore('app', () => {
// Public settings cache state
const publicSettingsLoaded = ref<boolean>(false)
const publicSettingsLoading = ref<boolean>(false)
const siteName = ref<string>('Sub2API')
const siteName = ref<string>('TrafficAPI')
const siteLogo = ref<string>('')
const siteVersion = ref<string>('')
const contactInfo = ref<string>('')
......@@ -285,7 +285,7 @@ export const useAppStore = defineStore('app', () => {
*/
function applySettings(config: PublicSettings): void {
cachedPublicSettings.value = config
siteName.value = config.site_name || 'Sub2API'
siteName.value = config.site_name || 'TrafficAPI'
siteLogo.value = config.site_logo || ''
siteVersion.value = config.version || ''
contactInfo.value = config.contact_info || ''
......
This diff is collapsed.
......@@ -372,7 +372,7 @@ const appStore = useAppStore()
// ==================== Site Settings (same as HomeView) ====================
const siteName = computed(() => appStore.cachedPublicSettings?.site_name || appStore.siteName || 'Sub2API')
const siteName = computed(() => appStore.cachedPublicSettings?.site_name || appStore.siteName || 'TrafficAPI')
const siteLogo = computed(() => appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '')
const docUrl = computed(() => appStore.cachedPublicSettings?.doc_url || appStore.docUrl || '')
const githubUrl = 'https://github.com/Wei-Shaw/sub2api'
......
......@@ -2121,9 +2121,9 @@ const form = reactive<SettingsForm>({
default_balance: 0,
default_concurrency: 1,
default_subscriptions: [],
site_name: 'Sub2API',
site_name: 'TrafficAPI',
site_logo: '',
site_subtitle: 'Subscription to API Conversion Platform',
site_subtitle: 'Intelligent AI Traffic Routing & Management',
api_base_url: '',
contact_info: '',
doc_url: '',
......
......@@ -212,7 +212,7 @@ const hasRegisterData = ref<boolean>(false)
// Public settings
const turnstileEnabled = ref<boolean>(false)
const turnstileSiteKey = ref<string>('')
const siteName = ref<string>('Sub2API')
const siteName = ref<string>('TrafficAPI')
const registrationEmailSuffixWhitelist = ref<string[]>([])
// Turnstile for resend
......@@ -249,7 +249,7 @@ onMounted(async () => {
const settings = await getPublicSettings()
turnstileEnabled.value = settings.turnstile_enabled
turnstileSiteKey.value = settings.turnstile_site_key || ''
siteName.value = settings.site_name || 'Sub2API'
siteName.value = settings.site_name || 'TrafficAPI'
registrationEmailSuffixWhitelist.value = normalizeRegistrationEmailSuffixWhitelist(
settings.registration_email_suffix_whitelist || []
)
......
......@@ -322,7 +322,7 @@ const promoCodeEnabled = ref<boolean>(true)
const invitationCodeEnabled = ref<boolean>(false)
const turnstileEnabled = ref<boolean>(false)
const turnstileSiteKey = ref<string>('')
const siteName = ref<string>('Sub2API')
const siteName = ref<string>('TrafficAPI')
const linuxdoOAuthEnabled = ref<boolean>(false)
const registrationEmailSuffixWhitelist = ref<string[]>([])
......@@ -374,7 +374,7 @@ onMounted(async () => {
invitationCodeEnabled.value = settings.invitation_code_enabled
turnstileEnabled.value = settings.turnstile_enabled
turnstileSiteKey.value = settings.turnstile_site_key || ''
siteName.value = settings.site_name || 'Sub2API'
siteName.value = settings.site_name || 'TrafficAPI'
linuxdoOAuthEnabled.value = settings.linuxdo_oauth_enabled
registrationEmailSuffixWhitelist.value = normalizeRegistrationEmailSuffixWhitelist(
settings.registration_email_suffix_whitelist || []
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment