diff --git a/app/exchange/page.tsx b/app/exchange/page.tsx new file mode 100644 index 0000000..0531906 --- /dev/null +++ b/app/exchange/page.tsx @@ -0,0 +1,394 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Link from "next/link"; +import en from "../../locales/en.json"; +import zh from "../../locales/zh.json"; + +type Locale = "en" | "zh"; +type Theme = "light" | "dark" | "system"; + +const locales = { en, zh }; + +// API 配置 +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "https://api.example.com"; + +interface ExchangeResult { + original_cdk: string; + new_cdk: string; + points: number; + remaining_days: number; + expired_at: string; +} + +// Icons +const SunIcon = () => ( + + + + + +); + +const MoonIcon = () => ( + + + +); + +const LanguageIcon = () => ( + + + + +); + +const CopyIcon = () => ( + + + + +); + +const CheckIcon = () => ( + + + +); + +const CloseIcon = () => ( + + + +); + +const GiftIcon = () => ( + + + + + + +); + +function getSystemLocale(): Locale { + if (typeof navigator === "undefined") return "en"; + const lang = navigator.language.toLowerCase(); + if (lang.startsWith("zh")) return "zh"; + return "en"; +} + +export default function ExchangePage() { + const [locale, setLocale] = useState("en"); + const [theme, setTheme] = useState("system"); + const [mounted, setMounted] = useState(false); + const [cdk, setCdk] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [result, setResult] = useState(null); + const [showModal, setShowModal] = useState(false); + const [copied, setCopied] = useState(false); + + const t = locales[locale]; + + useEffect(() => { + setMounted(true); + const savedLocale = localStorage.getItem("locale") as Locale | null; + if (savedLocale && (savedLocale === "en" || savedLocale === "zh")) { + setLocale(savedLocale); + } else { + setLocale(getSystemLocale()); + } + const savedTheme = localStorage.getItem("theme") as Theme | null; + if (savedTheme) { + setTheme(savedTheme); + } + }, []); + + useEffect(() => { + if (!mounted) return; + const applyTheme = (isDark: boolean) => { + document.documentElement.classList.toggle("dark", isDark); + }; + if (theme === "system") { + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + applyTheme(mediaQuery.matches); + const handler = (e: MediaQueryListEvent) => applyTheme(e.matches); + mediaQuery.addEventListener("change", handler); + return () => mediaQuery.removeEventListener("change", handler); + } else { + applyTheme(theme === "dark"); + } + }, [theme, mounted]); + + const toggleTheme = () => { + const newTheme = theme === "dark" ? "light" : theme === "light" ? "system" : "dark"; + setTheme(newTheme); + localStorage.setItem("theme", newTheme); + }; + + const toggleLocale = () => { + const newLocale = locale === "en" ? "zh" : "en"; + setLocale(newLocale); + localStorage.setItem("locale", newLocale); + }; + + const getThemeIcon = () => { + if (theme === "dark") return ; + if (theme === "light") return ; + return ( + + + + ); + }; + + const getErrorMessage = (code: number): string => { + switch (code) { + case 1002: + return t.exchange.errors.invalidFormat; + case 2001: + return t.exchange.errors.notFound; + case 3002: + return t.exchange.errors.alreadyExchanged; + case 3003: + return t.exchange.errors.expired; + case 3004: + return t.exchange.errors.tooShort; + case 5001: + case 5002: + return t.exchange.errors.serverError; + default: + return t.exchange.errors.serverError; + } + }; + + const handleExchange = async () => { + if (!cdk.trim()) { + setError(t.exchange.errors.emptyCdk); + return; + } + + //验证 CDK 格式(20-32位字母数字) + if (!/^[a-zA-Z0-9]{20,32}$/.test(cdk.trim())) { + setError(t.exchange.errors.invalidFormat); + return; + } + + setLoading(true); + setError(""); + + try { + const response = await fetch(`${API_BASE_URL}/points/exchange?cdk=${encodeURIComponent(cdk.trim())}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + + const data = await response.json(); + + if (data.code === 0) { + setResult(data.data); + setShowModal(true); + } else { + setError(getErrorMessage(data.code)); + } + } catch { + setError(t.exchange.errors.networkError); + } finally { + setLoading(false); + } + }; + + const handleCopy = async () => { + if (result?.new_cdk) { + try { + await navigator.clipboard.writeText(result.new_cdk); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch { + // Fallback for older browsers + const textArea = document.createElement("textarea"); + textArea.value = result.new_cdk; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand("copy"); + document.body.removeChild(textArea); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + } + }; + + const closeModal = () => { + setShowModal(false); + setCdk(""); + }; + + if (!mounted) return null; + + return ( +
+ {/* Navigation */} + + + {/* Main Content */} +
+
+
+
+
+
+ +
+
+

