From 595095e6a7584ed68e4ef1c063cbeebbb803c251 Mon Sep 17 00:00:00 2001 From: MistEO Date: Wed, 31 Dec 2025 16:50:36 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Ecdk=E5=85=91?= =?UTF-8?q?=E6=8D=A2=E9=A1=B5=E9=9D=A2=EF=BC=88=E6=B2=A1=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/exchange/page.tsx | 394 ++++++++++++++++++++++++++++++++++++++++++ app/page.tsx | 17 +- locales/en.json | 31 +++- locales/zh.json | 29 +++- 4 files changed, 466 insertions(+), 5 deletions(-) create mode 100644 app/exchange/page.tsx 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/page.tsx b/app/page.tsx index 31dd95f..5bb3d5f 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(); @@ -213,6 +222,10 @@ export default function Home() { diff --git a/locales/en.json b/locales/en.json index 9ef0c8d..403069e 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", @@ -64,5 +65,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..68eabe0 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": "强大功能", @@ -64,5 +65,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": "网络错误,请检查网络连接" + } } } From 6b1d5d35f1c2c55264835004730cb2deed47d29b Mon Sep 17 00:00:00 2001 From: MistEO Date: Wed, 31 Dec 2025 17:30:24 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E6=B2=A1=E5=8F=8D=E5=BA=94=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/globals.css | 1 + 1 file changed, 1 insertion(+) 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 */ From 2b06594adce3016f183bbb83d1504925bee94672 Mon Sep 17 00:00:00 2001 From: MistEO Date: Wed, 31 Dec 2025 17:33:09 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E5=85=91=E6=8D=A2=E4=BD=93?= =?UTF-8?q?=E9=AA=8C=E8=B5=84=E6=A0=BC=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/page.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 5bb3d5f..a2b110e 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -220,12 +220,11 @@ export default function Home() {

{t.hero.description}

From 0c77cde2752980d3adb96cdf1080badff1bb0ee5 Mon Sep 17 00:00:00 2001 From: MistEO Date: Wed, 31 Dec 2025 17:41:22 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=E4=BA=B2=E8=87=AA=E4=BD=93?= =?UTF-8?q?=E9=AA=8C.jpg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/page.tsx | 200 ++++++++++++++++++++++++++++++++++++++++++++---- locales/en.json | 13 +++- locales/zh.json | 13 +++- 3 files changed, 210 insertions(+), 16 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index a2b110e..4ba05e7 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -80,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]; @@ -169,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 ( @@ -283,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 403069e..aa5ec28 100644 --- a/locales/en.json +++ b/locales/en.json @@ -38,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", diff --git a/locales/zh.json b/locales/zh.json index 68eabe0..227d0ee 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -38,7 +38,18 @@ "description": "看看自然语言指令如何转化为设备操作。", "placeholder": "输入指令... 例如:'打开计算器'", "execute": "执行", - "examples": "示例指令:" + "examples": "示例指令:", + "simulation": { + "analyzing": "正在分析指令内容...", + "initializing": "初始化 MaaAI Agent 核心引擎...", + "connecting": "尝试连接本地设备控制服务...", + "browserDetected": "检测到当前运行于 Web 浏览器环境", + "limitation": "由于浏览器安全沙箱限制,无法直接访问本地设备控制接口", + "suggestion": "完整的设备自动化功能需要在桌面客户端环境中运行", + "download": "建议下载 MaaAI 桌面客户端以获取完整体验", + "redirecting": "正在为您打开下载页面...", + "downloadBtn": "立即下载客户端" + } }, "howItWorks": { "title": "工作原理",