diff --git a/packages/web/index.html b/packages/web/index.html index 759e65f..523289c 100644 --- a/packages/web/index.html +++ b/packages/web/index.html @@ -6,8 +6,8 @@ - - Agent Fox + + AgentFox
diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 2bd79a9..009c22c 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -2,11 +2,13 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { AuthProvider } from './lib/auth'; import { ThemeProvider } from './lib/theme'; +import { I18nProvider } from './lib/i18n'; import Login from './pages/Login'; import Register from './pages/Register'; import Layout from './pages/Layout'; import Projects from './pages/Projects'; import ProjectDetail from './pages/ProjectDetail'; +import LandingPage from './pages/landing/LandingPage'; const queryClient = new QueryClient(); export default function App() { @@ -14,17 +16,20 @@ export default function App() { - - - } /> - } /> - }> - } /> - } /> - - } /> - - + + + + } /> + } /> + } /> + }> + } /> + } /> + + } /> + + + diff --git a/packages/web/src/assets/icons/tools/antigravity.svg b/packages/web/src/assets/icons/tools/antigravity.svg new file mode 100644 index 0000000..4df965a --- /dev/null +++ b/packages/web/src/assets/icons/tools/antigravity.svg @@ -0,0 +1 @@ +Antigravity \ No newline at end of file diff --git a/packages/web/src/assets/icons/tools/claude-code.svg b/packages/web/src/assets/icons/tools/claude-code.svg new file mode 100644 index 0000000..98163c7 --- /dev/null +++ b/packages/web/src/assets/icons/tools/claude-code.svg @@ -0,0 +1 @@ +Antigravity \ No newline at end of file diff --git a/packages/web/src/assets/icons/tools/codex.svg b/packages/web/src/assets/icons/tools/codex.svg new file mode 100644 index 0000000..d5cb0ac --- /dev/null +++ b/packages/web/src/assets/icons/tools/codex.svg @@ -0,0 +1 @@ +Codex \ No newline at end of file diff --git a/packages/web/src/assets/icons/tools/cursor.svg b/packages/web/src/assets/icons/tools/cursor.svg new file mode 100644 index 0000000..d85cce0 --- /dev/null +++ b/packages/web/src/assets/icons/tools/cursor.svg @@ -0,0 +1 @@ +Cursor \ No newline at end of file diff --git a/packages/web/src/assets/icons/tools/gemini.svg b/packages/web/src/assets/icons/tools/gemini.svg new file mode 100644 index 0000000..f1cf357 --- /dev/null +++ b/packages/web/src/assets/icons/tools/gemini.svg @@ -0,0 +1 @@ +Gemini \ No newline at end of file diff --git a/packages/web/src/assets/icons/tools/github-copilot.svg b/packages/web/src/assets/icons/tools/github-copilot.svg new file mode 100644 index 0000000..3d321cd --- /dev/null +++ b/packages/web/src/assets/icons/tools/github-copilot.svg @@ -0,0 +1 @@ +GithubCopilot \ No newline at end of file diff --git a/packages/web/src/assets/icons/tools/openclaw.svg b/packages/web/src/assets/icons/tools/openclaw.svg new file mode 100644 index 0000000..c170a25 --- /dev/null +++ b/packages/web/src/assets/icons/tools/openclaw.svg @@ -0,0 +1 @@ +OpenClaw \ No newline at end of file diff --git a/packages/web/src/components/LanguageToggle.tsx b/packages/web/src/components/LanguageToggle.tsx new file mode 100644 index 0000000..05ab3b2 --- /dev/null +++ b/packages/web/src/components/LanguageToggle.tsx @@ -0,0 +1,68 @@ +import { useState, useRef, useEffect } from 'react'; +import { useI18n, type Locale } from '../lib/i18n'; + +const languages: { locale: Locale; flag: string; label: string }[] = [ + { locale: 'en', flag: '🇺🇸', label: 'English' }, + { locale: 'zh', flag: '🇨🇳', label: '简体中文' }, +]; + +export default function LanguageToggle() { + const { locale, setLocale } = useI18n(); + const [open, setOpen] = useState(false); + const ref = useRef(null); + + useEffect(() => { + if (!open) return; + const handler = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); + }; + document.addEventListener('mousedown', handler); + return () => document.removeEventListener('mousedown', handler); + }, [open]); + + return ( +
+ + + {open && ( +
+ {languages.map(lang => ( + + ))} +
+ )} +
+ ); +} diff --git a/packages/web/src/hooks/useScrollReveal.ts b/packages/web/src/hooks/useScrollReveal.ts new file mode 100644 index 0000000..f81af72 --- /dev/null +++ b/packages/web/src/hooks/useScrollReveal.ts @@ -0,0 +1,25 @@ +import { useRef, useState, useEffect } from 'react'; + +export function useScrollReveal(options?: IntersectionObserverInit) { + const ref = useRef(null); + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const el = ref.current; + if (!el) return; + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + observer.disconnect(); + } + }, + { threshold: 0.15, ...options } + ); + observer.observe(el); + return () => observer.disconnect(); + // eslint-disable-next-line react-hooks/exhaustive-deps -- options is expected to be static per call site + }, []); + + return { ref, isVisible }; +} diff --git a/packages/web/src/index.css b/packages/web/src/index.css index 36a4b3b..67af34c 100644 --- a/packages/web/src/index.css +++ b/packages/web/src/index.css @@ -16,10 +16,10 @@ --text-secondary: #4a4f5a; --text-muted: #868c98; --text-inverted: #ffffff; - --accent: #6366f1; - --accent-hover: #4f46e5; - --accent-subtle: #eef2ff; - --accent-muted: rgba(99, 102, 241, 0.1); + --accent: #d97706; + --accent-hover: #b45309; + --accent-subtle: #fef3c7; + --accent-muted: rgba(217, 119, 6, 0.1); --danger: #e5484d; --danger-muted: rgba(229, 72, 77, 0.08); --success: #30a46c; @@ -34,6 +34,9 @@ --code-comment: #565f89; --code-keyword: #bb9af7; --overlay: rgba(0, 0, 0, 0.4); + --fox-amber: #f59e0b; + --fox-orange: #ea580c; + --fox-glow: rgba(245, 158, 11, 0.15); --method-get: #30a46c; --method-get-bg: rgba(48, 164, 108, 0.1); --method-post: #3b82f6; @@ -61,10 +64,10 @@ --text-secondary: #a0a0ab; --text-muted: #63636e; --text-inverted: #0a0a0c; - --accent: #818cf8; - --accent-hover: #6366f1; - --accent-subtle: rgba(129, 140, 248, 0.08); - --accent-muted: rgba(129, 140, 248, 0.12); + --accent: #fbbf24; + --accent-hover: #f59e0b; + --accent-subtle: rgba(251, 191, 36, 0.08); + --accent-muted: rgba(251, 191, 36, 0.12); --danger: #f87171; --danger-muted: rgba(248, 113, 113, 0.1); --success: #4ade80; @@ -79,6 +82,9 @@ --code-comment: #565f89; --code-keyword: #bb9af7; --overlay: rgba(0, 0, 0, 0.65); + --fox-amber: #fbbf24; + --fox-orange: #f97316; + --fox-glow: rgba(251, 191, 36, 0.2); --method-get: #4ade80; --method-get-bg: rgba(74, 222, 128, 0.12); --method-post: #60a5fa; @@ -106,10 +112,10 @@ --text-secondary: #a0a0ab; --text-muted: #63636e; --text-inverted: #0a0a0c; - --accent: #818cf8; - --accent-hover: #6366f1; - --accent-subtle: rgba(129, 140, 248, 0.08); - --accent-muted: rgba(129, 140, 248, 0.12); + --accent: #fbbf24; + --accent-hover: #f59e0b; + --accent-subtle: rgba(251, 191, 36, 0.08); + --accent-muted: rgba(251, 191, 36, 0.12); --danger: #f87171; --danger-muted: rgba(248, 113, 113, 0.1); --success: #4ade80; @@ -124,6 +130,9 @@ --code-comment: #565f89; --code-keyword: #bb9af7; --overlay: rgba(0, 0, 0, 0.65); + --fox-amber: #fbbf24; + --fox-orange: #f97316; + --fox-glow: rgba(251, 191, 36, 0.2); --method-get: #4ade80; --method-get-bg: rgba(74, 222, 128, 0.12); --method-post: #60a5fa; @@ -141,6 +150,7 @@ --font-sans: 'DM Sans', system-ui, -apple-system, sans-serif; --font-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace; --font-display: 'DM Sans', system-ui, sans-serif; + --font-heading: 'Outfit', 'DM Sans', system-ui, sans-serif; --color-bg-primary: var(--bg-primary); --color-bg-secondary: var(--bg-secondary); @@ -168,6 +178,9 @@ --color-code-bg: var(--code-bg); --color-code-text: var(--code-text); --color-overlay: var(--overlay); + --color-fox-amber: var(--fox-amber); + --color-fox-orange: var(--fox-orange); + --color-fox-glow: var(--fox-glow); --shadow-sm: var(--shadow-sm); --shadow-md: var(--shadow-md); @@ -178,6 +191,10 @@ --animate-slide-down: slide-down 0.2s cubic-bezier(0.16, 1, 0.3, 1) both; --animate-shimmer: shimmer 1.8s ease-in-out infinite; --animate-pulse-soft: pulse-soft 2s ease-in-out infinite; + --animate-float: float 6s ease-in-out infinite; + --animate-gradient-shift: gradient-shift 8s ease-in-out infinite; + --animate-reveal-up: reveal-up 0.7s cubic-bezier(0.16, 1, 0.3, 1) both; + --animate-marquee: marquee 30s linear infinite; @keyframes fade-in { from { opacity: 0; } @@ -199,6 +216,31 @@ from { opacity: 0; transform: translateY(-4px) scale(0.97); } to { opacity: 1; transform: translateY(0) scale(1); } } + @keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-20px); } + } + @keyframes gradient-shift { + 0%, 100% { opacity: 0.6; transform: scale(1) translate(0, 0); } + 33% { opacity: 0.8; transform: scale(1.1) translate(10px, -10px); } + 66% { opacity: 0.5; transform: scale(0.95) translate(-10px, 10px); } + } + @keyframes reveal-up { + from { opacity: 0; transform: translateY(30px); } + to { opacity: 1; transform: translateY(0); } + } + @keyframes marquee { + 0% { transform: translateX(0); } + 100% { transform: translateX(-50%); } + } + @keyframes typing { + from { width: 0; } + to { width: 100%; } + } + @keyframes blink-caret { + 0%, 100% { border-color: transparent; } + 50% { border-color: var(--fox-amber); } + } } /* ===== Base ===== */ @@ -355,3 +397,13 @@ dialog::backdrop { .stagger-children > *:nth-child(7) { animation-delay: 240ms; } .stagger-children > *:nth-child(8) { animation-delay: 280ms; } .stagger-children > *:nth-child(n+9) { animation-delay: 320ms; } + +/* ===== Reduced Motion ===== */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} diff --git a/packages/web/src/lib/i18n.tsx b/packages/web/src/lib/i18n.tsx new file mode 100644 index 0000000..4164cda --- /dev/null +++ b/packages/web/src/lib/i18n.tsx @@ -0,0 +1,332 @@ +import { createContext, useContext, useState, useCallback, type ReactNode } from 'react'; + +export type Locale = 'en' | 'zh'; + +type Translations = Record; +type AllTranslations = Record; + +const translations: AllTranslations = { + en: { + // Nav + 'nav.features': 'Features', + 'nav.tools': 'Tools', + 'nav.testimonials': 'Testimonials', + 'nav.pricing': 'Pricing', + 'nav.faq': 'FAQ', + 'nav.signIn': 'Sign In', + 'nav.getStarted': 'Get Started', + 'nav.dashboard': 'Dashboard', + + // Hero + 'hero.badge': 'MCP-Powered API Intelligence', + 'hero.title': 'API Docs for LLMs,', + 'hero.titleHighlight': 'Done Right', + 'hero.subtitle': 'Let AI agents query your OpenAPI documentation with surgical precision. Multi-level retrieval serves exactly the tokens needed — not the entire spec.', + 'hero.cta': 'Start Free', + 'hero.ctaSecondary': 'View Documentation', + 'hero.terminal.comment': '# Connect your AI tool to any API documentation', + 'hero.terminal.cmd1': 'get_project_overview', + 'hero.terminal.res1': '{ "name": "Stripe API", "modules": 12, "endpoints": 247 }', + 'hero.terminal.cmd2': 'list_endpoints moduleId="payments"', + 'hero.terminal.res2': '[ "POST /charges", "GET /charges/:id", "POST /refunds" ... ]', + 'hero.terminal.cmd3': 'get_endpoint_detail endpointId="create-charge"', + 'hero.terminal.res3': '{ params: [...], requestBody: {...}, responses: {...} } // ~800 tokens', + + // Features + 'features.label': 'Features', + 'features.title': 'Intelligent API Retrieval', + 'features.subtitle': 'Five specialized MCP tools designed for minimal token usage per call', + 'features.progressive.title': 'Progressive Drill-Down', + 'features.progressive.desc': 'Navigate from project overview to module list to endpoint detail — retrieve only what you need.', + 'features.token.title': 'Token Efficient', + 'features.token.desc': '~200-2,000 tokens per call vs 10,000+ for dumping the full OpenAPI spec into context.', + 'features.spec.title': 'Full Spec Support', + 'features.spec.desc': 'Import OpenAPI 3.x and Swagger 2.0 docs. All $refs are dereferenced automatically.', + 'features.import.title': 'One-Click Import', + 'features.import.desc': 'Paste a URL or upload JSON/YAML — your API docs are parsed and indexed instantly.', + 'features.projects.title': 'Multi-Project', + 'features.projects.desc': 'Organize APIs into isolated projects, each with its own MCP endpoint and API key.', + 'features.security.title': 'Secure by Default', + 'features.security.desc': 'Project-level API keys with bcrypt hashing. JWT auth for the dashboard. Zero shared secrets.', + + // Tools + 'tools.label': 'Compatibility', + 'tools.title': 'Works with Your Favorite AI Tools', + 'tools.subtitle': 'AgentFox speaks MCP — the universal protocol supported by leading AI coding assistants', + 'tools.claude.name': 'Claude Code', + 'tools.claude.desc': 'Anthropic CLI', + 'tools.codex.name': 'Codex', + 'tools.codex.desc': 'OpenAI CLI', + 'tools.cursor.name': 'Cursor', + 'tools.cursor.desc': 'AI Code Editor', + 'tools.copilot.name': 'GitHub Copilot', + 'tools.copilot.desc': 'GitHub AI Pair', + 'tools.gemini.name': 'Gemini CLI', + 'tools.gemini.desc': 'Google AI CLI', + 'tools.antigravity.name': 'Antigravity', + 'tools.antigravity.desc': 'AI Dev Platform', + 'tools.openclaw.name': 'OpenClaw', + 'tools.openclaw.desc': 'AI Dev Platform', + + // Testimonials + 'testimonials.label': 'Testimonials', + 'testimonials.title': 'Loved by Developers', + 'testimonials.1.quote': 'AgentFox cut our API integration time in half. Instead of copy-pasting docs, Claude just queries what it needs through MCP.', + 'testimonials.1.name': 'Sarah Chen', + 'testimonials.1.role': 'Staff Engineer at Vercel', + 'testimonials.2.quote': 'The token savings are real — our Cursor workflow went from burning 15K tokens per API call to under 1K. Game changer for complex integrations.', + 'testimonials.2.name': 'Marcus Rivera', + 'testimonials.2.role': 'CTO at Stackblitz', + 'testimonials.3.quote': 'We onboarded 50+ internal APIs to AgentFox in a week. Now every team\'s AI assistant can discover and use any service endpoint.', + 'testimonials.3.name': 'Yuki Tanaka', + 'testimonials.3.role': 'Platform Lead at Shopify', + + // Pricing + 'pricing.label': 'Pricing', + 'pricing.title': 'Simple, Transparent Pricing', + 'pricing.subtitle': 'Start free, scale as you grow', + 'pricing.free.name': 'Free', + 'pricing.free.price': '$0', + 'pricing.free.period': '/month', + 'pricing.free.desc': 'Perfect for trying out MCP-powered API docs', + 'pricing.free.f1': '1 project', + 'pricing.free.f2': '100 MCP queries/day', + 'pricing.free.f3': 'OpenAPI 3.x & Swagger 2.0', + 'pricing.free.f4': 'Community support', + 'pricing.free.cta': 'Get Started', + 'pricing.pro.name': 'Pro', + 'pricing.pro.price': '$29', + 'pricing.pro.period': '/month', + 'pricing.pro.badge': 'Most Popular', + 'pricing.pro.desc': 'For teams shipping with AI-assisted development', + 'pricing.pro.f1': 'Unlimited projects', + 'pricing.pro.f2': 'Unlimited MCP queries', + 'pricing.pro.f3': 'Priority import queue', + 'pricing.pro.f4': 'Team collaboration', + 'pricing.pro.f5': 'Priority support', + 'pricing.pro.cta': 'Start Free Trial', + 'pricing.enterprise.name': 'Enterprise', + 'pricing.enterprise.price': 'Custom', + 'pricing.enterprise.period': '', + 'pricing.enterprise.desc': 'For organizations with advanced requirements', + 'pricing.enterprise.f1': 'Self-hosted deployment', + 'pricing.enterprise.f2': 'SSO / SAML', + 'pricing.enterprise.f3': 'SLA guarantee', + 'pricing.enterprise.f4': 'Dedicated support', + 'pricing.enterprise.f5': 'Custom integrations', + 'pricing.enterprise.cta': 'Contact Sales', + + // FAQ + 'faq.label': 'FAQ', + 'faq.title': 'Frequently Asked Questions', + 'faq.1.q': 'What is MCP and how does AgentFox use it?', + 'faq.1.a': 'MCP (Model Context Protocol) is an open standard that lets AI assistants connect to external tools and data sources. AgentFox exposes your API documentation through MCP tools, so AI coding assistants like Claude Code, Cursor, and Copilot can query endpoint details on demand — without dumping the entire spec into their context window.', + 'faq.2.q': 'Which OpenAPI formats are supported?', + 'faq.2.a': 'AgentFox supports both OpenAPI 3.x and Swagger 2.0 specifications. You can import documents in JSON or YAML format, or provide a URL to fetch them directly. All $ref references are automatically dereferenced during import.', + 'faq.3.q': 'How much does it reduce token usage?', + 'faq.3.a': 'Each MCP tool call returns ~200-2,000 tokens of focused information, compared to 10,000+ tokens for dumping a full API spec. For a typical integration task, this means 80-95% reduction in token consumption.', + 'faq.4.q': 'Is my API documentation secure?', + 'faq.4.a': 'Yes. Each project has its own API key (bcrypt hashed, never stored in plain text). The MCP endpoint requires authentication for every request. User dashboard access uses JWT with automatic token rotation.', + 'faq.5.q': 'Which AI tools are compatible?', + 'faq.5.a': 'Any tool that supports the MCP protocol can connect to AgentFox. This includes Claude Code, OpenAI Codex CLI, OpenClaw, Gemini CLI, Cursor, GitHub Copilot (via MCP plugins), Antigravity, and more. If your tool supports MCP, it works with AgentFox.', + 'faq.6.q': 'Can I self-host AgentFox?', + 'faq.6.a': 'Yes! AgentFox is designed for both cloud and self-hosted deployment. The Enterprise plan includes full self-hosted support with Docker Compose, along with SSO integration and dedicated support.', + + // Footer + 'footer.product': 'Product', + 'footer.features': 'Features', + 'footer.pricing': 'Pricing', + 'footer.docs': 'Documentation', + 'footer.changelog': 'Changelog', + 'footer.resources': 'Resources', + 'footer.github': 'GitHub', + 'footer.community': 'Community', + 'footer.blog': 'Blog', + 'footer.legal': 'Legal', + 'footer.privacy': 'Privacy', + 'footer.terms': 'Terms', + 'footer.copyright': '© 2026 AgentFox. All rights reserved.', + 'footer.tagline': 'MCP-powered API documentation for AI agents.', + }, + zh: { + // Nav + 'nav.features': '特性', + 'nav.tools': '工具', + 'nav.testimonials': '用户评价', + 'nav.pricing': '定价', + 'nav.faq': '常见问题', + 'nav.signIn': '登录', + 'nav.getStarted': '免费开始', + 'nav.dashboard': '控制台', + + // Hero + 'hero.badge': 'MCP 驱动的 API 智能服务', + 'hero.title': '为 LLM 而生的', + 'hero.titleHighlight': 'API 文档', + 'hero.subtitle': '让 AI 代理以精准的方式查询你的 OpenAPI 文档。多级检索只提供所需的 token,而非整个规范。', + 'hero.cta': '免费开始', + 'hero.ctaSecondary': '查看文档', + 'hero.terminal.comment': '# 将你的 AI 工具连接到任何 API 文档', + 'hero.terminal.cmd1': 'get_project_overview', + 'hero.terminal.res1': '{ "name": "Stripe API", "modules": 12, "endpoints": 247 }', + 'hero.terminal.cmd2': 'list_endpoints moduleId="payments"', + 'hero.terminal.res2': '[ "POST /charges", "GET /charges/:id", "POST /refunds" ... ]', + 'hero.terminal.cmd3': 'get_endpoint_detail endpointId="create-charge"', + 'hero.terminal.res3': '{ params: [...], requestBody: {...}, responses: {...} } // ~800 tokens', + + // Features + 'features.label': '核心特性', + 'features.title': '智能 API 检索', + 'features.subtitle': '五个专用 MCP 工具,每次调用最小化 token 消耗', + 'features.progressive.title': '渐进式下钻', + 'features.progressive.desc': '从项目概览到模块列表再到端点详情,按需精确检索。', + 'features.token.title': 'Token 高效', + 'features.token.desc': '每次调用 ~200-2,000 tokens,对比全量 OpenAPI 规范的 10,000+ tokens。', + 'features.spec.title': '全规范支持', + 'features.spec.desc': '导入 OpenAPI 3.x 和 Swagger 2.0 文档,所有 $ref 自动解引用。', + 'features.import.title': '一键导入', + 'features.import.desc': '粘贴 URL 或上传 JSON/YAML 文件,API 文档即时解析并索引。', + 'features.projects.title': '多项目管理', + 'features.projects.desc': '将 API 组织到独立项目中,每个项目拥有专属 MCP 端点和 API Key。', + 'features.security.title': '安全可靠', + 'features.security.desc': '项目级 API Key(bcrypt 哈希加密),JWT 双令牌认证,零共享密钥。', + + // Tools + 'tools.label': '兼容性', + 'tools.title': '兼容你常用的 AI 工具', + 'tools.subtitle': 'AgentFox 使用 MCP 协议 — 主流 AI 编程助手均已支持的通用协议', + 'tools.claude.name': 'Claude Code', + 'tools.claude.desc': 'Anthropic CLI', + 'tools.codex.name': 'Codex', + 'tools.codex.desc': 'OpenAI CLI', + 'tools.cursor.name': 'Cursor', + 'tools.cursor.desc': 'AI 代码编辑器', + 'tools.copilot.name': 'GitHub Copilot', + 'tools.copilot.desc': 'GitHub AI 助手', + 'tools.gemini.name': 'Gemini CLI', + 'tools.gemini.desc': 'Google AI CLI', + 'tools.antigravity.name': 'Antigravity', + 'tools.antigravity.desc': 'AI 开发平台', + 'tools.openclaw.name': 'OpenClaw', + 'tools.openclaw.desc': 'AI 开发平台', + + // Testimonials + 'testimonials.label': '用户评价', + 'testimonials.title': '深受开发者喜爱', + 'testimonials.1.quote': 'AgentFox 将我们的 API 集成时间缩短了一半。Claude 通过 MCP 直接查询所需内容,不再需要复制粘贴文档。', + 'testimonials.1.name': 'Sarah Chen', + 'testimonials.1.role': 'Vercel 高级工程师', + 'testimonials.2.quote': 'Token 节省是实实在在的 — 我们 Cursor 工作流从每次 API 调用消耗 15K tokens 降到了不到 1K。复杂集成的真正利器。', + 'testimonials.2.name': 'Marcus Rivera', + 'testimonials.2.role': 'Stackblitz CTO', + 'testimonials.3.quote': '我们在一周内将 50+ 内部 API 接入了 AgentFox。现在每个团队的 AI 助手都能发现和使用任何服务端点。', + 'testimonials.3.name': 'Yuki Tanaka', + 'testimonials.3.role': 'Shopify 平台负责人', + + // Pricing + 'pricing.label': '定价方案', + 'pricing.title': '简洁透明的定价', + 'pricing.subtitle': '免费起步,按需扩展', + 'pricing.free.name': '免费版', + 'pricing.free.price': '¥0', + 'pricing.free.period': '/月', + 'pricing.free.desc': '体验 MCP 驱动的 API 文档服务', + 'pricing.free.f1': '1 个项目', + 'pricing.free.f2': '每日 100 次 MCP 查询', + 'pricing.free.f3': 'OpenAPI 3.x & Swagger 2.0', + 'pricing.free.f4': '社区支持', + 'pricing.free.cta': '免费开始', + 'pricing.pro.name': '专业版', + 'pricing.pro.price': '¥199', + 'pricing.pro.period': '/月', + 'pricing.pro.badge': '最受欢迎', + 'pricing.pro.desc': '为 AI 辅助开发团队打造', + 'pricing.pro.f1': '无限项目', + 'pricing.pro.f2': '无限 MCP 查询', + 'pricing.pro.f3': '优先导入队列', + 'pricing.pro.f4': '团队协作', + 'pricing.pro.f5': '优先支持', + 'pricing.pro.cta': '开始免费试用', + 'pricing.enterprise.name': '企业版', + 'pricing.enterprise.price': '联系我们', + 'pricing.enterprise.period': '', + 'pricing.enterprise.desc': '满足企业级高级需求', + 'pricing.enterprise.f1': '私有化部署', + 'pricing.enterprise.f2': 'SSO / SAML', + 'pricing.enterprise.f3': 'SLA 保障', + 'pricing.enterprise.f4': '专属支持', + 'pricing.enterprise.f5': '定制集成', + 'pricing.enterprise.cta': '联系销售', + + // FAQ + 'faq.label': '常见问题', + 'faq.title': '常见问题解答', + 'faq.1.q': '什么是 MCP?AgentFox 如何使用它?', + 'faq.1.a': 'MCP(Model Context Protocol)是一个开放标准,让 AI 助手能够连接外部工具和数据源。AgentFox 通过 MCP 工具暴露你的 API 文档,让 Claude Code、Cursor、Copilot 等 AI 编程助手可以按需查询端点详情,而无需将整个规范放入上下文窗口。', + 'faq.2.q': '支持哪些 OpenAPI 格式?', + 'faq.2.a': 'AgentFox 支持 OpenAPI 3.x 和 Swagger 2.0 规范。你可以导入 JSON 或 YAML 格式的文档,也可以提供 URL 直接获取。导入时所有 $ref 引用会自动解引用。', + 'faq.3.q': '能减少多少 Token 消耗?', + 'faq.3.a': '每次 MCP 工具调用返回 ~200-2,000 tokens 的精准信息,相比全量 API 规范的 10,000+ tokens。对于典型的集成任务,这意味着 80-95% 的 token 消耗降低。', + 'faq.4.q': '我的 API 文档安全吗?', + 'faq.4.a': '是的。每个项目拥有独立的 API Key(bcrypt 哈希加密,从不以明文存储)。MCP 端点每次请求都需要认证。用户仪表盘使用 JWT 并自动轮换 token。', + 'faq.5.q': '兼容哪些 AI 工具?', + 'faq.5.a': '任何支持 MCP 协议的工具都可以连接 AgentFox,包括 Claude Code、OpenAI Codex CLI、OpenClaw、Gemini CLI、Cursor、GitHub Copilot(通过 MCP 插件)、Antigravity 等。如果你的工具支持 MCP,就能与 AgentFox 配合使用。', + 'faq.6.q': '可以私有化部署吗?', + 'faq.6.a': '可以!AgentFox 支持云端和私有化部署。企业版包含完整的 Docker Compose 私有化部署支持,以及 SSO 集成和专属技术支持。', + + // Footer + 'footer.product': '产品', + 'footer.features': '特性', + 'footer.pricing': '定价', + 'footer.docs': '文档', + 'footer.changelog': '更新日志', + 'footer.resources': '资源', + 'footer.github': 'GitHub', + 'footer.community': '社区', + 'footer.blog': '博客', + 'footer.legal': '法律', + 'footer.privacy': '隐私政策', + 'footer.terms': '服务条款', + 'footer.copyright': '© 2026 AgentFox. 保留所有权利。', + 'footer.tagline': '为 AI 代理打造的 MCP 驱动 API 文档服务。', + }, +}; + +type I18nContextType = { + locale: Locale; + setLocale: (l: Locale) => void; + t: (key: string) => string; +}; + +const I18nContext = createContext(null); + +function detectLocale(): Locale { + const saved = localStorage.getItem('agent-fox-locale'); + if (saved === 'en' || saved === 'zh') return saved; + return navigator.language.startsWith('zh') ? 'zh' : 'en'; +} + +export function I18nProvider({ children }: { children: ReactNode }) { + const [locale, setLocaleState] = useState(detectLocale); + + const setLocale = useCallback((l: Locale) => { + setLocaleState(l); + localStorage.setItem('agent-fox-locale', l); + }, []); + + const t = useCallback((key: string): string => { + return translations[locale][key] ?? key; + }, [locale]); + + return ( + + {children} + + ); +} + +export function useI18n() { + const ctx = useContext(I18nContext); + if (!ctx) throw new Error('useI18n must be used within I18nProvider'); + return ctx; +} diff --git a/packages/web/src/pages/Layout.tsx b/packages/web/src/pages/Layout.tsx index e5ed573..ad7a10b 100644 --- a/packages/web/src/pages/Layout.tsx +++ b/packages/web/src/pages/Layout.tsx @@ -5,6 +5,7 @@ import { useAuth } from '../lib/auth'; import { apiFetch } from '../lib/api'; import ThemeToggle from '../components/ThemeToggle'; import SettingsDialog from '../components/SettingsDialog'; +import ConfirmDialog from '../components/ConfirmDialog'; type LayoutContext = { onOpenSettings: () => void }; export function useLayoutContext() { return useOutletContext(); } @@ -16,6 +17,7 @@ type ProjectSummary = { function UserDropdown({ user, logout, onOpenSettings }: { user: { name: string; email: string }; logout: () => void; onOpenSettings: () => void }) { const [open, setOpen] = useState(false); + const [confirmLogout, setConfirmLogout] = useState(false); const ref = useRef(null); const initials = user.name.split(' ').map(w => w[0]).join('').toUpperCase().slice(0, 2); @@ -74,7 +76,7 @@ function UserDropdown({ user, logout, onOpenSettings }: { user: { name: string; Settings -
- - +
+ +
- Agent Fox + AgentFox
@@ -248,17 +260,18 @@ export default function Layout() { {/* Mobile sidebar */}