{t.exchange.title}

+

{t.exchange.description}

+
+ +
+
+
+ { + setCdk(e.target.value); + setError(""); + }} + placeholder={t.exchange.cdkPlaceholder} + className="w-full bg-surface-light border border-border rounded-lg px-4 py-4 text-foreground placeholder:text-foreground/40 focus:outline-none focus:border-accent/50 transition-colors font-mono text-center text-lg tracking-wider" + maxLength={32} + disabled={loading} + /> +
+ + {error && ( +
+ {error} +
+ )} + + + +
+ + ← {t.exchange.backHome} + +
+
+
+
+
+ + {/* Success Modal */} + {showModal && result && ( +
+
+
+ + +
+
+ + + +
+

{t.exchange.successTitle}

+
+ +
+ {/* New CDK */} +
+
{t.exchange.newCdk}
+
+ {result.new_cdk} + +
+ {copied && ( +
{t.exchange.copySuccess}
+ )} +
+ + {/* Stats */} +
+
+
{t.exchange.points}
+
{result.points}
+
+
+
{t.exchange.remainingDays}
+
{result.remaining_days}
+
+
+ + {/* Expiry */} +
+
{t.exchange.expiredAt}
+
{result.expired_at}
+
+ + +
+
+
+ )} + + {/* Footer */} +
+
+
+ +
+ M +
+ MaaAI + +

{t.footer.copyright}

+
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index 6720b2a..cd97780 100644 --- a/app/globals.css +++ b/app/globals.css @@ -97,6 +97,7 @@ body { mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: xor; mask-composite: exclude; + pointer-events: none; } /* Animations */ diff --git a/app/page.tsx b/app/page.tsx index 31dd95f..4ba05e7 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -44,8 +44,8 @@ const GitHubIcon = () => ( const SunIcon = () => ( - - + + @@ -64,6 +64,15 @@ const LanguageIcon = () => ( ); +const GiftIcon = () => ( + + + + + + +); + function getSystemLocale(): Locale { if (typeof navigator === "undefined") return "en"; const lang = navigator.language.toLowerCase(); @@ -71,11 +80,20 @@ function getSystemLocale(): Locale { return "en"; } +// 下载链接(待填写) +const DOWNLOAD_URL = ""; + export default function Home() { const [locale, setLocale] = useState("en"); const [theme, setTheme] = useState("system"); const [mounted, setMounted] = useState(false); const [command, setCommand] = useState(""); + + // 演示模拟状态 + const [isSimulating, setIsSimulating] = useState(false); + const [simulationLines, setSimulationLines] = useState>([]); + const [currentTypingText, setCurrentTypingText] = useState(""); + const [simulationComplete, setSimulationComplete] = useState(false); const t = locales[locale]; @@ -160,6 +178,76 @@ export default function Home() { steps: ["Launching Notepad...", "Window detected: Untitled - Notepad", "Typing text: \"Hello World\"", "Task completed successfully"] }; + // 流式输出单行文字 + const typeText = (text: string, onComplete: () => void) => { + let index = 0; + setCurrentTypingText(""); + const interval = setInterval(() => { + if (index < text.length) { + setCurrentTypingText(text.slice(0, index + 1)); + index++; + } else { + clearInterval(interval); + onComplete(); + } + }, 30 + Math.random() * 20); // 随机延迟模拟真实打字效果 + return interval; + }; + + // 执行演示模拟 + const runSimulation = async (inputCommand: string) => { + if (!inputCommand.trim() || isSimulating) return; + + setIsSimulating(true); + setSimulationComplete(false); + setSimulationLines([]); + setCurrentTypingText(""); + + const sim = t.demo.simulation; + const steps: Array<{ text: string; type: "info" | "warning" | "error" | "success"; delay: number; instant?: boolean }> = [ + { text: `> ${inputCommand}`, type: "info", delay: 300 }, + { text: sim.analyzing, type: "info", delay: 800 }, + { text: sim.initializing, type: "info", delay: 1000 }, + { text: sim.connecting, type: "info", delay: 1200 }, + { text: sim.browserDetected, type: "warning", delay: 400, instant: true }, + { text: sim.limitation, type: "error", delay: 500, instant: true }, + { text: sim.suggestion, type: "warning", delay: 400, instant: true }, + { text: sim.download, type: "success", delay: 400, instant: true }, + { text: sim.redirecting, type: "info", delay: 1500, instant: true }, + ]; + + for (let i = 0; i < steps.length; i++) { + const step = steps[i]; + + if (step.instant) { + // 直接显示整行 + setSimulationLines(prev => [...prev, { text: step.text, type: step.type }]); + } else { + // 流式打字效果 + await new Promise((resolve) => { + typeText(step.text, () => { + setSimulationLines(prev => [...prev, { text: step.text, type: step.type }]); + setCurrentTypingText(""); + resolve(); + }); + }); + } + + // 每行之间的延迟 + await new Promise(resolve => setTimeout(resolve, step.delay)); + } + + setSimulationComplete(true); + setIsSimulating(false); + + // 自动跳转到下载页面 + if (DOWNLOAD_URL) { + setTimeout(() => { + window.open(DOWNLOAD_URL, "_blank"); + }, 1000); + } + }; + if (!mounted) return null; return ( @@ -211,7 +299,10 @@ export default function Home() {

{t.hero.description}

@@ -271,23 +362,116 @@ export default function Home() {
+ {/* 输入区域 */}
- setCommand(e.target.value)} placeholder={t.demo.placeholder} - className="flex-1 bg-surface-light border border-border rounded-lg px-4 py-3 text-foreground placeholder:text-foreground/40 focus:outline-none focus:border-accent/50 transition-colors"/> - + setCommand(e.target.value)} + placeholder={t.demo.placeholder} + disabled={isSimulating} + onKeyDown={(e) => e.key === "Enter" && runSimulation(command)} + className="flex-1 bg-surface-light border border-border rounded-lg px-4 py-3 text-foreground placeholder:text-foreground/40 focus:outline-none focus:border-accent/50 transition-colors disabled:opacity-50" + /> +
-
-

{t.demo.examples}

- {useCases.map((useCase, index) => ( - - ))} -
+ )} +
+ )} + + {/* 示例命令列表 */} + {!simulationLines.length && !currentTypingText && ( +
+

{t.demo.examples}

+ {useCases.map((useCase, index) => ( + + ))} +
+ )}
diff --git a/locales/en.json b/locales/en.json index 9ef0c8d..aa5ec28 100644 --- a/locales/en.json +++ b/locales/en.json @@ -10,7 +10,8 @@ "titleHighlight": "With Natural Language", "description": "MaaAI is an intelligent AI agent that understands your commands and automates Windows and Android device operations. Just describe what you want to do.", "getStarted": "Get Started", - "seeDemo": "See Demo" + "seeDemo": "See Demo", + "exchangeCdk": "Exchange Trial Access" }, "features": { "title": "Powerful Capabilities", @@ -37,7 +38,18 @@ "description": "See how natural language commands translate to device actions.", "placeholder": "Type a command... e.g., 'Open Calculator'", "execute": "Execute", - "examples": "Example commands:" + "examples": "Example commands:", + "simulation": { + "analyzing": "Analyzing command content...", + "initializing": "Initializing MaaAI Agent core engine...", + "connecting": "Attempting to connect to local device control service...", + "browserDetected": "Detected: Currently running in Web browser environment", + "limitation": "Due to browser security sandbox restrictions, unable to access local device control interface", + "suggestion": "Full device automation capabilities require running in the desktop client environment", + "download": "Please download MaaAI Desktop Client for the complete experience", + "redirecting": "Opening download page for you...", + "downloadBtn": "Download Client Now" + } }, "howItWorks": { "title": "How It Works", @@ -64,5 +76,31 @@ "footer": { "documentation": "Documentation", "copyright": "© 2025 MaaAI. Open Source Project." + }, + "exchange": { + "title": "Exchange Trial Access", + "description": "Enter your CDK activation code to exchange remaining validity for points and get new trial access.", + "cdkPlaceholder": "Enter CDK activation code", + "exchangeButton": "Exchange Now", + "exchanging": "Exchanging...", + "successTitle": "Exchange Successful!", + "newCdk": "New CDK", + "points": "Points Earned", + "remainingDays": "Remaining Days", + "expiredAt": "Expires At", + "copySuccess": "Copied!", + "copy": "Copy", + "close": "Close", + "backHome": "Back to Home", + "errors": { + "emptyCdk": "Please enter CDK activation code", + "invalidFormat": "Invalid CDK format", + "notFound": "Order not found", + "alreadyExchanged": "This CDK has already been exchanged", + "expired": "CDK has expired", + "tooShort": "Remaining time too short, no points to exchange", + "serverError": "Server error, please try again later", + "networkError": "Network error, please check your connection" + } } -} +} \ No newline at end of file diff --git a/locales/zh.json b/locales/zh.json index 95767cd..227d0ee 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -10,7 +10,8 @@ "titleHighlight": "控制你的设备", "description": "MaaAI 是一个智能 AI Agent,能够理解你的指令并自动化操作 Windows 和 Android 设备。只需描述你想做什么。", "getStarted": "开始使用", - "seeDemo": "查看演示" + "seeDemo": "查看演示", + "exchangeCdk": "兑换体验资格" }, "features": { "title": "强大功能", @@ -37,7 +38,18 @@ "description": "看看自然语言指令如何转化为设备操作。", "placeholder": "输入指令... 例如:'打开计算器'", "execute": "执行", - "examples": "示例指令:" + "examples": "示例指令:", + "simulation": { + "analyzing": "正在分析指令内容...", + "initializing": "初始化 MaaAI Agent 核心引擎...", + "connecting": "尝试连接本地设备控制服务...", + "browserDetected": "检测到当前运行于 Web 浏览器环境", + "limitation": "由于浏览器安全沙箱限制,无法直接访问本地设备控制接口", + "suggestion": "完整的设备自动化功能需要在桌面客户端环境中运行", + "download": "建议下载 MaaAI 桌面客户端以获取完整体验", + "redirecting": "正在为您打开下载页面...", + "downloadBtn": "立即下载客户端" + } }, "howItWorks": { "title": "工作原理", @@ -64,5 +76,31 @@ "footer": { "documentation": "文档", "copyright": "© 2025 MaaAI. 开源项目。" + }, + "exchange": { + "title": "兑换体验资格", + "description": "输入您的 CDK 激活码,将剩余有效期兑换为积分,获取新的体验资格。", + "cdkPlaceholder": "请输入 CDK 激活码", + "exchangeButton": "立即兑换", + "exchanging": "兑换中...", + "successTitle": "兑换成功!", + "newCdk": "新 CDK", + "points": "获得积分", + "remainingDays": "剩余天数", + "expiredAt": "到期时间", + "copySuccess": "复制成功!", + "copy": "复制", + "close": "关闭", + "backHome": "返回首页", + "errors": { + "emptyCdk": "请输入 CDK 激活码", + "invalidFormat": "CDK 格式无效", + "notFound": "订单未找到", + "alreadyExchanged": "该 CDK 已兑换过", + "expired": "CDK 已过期", + "tooShort": "剩余时间太短,无积分可兑换", + "serverError": "服务器错误,请稍后重试", + "networkError": "网络错误,请检查网络连接" + } } }