From 266481caee3c725897116840936e144ca400157b Mon Sep 17 00:00:00 2001 From: icy Date: Sun, 2 Nov 2025 02:31:19 +0800 Subject: [PATCH 01/32] Add beta mode --- api/server.go | 39 +++++ config/database.go | 113 ++++++++++++++ generate_beta_code.sh | 221 ++++++++++++++++++++++++++++ main.go | 42 ++++++ web/src/components/RegisterPage.tsx | 44 +++++- web/src/contexts/AuthContext.tsx | 11 +- 6 files changed, 464 insertions(+), 6 deletions(-) create mode 100755 generate_beta_code.sh diff --git a/api/server.go b/api/server.go index ad16470a..a3c18431 100644 --- a/api/server.go +++ b/api/server.go @@ -166,8 +166,13 @@ func (s *Server) handleGetSystemConfig(c *gin.Context) { altcoinLeverage = val } + // 获取内测模式配置 + betaModeStr, _ := s.database.GetSystemConfig("beta_mode") + betaMode := betaModeStr == "true" + c.JSON(http.StatusOK, gin.H{ "admin_mode": auth.IsAdminMode(), + "beta_mode": betaMode, "default_coins": defaultCoins, "btc_eth_leverage": btcEthLeverage, "altcoin_leverage": altcoinLeverage, @@ -1168,6 +1173,7 @@ func (s *Server) handleRegister(c *gin.Context) { var req struct { Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required,min=6"` + BetaCode string `json:"beta_code"` } if err := c.ShouldBindJSON(&req); err != nil { @@ -1175,6 +1181,27 @@ func (s *Server) handleRegister(c *gin.Context) { return } + // 检查是否开启了内测模式 + betaModeStr, _ := s.database.GetSystemConfig("beta_mode") + if betaModeStr == "true" { + // 内测模式下必须提供有效的内测码 + if req.BetaCode == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "内测期间,注册需要提供内测码"}) + return + } + + // 验证内测码 + isValid, err := s.database.ValidateBetaCode(req.BetaCode) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "验证内测码失败"}) + return + } + if !isValid { + c.JSON(http.StatusBadRequest, gin.H{"error": "内测码无效或已被使用"}) + return + } + } + // 检查邮箱是否已存在 _, err := s.database.GetUserByEmail(req.Email) if err == nil { @@ -1212,6 +1239,18 @@ func (s *Server) handleRegister(c *gin.Context) { return } + // 如果是内测模式,标记内测码为已使用 + betaModeStr2, _ := s.database.GetSystemConfig("beta_mode") + if betaModeStr2 == "true" && req.BetaCode != "" { + err := s.database.UseBetaCode(req.BetaCode, req.Email) + if err != nil { + log.Printf("⚠️ 标记内测码为已使用失败: %v", err) + // 这里不返回错误,因为用户已经创建成功 + } else { + log.Printf("✓ 内测码 %s 已被用户 %s 使用", req.BetaCode, req.Email) + } + } + // 返回OTP设置信息 qrCodeURL := auth.GetOTPQRCodeURL(otpSecret, req.Email) c.JSON(http.StatusOK, gin.H{ diff --git a/config/database.go b/config/database.go index c5eef755..686f9346 100644 --- a/config/database.go +++ b/config/database.go @@ -6,6 +6,7 @@ import ( "encoding/base32" "fmt" "log" + "os" "strings" "time" @@ -125,6 +126,15 @@ func (d *Database) createTables() error { updated_at DATETIME DEFAULT CURRENT_TIMESTAMP )`, + // 内测码表 + `CREATE TABLE IF NOT EXISTS beta_codes ( + code TEXT PRIMARY KEY, + used BOOLEAN DEFAULT 0, + used_by TEXT DEFAULT '', + used_at DATETIME DEFAULT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + )`, + // 触发器:自动更新 updated_at `CREATE TRIGGER IF NOT EXISTS update_users_updated_at AFTER UPDATE ON users @@ -246,6 +256,7 @@ func (d *Database) initDefaultData() error { // 初始化系统配置 - 创建所有字段,设置默认值,后续由config.json同步更新 systemConfigs := map[string]string{ "admin_mode": "true", // 默认开启管理员模式,便于首次使用 + "beta_mode": "false", // 默认关闭内测模式 "api_server_port": "8080", // 默认API端口 "use_default_coins": "true", // 默认使用内置币种列表 "default_coins": `["BTCUSDT","ETHUSDT","SOLUSDT","BNBUSDT","XRPUSDT","DOGEUSDT","ADAUSDT","HYPEUSDT"]`, // 默认币种列表(JSON格式) @@ -943,4 +954,106 @@ func (d *Database) UpdateUserSignalSource(userID, coinPoolURL, oiTopURL string) // Close 关闭数据库连接 func (d *Database) Close() error { return d.db.Close() +} + +// LoadBetaCodesFromFile 从文件加载内测码到数据库 +func (d *Database) LoadBetaCodesFromFile(filePath string) error { + // 读取文件内容 + content, err := os.ReadFile(filePath) + if err != nil { + return fmt.Errorf("读取内测码文件失败: %w", err) + } + + // 按行分割内测码 + lines := strings.Split(string(content), "\n") + var codes []string + for _, line := range lines { + code := strings.TrimSpace(line) + if code != "" && !strings.HasPrefix(code, "#") { + codes = append(codes, code) + } + } + + // 批量插入内测码 + tx, err := d.db.Begin() + if err != nil { + return fmt.Errorf("开始事务失败: %w", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(`INSERT OR IGNORE INTO beta_codes (code) VALUES (?)`) + if err != nil { + return fmt.Errorf("准备语句失败: %w", err) + } + defer stmt.Close() + + insertedCount := 0 + for _, code := range codes { + result, err := stmt.Exec(code) + if err != nil { + log.Printf("插入内测码 %s 失败: %v", code, err) + continue + } + + if rowsAffected, _ := result.RowsAffected(); rowsAffected > 0 { + insertedCount++ + } + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("提交事务失败: %w", err) + } + + log.Printf("✅ 成功加载 %d 个内测码到数据库 (总计 %d 个)", insertedCount, len(codes)) + return nil +} + +// ValidateBetaCode 验证内测码是否有效且未使用 +func (d *Database) ValidateBetaCode(code string) (bool, error) { + var used bool + err := d.db.QueryRow(`SELECT used FROM beta_codes WHERE code = ?`, code).Scan(&used) + if err != nil { + if err == sql.ErrNoRows { + return false, nil // 内测码不存在 + } + return false, err + } + return !used, nil // 内测码存在且未使用 +} + +// UseBetaCode 使用内测码(标记为已使用) +func (d *Database) UseBetaCode(code, userEmail string) error { + result, err := d.db.Exec(` + UPDATE beta_codes SET used = 1, used_by = ?, used_at = CURRENT_TIMESTAMP + WHERE code = ? AND used = 0 + `, userEmail, code) + if err != nil { + return err + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + + if rowsAffected == 0 { + return fmt.Errorf("内测码无效或已被使用") + } + + return nil +} + +// GetBetaCodeStats 获取内测码统计信息 +func (d *Database) GetBetaCodeStats() (total, used int, err error) { + err = d.db.QueryRow(`SELECT COUNT(*) FROM beta_codes`).Scan(&total) + if err != nil { + return 0, 0, err + } + + err = d.db.QueryRow(`SELECT COUNT(*) FROM beta_codes WHERE used = 1`).Scan(&used) + if err != nil { + return 0, 0, err + } + + return total, used, nil } \ No newline at end of file diff --git a/generate_beta_code.sh b/generate_beta_code.sh new file mode 100755 index 00000000..cee2ca3a --- /dev/null +++ b/generate_beta_code.sh @@ -0,0 +1,221 @@ +#!/bin/bash + +# 内测码生成脚本 +# 生成6位不重复的内测码并写入 beta_codes.txt + +BETA_CODES_FILE="beta_codes.txt" +COUNT=1 +LIST_ONLY=false +CODE_LENGTH=6 + +# 字符集(避免易混淆字符:0/O, 1/I/l) +CHARSET="23456789abcdefghjkmnpqrstuvwxyz" + +# 显示帮助信息 +show_help() { + cat << EOF +用法: $0 [选项] + +选项: + -c COUNT 生成内测码数量 (默认: 1) + -l 列出现有内测码 + -f FILE 内测码文件路径 (默认: beta_codes.txt) + -h 显示此帮助信息 + +示例: + $0 -c 10 # 生成10个内测码 + $0 -l # 列出现有内测码 + $0 -f custom.txt -c 5 # 在自定义文件中生成5个内测码 +EOF +} + +# 生成随机内测码 +generate_beta_code() { + local length="$1" + local charset="$2" + local code="" + + for ((i=0; i/dev/null | tr -d ' \t' | grep -v '^#' || true + fi +} + +# 检查内测码是否已存在 +code_exists() { + local code="$1" + local file="$2" + if [ -f "$file" ]; then + grep -Fxq "$code" "$file" 2>/dev/null + else + return 1 + fi +} + +# 添加内测码到文件 +add_code_to_file() { + local code="$1" + local file="$2" + echo "$code" >> "$file" +} + +# 验证内测码格式 +validate_code() { + local code="$1" + # 检查长度 + if [ ${#code} -ne $CODE_LENGTH ]; then + return 1 + fi + # 检查字符是否都在允许的字符集中 + if [[ ! "$code" =~ ^[$CHARSET]+$ ]]; then + return 1 + fi + return 0 +} + +# 去重并排序内测码 +dedupe_and_sort_codes() { + local file="$1" + if [ -f "$file" ]; then + # 过滤空行和注释,去重并排序 + grep -v '^$' "$file" | grep -v '^#' | sort -u > "${file}.tmp" && mv "${file}.tmp" "$file" + fi +} + +# 解析命令行参数 +while getopts "c:lf:h" opt; do + case $opt in + c) + COUNT="$OPTARG" + if ! [[ "$COUNT" =~ ^[0-9]+$ ]] || [ "$COUNT" -lt 1 ]; then + echo "错误: count 必须是正整数" >&2 + exit 1 + fi + ;; + l) + LIST_ONLY=true + ;; + f) + BETA_CODES_FILE="$OPTARG" + ;; + h) + show_help + exit 0 + ;; + \?) + echo "无效选项: -$OPTARG" >&2 + echo "使用 -h 查看帮助信息" >&2 + exit 1 + ;; + esac +done + +# 如果是列出现有内测码 +if [ "$LIST_ONLY" = true ]; then + if [ -f "$BETA_CODES_FILE" ]; then + existing_codes=$(read_existing_codes "$BETA_CODES_FILE") + if [ -z "$existing_codes" ]; then + echo "内测码列表为空" + else + count=$(echo "$existing_codes" | wc -l | tr -d ' ') + echo "当前内测码 ($count 个):" + echo "$existing_codes" | nl -w3 -s'. ' + fi + else + echo "内测码文件不存在: $BETA_CODES_FILE" + fi + exit 0 +fi + +# 读取现有内测码 +existing_codes=$(read_existing_codes "$BETA_CODES_FILE") + +# 生成新内测码 +new_codes=() +max_attempts=1000 # 防止无限循环 + +echo "正在生成 $COUNT 个内测码..." + +for ((i=1; i<=COUNT; i++)); do + attempts=0 + while [ $attempts -lt $max_attempts ]; do + code=$(generate_beta_code $CODE_LENGTH "$CHARSET") + + # 验证格式 + if ! validate_code "$code"; then + ((attempts++)) + continue + fi + + # 检查是否已存在 + if code_exists "$code" "$BETA_CODES_FILE"; then + ((attempts++)) + continue + fi + + # 检查是否与本次生成的重复 + duplicate=false + for existing_code in "${new_codes[@]}"; do + if [ "$code" = "$existing_code" ]; then + duplicate=true + break + fi + done + + if [ "$duplicate" = false ]; then + new_codes+=("$code") + break + fi + + ((attempts++)) + done + + if [ $attempts -eq $max_attempts ]; then + echo "警告: 生成第 $i 个内测码时达到最大尝试次数,可能字符空间不足" >&2 + break + fi +done + +# 检查是否成功生成了内测码 +if [ ${#new_codes[@]} -eq 0 ]; then + echo "未能生成任何新的内测码" + exit 1 +fi + +# 添加到文件 +for code in "${new_codes[@]}"; do + add_code_to_file "$code" "$BETA_CODES_FILE" +done + +# 去重并排序 +dedupe_and_sort_codes "$BETA_CODES_FILE" + +echo "成功生成 ${#new_codes[@]} 个内测码:" +printf ' %s\n' "${new_codes[@]}" +echo +echo "内测码文件: $BETA_CODES_FILE" + +# 显示当前总数 +if [ -f "$BETA_CODES_FILE" ]; then + total_count=$(read_existing_codes "$BETA_CODES_FILE" | wc -l | tr -d ' ') + echo "当前内测码总计: $total_count 个" +fi + +# 显示文件头部信息(如果是新文件) +if [ ! -s "$BETA_CODES_FILE" ] || [ $(wc -l < "$BETA_CODES_FILE") -eq ${#new_codes[@]} ]; then + echo + echo "内测码规则:" + echo "- 长度: $CODE_LENGTH 位" + echo "- 字符集: 数字 2-9, 小写字母 a-z (排除 0,1,i,l,o 避免混淆)" + echo "- 每个内测码唯一且不重复" +fi \ No newline at end of file diff --git a/main.go b/main.go index 1d9631a9..69fa8064 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,7 @@ type LeverageConfig struct { // ConfigFile 配置文件结构,只包含需要同步到数据库的字段 type ConfigFile struct { AdminMode bool `json:"admin_mode"` + BetaMode bool `json:"beta_mode"` APIServerPort int `json:"api_server_port"` UseDefaultCoins bool `json:"use_default_coins"` DefaultCoins []string `json:"default_coins"` @@ -62,6 +63,7 @@ func syncConfigToDatabase(database *config.Database) error { // 同步各配置项到数据库 configs := map[string]string{ "admin_mode": fmt.Sprintf("%t", configFile.AdminMode), + "beta_mode": fmt.Sprintf("%t", configFile.BetaMode), "api_server_port": strconv.Itoa(configFile.APIServerPort), "use_default_coins": fmt.Sprintf("%t", configFile.UseDefaultCoins), "coin_pool_api_url": configFile.CoinPoolAPIURL, @@ -105,6 +107,41 @@ func syncConfigToDatabase(database *config.Database) error { return nil } +// loadBetaCodesToDatabase 加载内测码文件到数据库 +func loadBetaCodesToDatabase(database *config.Database) error { + betaCodeFile := "beta_codes.txt" + + // 检查内测码文件是否存在 + if _, err := os.Stat(betaCodeFile); os.IsNotExist(err) { + log.Printf("📄 内测码文件 %s 不存在,跳过加载", betaCodeFile) + return nil + } + + // 获取文件信息 + fileInfo, err := os.Stat(betaCodeFile) + if err != nil { + return fmt.Errorf("获取内测码文件信息失败: %w", err) + } + + log.Printf("🔄 发现内测码文件 %s (%.1f KB),开始加载...", betaCodeFile, float64(fileInfo.Size())/1024) + + // 加载内测码到数据库 + err = database.LoadBetaCodesFromFile(betaCodeFile) + if err != nil { + return fmt.Errorf("加载内测码失败: %w", err) + } + + // 显示统计信息 + total, used, err := database.GetBetaCodeStats() + if err != nil { + log.Printf("⚠️ 获取内测码统计失败: %v", err) + } else { + log.Printf("✅ 内测码加载完成: 总计 %d 个,已使用 %d 个,剩余 %d 个", total, used, total-used) + } + + return nil +} + func main() { fmt.Println("╔════════════════════════════════════════════════════════════╗") fmt.Println("║ 🤖 AI多模型交易系统 - 支持 DeepSeek & Qwen ║") @@ -129,6 +166,11 @@ func main() { log.Printf("⚠️ 同步config.json到数据库失败: %v", err) } + // 加载内测码到数据库 + if err := loadBetaCodesToDatabase(database); err != nil { + log.Printf("⚠️ 加载内测码到数据库失败: %v", err) + } + // 获取系统配置 useDefaultCoinsStr, _ := database.GetSystemConfig("use_default_coins") useDefaultCoins := useDefaultCoinsStr == "true" diff --git a/web/src/components/RegisterPage.tsx b/web/src/components/RegisterPage.tsx index 01691aac..c6f23175 100644 --- a/web/src/components/RegisterPage.tsx +++ b/web/src/components/RegisterPage.tsx @@ -1,7 +1,8 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { useAuth } from '../contexts/AuthContext'; import { useLanguage } from '../contexts/LanguageContext'; import { t } from '../i18n/translations'; +import { getSystemConfig } from '../lib/config'; export function RegisterPage() { const { language } = useLanguage(); @@ -10,12 +11,23 @@ export function RegisterPage() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); + const [betaCode, setBetaCode] = useState(''); const [otpCode, setOtpCode] = useState(''); const [userID, setUserID] = useState(''); const [otpSecret, setOtpSecret] = useState(''); const [qrCodeURL, setQrCodeURL] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); + const [betaMode, setBetaMode] = useState(false); + + useEffect(() => { + // 获取系统配置,检查是否开启内测模式 + getSystemConfig().then(config => { + setBetaMode(config.beta_mode || false); + }).catch(err => { + console.error('Failed to fetch system config:', err); + }); + }, []); const handleRegister = async (e: React.FormEvent) => { e.preventDefault(); @@ -31,9 +43,14 @@ export function RegisterPage() { return; } + if (betaMode && !betaCode.trim()) { + setError('内测期间,注册需要提供内测码'); + return; + } + setLoading(true); - const result = await register(email, password); + const result = await register(email, password, betaCode.trim() || undefined); if (result.success && result.userID) { setUserID(result.userID); @@ -137,6 +154,27 @@ export function RegisterPage() { /> + {betaMode && ( +
+ + setBetaCode(e.target.value.replace(/[^a-z0-9]/gi, '').toLowerCase())} + className="w-full px-3 py-2 rounded font-mono" + style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }} + placeholder="请输入6位内测码" + maxLength={6} + required={betaMode} + /> +

+ 内测码由6位字母数字组成,区分大小写 +

+
+ )} + {error && (
{error} @@ -145,7 +183,7 @@ export function RegisterPage() { +
+ {/* Left Side - Navigation Tabs */} +
+ {isLoggedIn ? ( + // Main app navigation when logged in + <> + + + + + + + ) : ( + // Landing page navigation when not logged in + { + if (currentPage !== 'competition') { + e.currentTarget.style.color = 'var(--brand-yellow)'; + } + }} + onMouseLeave={(e) => { + if (currentPage !== 'competition') { + e.currentTarget.style.color = 'var(--brand-light-gray)'; + } + }} + > + {/* Background for selected state */} + {currentPage === 'competition' && ( + + )} + + {t('realtimeNav', language)} + + )} +
+ + {/* Right Side - Original Navigation Items and Login */} +
+ {/* Only show original navigation items on home page */} + {isHomePage && [ + { key: 'features', label: t('features', language) }, + { key: 'howItWorks', label: t('howItWorks', language) }, + { key: 'GitHub', label: 'GitHub' }, + { key: 'community', label: t('community', language) } + ].map((item) => ( + + {item.label} + + + ))} + + {/* User Info and Actions */} + {isLoggedIn && user ? ( +
+ {/* User Info with Dropdown */} +
+ + + {userDropdownOpen && ( +
+
+
{t('loggedInAs', language)}
+
{user.email}
+
+ {!isAdminMode && onLogout && ( + + )} +
+ )} +
+
+ ) : ( + /* Show login/register buttons when not logged in and not on login/register pages */ + currentPage !== 'login' && currentPage !== 'register' && ( + + ) + )} + + {/* Language Toggle - Always at the rightmost */} +
+ + + {languageDropdownOpen && ( +
+ + +
+ )} +
+
{/* Mobile Menu Button */} @@ -74,21 +381,232 @@ export default function HeaderBar({ onLoginClick }: { onLoginClick: () => void } style={{ background: 'var(--brand-dark-gray)', borderTop: '1px solid rgba(240, 185, 11, 0.1)' }} >
- {['功能', '如何运作', 'GitHub', '社区'].map((item) => ( - - {item} + {/* New Navigation Tabs */} + {isLoggedIn ? ( + + ) : ( + + {/* Background for selected state */} + {currentPage === 'competition' && ( + + )} + + {t('realtimeNav', language)} + + )} + {/* Only show 配置 and 看板 when logged in */} + {isLoggedIn && ( + <> + + + + )} + + {/* Original Navigation Items - Only on home page */} + {isHomePage && [ + { key: 'features', label: t('features', language) }, + { key: 'howItWorks', label: t('howItWorks', language) }, + { key: 'GitHub', label: 'GitHub' }, + { key: 'community', label: t('community', language) } + ].map((item) => ( + + {item.label} ))} - + + {/* Language Toggle */} +
+
+ {t('language', language)}: +
+
+ + +
+
+ + {/* User info and logout for mobile when logged in */} + {isLoggedIn && user && ( +
+
+
+ {user.email[0].toUpperCase()} +
+
+
{t('loggedInAs', language)}
+
{user.email}
+
+
+ {!isAdminMode && onLogout && ( + + )} +
+ )} + + {/* Show login/register buttons when not logged in and not on login/register pages */} + {!isLoggedIn && currentPage !== 'login' && currentPage !== 'register' && ( +
+ setMobileMenuOpen(false)} + > + {t('signIn', language)} + + setMobileMenuOpen(false)} + > + {t('signUp', language)} + +
+ )}
diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts index ed5caaad..2aff0bdc 100644 --- a/web/src/i18n/translations.ts +++ b/web/src/i18n/translations.ts @@ -15,6 +15,11 @@ export const translations = { logout: 'Logout', switchTrader: 'Switch Trader:', view: 'View', + + // Navigation + realtimeNav: 'Live', + configNav: 'Config', + dashboardNav: 'Dashboard', // Footer footerTitle: 'NOFX - AI Trading System', @@ -317,6 +322,96 @@ export const translations = { passwordRequired: 'Password is required', invalidEmail: 'Invalid email format', passwordTooShort: 'Password must be at least 6 characters', + + // Landing Page + features: 'Features', + howItWorks: 'How it Works', + community: 'Community', + language: 'Language', + loggedInAs: 'Logged in as', + exitLogin: 'Sign Out', + signIn: 'Sign In', + signUp: 'Sign Up', + + // Hero Section + githubStarsInDays: '2.5K+ GitHub Stars in 3 days', + heroTitle1: 'Read the Market.', + heroTitle2: 'Write the Trade.', + heroDescription: 'NOFX is the future standard for AI trading — an open, community-driven agentic trading OS. Supporting Binance, Aster DEX and other exchanges, self-hosted, multi-agent competition, let AI automatically make decisions, execute and optimize trades for you.', + poweredBy: 'Powered by Aster DEX and Binance, strategically invested by Amber.ac.', + + // Landing Page CTA + readyToDefine: 'Ready to define the future of AI trading?', + startWithCrypto: 'Starting with crypto markets, expanding to TradFi. NOFX is the infrastructure of AgentFi.', + getStartedNow: 'Get Started Now', + viewSourceCode: 'View Source Code', + + // Features Section + coreFeatures: 'Core Features', + whyChooseNofx: 'Why Choose NOFX?', + openCommunityDriven: 'Open source, transparent, community-driven AI trading OS', + openSourceSelfHosted: '100% Open Source & Self-Hosted', + openSourceDesc: 'Your framework, your rules. Non-black box, supports custom prompts and multi-models.', + openSourceFeatures1: 'Fully open source code', + openSourceFeatures2: 'Self-hosting deployment support', + openSourceFeatures3: 'Custom AI prompts', + openSourceFeatures4: 'Multi-model support (DeepSeek, Qwen)', + multiAgentCompetition: 'Multi-Agent Intelligent Competition', + multiAgentDesc: 'AI strategies battle at high speed in sandbox, survival of the fittest, achieving strategy evolution.', + multiAgentFeatures1: 'Multiple AI agents running in parallel', + multiAgentFeatures2: 'Automatic strategy optimization', + multiAgentFeatures3: 'Sandbox security testing', + multiAgentFeatures4: 'Cross-market strategy porting', + secureReliableTrading: 'Secure and Reliable Trading', + secureDesc: 'Enterprise-grade security, complete control over your funds and trading strategies.', + secureFeatures1: 'Local private key management', + secureFeatures2: 'Fine-grained API permission control', + secureFeatures3: 'Real-time risk monitoring', + secureFeatures4: 'Trading log auditing', + + // About Section + aboutNofx: 'About NOFX', + whatIsNofx: 'What is NOFX?', + nofxNotAnotherBot: "NOFX is not another trading bot, but the 'Linux' of AI trading —", + nofxDescription1: 'a transparent, trustworthy open source OS that provides a unified', + nofxDescription2: "'decision-risk-execution' layer, supporting all asset classes.", + nofxDescription3: 'Starting with crypto markets (24/7, high volatility perfect testing ground), future expansion to stocks, futures, forex. Core: open architecture, AI', + nofxDescription4: 'Darwinism (multi-agent self-competition, strategy evolution), CodeFi', + nofxDescription5: 'flywheel (developers get point rewards for PR contributions).', + youFullControl: 'You 100% Control', + fullControlDesc: 'Complete control over AI prompts and funds', + startupMessages1: 'Starting automated trading system...', + startupMessages2: 'API server started on port 8080', + startupMessages3: 'Web console http://localhost:3000', + + // How It Works Section + howToStart: 'How to Get Started with NOFX', + fourSimpleSteps: 'Four simple steps to start your AI automated trading journey', + step1Title: 'Clone GitHub Repository', + step1Desc: 'git clone https://github.com/tinkle-community/nofx and switch to dev branch to test new features.', + step2Title: 'Configure Environment', + step2Desc: 'Frontend setup for exchange APIs (like Binance, Hyperliquid), AI models and custom prompts.', + step3Title: 'Deploy & Run', + step3Desc: 'One-click Docker deployment, start AI agents. Note: High-risk market, only test with money you can afford to lose.', + step4Title: 'Optimize & Contribute', + step4Desc: 'Monitor trading, submit PRs to improve framework. Join Telegram to share strategies.', + importantRiskWarning: 'Important Risk Warning', + riskWarningText: 'Dev branch is unstable, do not use funds you cannot afford to lose. NOFX is non-custodial, no official strategies. Trading involves risks, invest carefully.', + + // Community Section (testimonials are kept as-is since they are quotes) + + // Footer Section + futureStandardAI: 'The future standard of AI trading', + links: 'Links', + resources: 'Resources', + documentation: 'Documentation', + supporters: 'Supporters', + strategicInvestment: '(Strategic Investment)', + + // Login Modal + accessNofxPlatform: 'Access NOFX Platform', + loginRegisterPrompt: 'Please login or register to access the full AI trading platform', + registerNewAccount: 'Register New Account', }, zh: { // Header @@ -332,6 +427,11 @@ export const translations = { logout: '退出', switchTrader: '切换交易员:', view: '查看', + + // Navigation + realtimeNav: '实时', + configNav: '配置', + dashboardNav: '看板', // Footer footerTitle: 'NOFX - AI交易系统', @@ -634,6 +734,96 @@ export const translations = { passwordRequired: '请输入密码', invalidEmail: '邮箱格式不正确', passwordTooShort: '密码至少需要6个字符', + + // Landing Page + features: '功能', + howItWorks: '如何运作', + community: '社区', + language: '语言', + loggedInAs: '已登录为', + exitLogin: '退出登录', + signIn: '登录', + signUp: '注册', + + // Hero Section + githubStarsInDays: '3 天内 2.5K+ GitHub Stars', + heroTitle1: 'Read the Market.', + heroTitle2: 'Write the Trade.', + heroDescription: 'NOFX 是 AI 交易的未来标准——一个开放、社区驱动的代理式交易操作系统。支持 Binance、Aster DEX 等交易所,自托管、多代理竞争,让 AI 为你自动决策、执行和优化交易。', + poweredBy: '由 Aster DEX 和 Binance 提供支持,Amber.ac 战略投资。', + + // Landing Page CTA + readyToDefine: '准备好定义 AI 交易的未来吗?', + startWithCrypto: '从加密市场起步,扩展到 TradFi。NOFX 是 AgentFi 的基础架构。', + getStartedNow: '立即开始', + viewSourceCode: '查看源码', + + // Features Section + coreFeatures: '核心功能', + whyChooseNofx: '为什么选择 NOFX?', + openCommunityDriven: '开源、透明、社区驱动的 AI 交易操作系统', + openSourceSelfHosted: '100% 开源与自托管', + openSourceDesc: '你的框架,你的规则。非黑箱,支持自定义提示词和多模型。', + openSourceFeatures1: '完全开源代码', + openSourceFeatures2: '支持自托管部署', + openSourceFeatures3: '自定义 AI 提示词', + openSourceFeatures4: '多模型支持(DeepSeek、Qwen)', + multiAgentCompetition: '多代理智能竞争', + multiAgentDesc: 'AI 策略在沙盒中高速战斗,最优者生存,实现策略进化。', + multiAgentFeatures1: '多 AI 代理并行运行', + multiAgentFeatures2: '策略自动优化', + multiAgentFeatures3: '沙盒安全测试', + multiAgentFeatures4: '跨市场策略移植', + secureReliableTrading: '安全可靠交易', + secureDesc: '企业级安全保障,完全掌控你的资金和交易策略。', + secureFeatures1: '本地私钥管理', + secureFeatures2: 'API 权限精细控制', + secureFeatures3: '实时风险监控', + secureFeatures4: '交易日志审计', + + // About Section + aboutNofx: '关于 NOFX', + whatIsNofx: '什么是 NOFX?', + nofxNotAnotherBot: 'NOFX 不是另一个交易机器人,而是 AI 交易的 \'Linux\' ——', + nofxDescription1: '一个透明、可信任的开源 OS,提供统一的 \'决策-风险-执行\'', + nofxDescription2: '层,支持所有资产类别。', + nofxDescription3: '从加密市场起步(24/7、高波动性完美测试场),未来扩展到股票、期货、外汇。核心:开放架构、AI', + nofxDescription4: '达尔文主义(多代理自竞争、策略进化)、CodeFi 飞轮(开发者 PR', + nofxDescription5: '贡献获积分奖励)。', + youFullControl: '你 100% 掌控', + fullControlDesc: '完全掌控 AI 提示词和资金', + startupMessages1: ' 启动自动交易系统...', + startupMessages2: ' API服务器启动在端口 8080', + startupMessages3: ' Web 控制台 http://localhost:3000', + + // How It Works Section + howToStart: '如何开始使用 NOFX', + fourSimpleSteps: '四个简单步骤,开启 AI 自动交易之旅', + step1Title: '拉取 GitHub 仓库', + step1Desc: 'git clone https://github.com/tinkle-community/nofx 并切换到 dev 分支测试新功能。', + step2Title: '配置环境', + step2Desc: '前端设置交易所 API(如 Binance、Hyperliquid)、AI 模型和自定义提示词。', + step3Title: '部署与运行', + step3Desc: '一键 Docker 部署,启动 AI 代理。注意:高风险市场,仅用闲钱测试。', + step4Title: '优化与贡献', + step4Desc: '监控交易,提交 PR 改进框架。加入 Telegram 分享策略。', + importantRiskWarning: '重要风险提示', + riskWarningText: 'dev 分支不稳定,勿用无法承受损失的资金。NOFX 非托管,无官方策略。交易有风险,投资需谨慎。', + + // Community Section (testimonials are kept as-is since they are quotes) + + // Footer Section + futureStandardAI: 'AI 交易的未来标准', + links: '链接', + resources: '资源', + documentation: '文档', + supporters: '支持方', + strategicInvestment: '(战略投资)', + + // Login Modal + accessNofxPlatform: '访问 NOFX 平台', + loginRegisterPrompt: '请选择登录或注册以访问完整的 AI 交易平台', + registerNewAccount: '注册新账号', } }; From d8f0a154b4748c8853e5246d7a7dc2ec176b2665 Mon Sep 17 00:00:00 2001 From: icy Date: Sun, 2 Nov 2025 06:21:48 +0800 Subject: [PATCH 03/32] =?UTF-8?q?feat(i18n):=20internationalize=20landing?= =?UTF-8?q?=20page=20sections=20-=20Update=20AboutSection,=20FeaturesSecti?= =?UTF-8?q?on,=20HowItWorksSection=20to=20use=20language=20prop=20pattern?= =?UTF-8?q?=20-=20Replace=20useLanguage=20hook=20with=20language=20prop=20?= =?UTF-8?q?interface=20for=20consistency=20-=20Add=20comprehensive=20inter?= =?UTF-8?q?nationalization=20support=20for=20landing=20page=20content=20-?= =?UTF-8?q?=20Update=20HeroSection=20and=20LandingPage=20to=20support=20la?= =?UTF-8?q?nguage=20prop=20flow=20=F0=9F=A4=96=20Generated=20with=20[Claud?= =?UTF-8?q?e=20Code](https://claude.ai/code)=20Co-Authored-By:=20tinkle-co?= =?UTF-8?q?mmunity=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/landing/AboutSection.tsx | 33 ++++---- .../components/landing/FeaturesSection.tsx | 46 ++++++++--- web/src/components/landing/HeroSection.tsx | 81 ++++++++++++++++--- .../components/landing/HowItWorksSection.tsx | 23 +++--- web/src/pages/LandingPage.tsx | 58 +++++++++---- 5 files changed, 175 insertions(+), 66 deletions(-) diff --git a/web/src/components/landing/AboutSection.tsx b/web/src/components/landing/AboutSection.tsx index d087a66f..55ff9d9b 100644 --- a/web/src/components/landing/AboutSection.tsx +++ b/web/src/components/landing/AboutSection.tsx @@ -2,8 +2,13 @@ import { motion } from 'framer-motion' import { Shield, Target } from 'lucide-react' import AnimatedSection from './AnimatedSection' import Typewriter from '../Typewriter' +import { t, Language } from '../../i18n/translations' -export default function AboutSection() { +interface AboutSectionProps { + language: Language +} + +export default function AboutSection({ language }: AboutSectionProps) { return (
@@ -31,7 +36,7 @@ export default function AboutSection() { className='text-sm font-semibold' style={{ color: 'var(--brand-yellow)' }} > - 关于 NOFX + {t('aboutNofx', language)} @@ -39,23 +44,19 @@ export default function AboutSection() { className='text-4xl font-bold' style={{ color: 'var(--brand-light-gray)' }} > - 什么是 NOFX? + {t('whatIsNofx', language)}

- NOFX 不是另一个交易机器人,而是 AI 交易的 'Linux' —— - 一个透明、可信任的开源 OS,提供统一的 '决策-风险-执行' - 层,支持所有资产类别。 + {t('nofxNotAnotherBot', language)} {t('nofxDescription1', language)} {t('nofxDescription2', language)}

- 从加密市场起步(24/7、高波动性完美测试场),未来扩展到股票、期货、外汇。核心:开放架构、AI - 达尔文主义(多代理自竞争、策略进化)、CodeFi 飞轮(开发者 PR - 贡献获积分奖励)。 + {t('nofxDescription3', language)} {t('nofxDescription4', language)} {t('nofxDescription5', language)}

- 你 100% 掌控 + {t('youFullControl', language)}
- 完全掌控 AI 提示词和资金 + {t('fullControlDesc', language)}
@@ -101,16 +102,16 @@ export default function AboutSection() { '$ cd nofx', '$ chmod +x start.sh', '$ ./start.sh start --build', - ' 启动自动交易系统...', - ' API服务器启动在端口 8080', - ' Web 控制台 http://localhost:3000', + t('startupMessages1', language), + t('startupMessages2', language), + t('startupMessages3', language), ]} typingSpeed={70} lineDelay={900} className='text-sm font-mono' style={{ - color: '#00FF41', - textShadow: '0 0 6px rgba(0,255,65,0.6)', + color: '#00FF88', + textShadow: '0 0 8px rgba(0,255,136,0.4)', }} /> diff --git a/web/src/components/landing/FeaturesSection.tsx b/web/src/components/landing/FeaturesSection.tsx index 7eef7e00..3026405f 100644 --- a/web/src/components/landing/FeaturesSection.tsx +++ b/web/src/components/landing/FeaturesSection.tsx @@ -2,8 +2,13 @@ import { motion } from 'framer-motion' import AnimatedSection from './AnimatedSection' import { CryptoFeatureCard } from '../CryptoFeatureCard' import { Code, Cpu, Lock, Rocket } from 'lucide-react' +import { t, Language } from '../../i18n/translations' -export default function FeaturesSection() { +interface FeaturesSectionProps { + language: Language +} + +export default function FeaturesSection({ language }: FeaturesSectionProps) { return (
@@ -15,37 +20,52 @@ export default function FeaturesSection() { > - 核心功能 + {t('coreFeatures', language)}

- 为什么选择 NOFX? + {t('whyChooseNofx', language)}

- 开源、透明、社区驱动的 AI 交易操作系统 + {t('openCommunityDriven', language)}

} - title='100% 开源与自托管' - description='你的框架,你的规则。非黑箱,支持自定义提示词和多模型。' - features={['完全开源代码', '支持自托管部署', '自定义 AI 提示词', '多模型支持(DeepSeek、Qwen)']} + title={t('openSourceSelfHosted', language)} + description={t('openSourceDesc', language)} + features={[ + t('openSourceFeatures1', language), + t('openSourceFeatures2', language), + t('openSourceFeatures3', language), + t('openSourceFeatures4', language) + ]} delay={0} /> } - title='多代理智能竞争' - description='AI 策略在沙盒中高速战斗,最优者生存,实现策略进化。' - features={['多 AI 代理并行运行', '策略自动优化', '沙盒安全测试', '跨市场策略移植']} + title={t('multiAgentCompetition', language)} + description={t('multiAgentDesc', language)} + features={[ + t('multiAgentFeatures1', language), + t('multiAgentFeatures2', language), + t('multiAgentFeatures3', language), + t('multiAgentFeatures4', language) + ]} delay={0.1} /> } - title='安全可靠交易' - description='企业级安全保障,完全掌控你的资金和交易策略。' - features={['本地私钥管理', 'API 权限精细控制', '实时风险监控', '交易日志审计']} + title={t('secureReliableTrading', language)} + description={t('secureDesc', language)} + features={[ + t('secureFeatures1', language), + t('secureFeatures2', language), + t('secureFeatures3', language), + t('secureFeatures4', language) + ]} delay={0.2} />
diff --git a/web/src/components/landing/HeroSection.tsx b/web/src/components/landing/HeroSection.tsx index 61de5e7a..52656bf6 100644 --- a/web/src/components/landing/HeroSection.tsx +++ b/web/src/components/landing/HeroSection.tsx @@ -1,10 +1,16 @@ -import { motion, useScroll, useTransform } from 'framer-motion' +import { motion, useScroll, useTransform, useAnimation } from 'framer-motion' import { Sparkles } from 'lucide-react' +import { t, Language } from '../../i18n/translations' -export default function HeroSection() { +interface HeroSectionProps { + language: Language +} + +export default function HeroSection({ language }: HeroSectionProps) { const { scrollYProgress } = useScroll() const opacity = useTransform(scrollYProgress, [0, 0.2], [1, 0]) const scale = useTransform(scrollYProgress, [0, 0.2], [1, 0.8]) + const handControls = useAnimation() const fadeInUp = { initial: { opacity: 0, y: 60 }, @@ -27,40 +33,39 @@ export default function HeroSection() { > - 3 天内 2.5K+ GitHub Stars +{t('githubStarsInDays', language)}

- Read the Market. + {t('heroTitle1', language)}
- Write the Trade. + {t('heroTitle2', language)}

- NOFX 是 AI 交易的未来标准——一个开放、社区驱动的代理式交易操作系统。支持 Binance、Aster DEX 等交易所, - 自托管、多代理竞争,让 AI 为你自动决策、执行和优化交易。 + {t('heroDescription', language)}
GitHub Stars GitHub Forks GitHub Contributors @@ -68,12 +73,62 @@ export default function HeroSection() {
- 由 Aster DEX 和 Binance 提供支持,Amber.ac 战略投资。 +{t('poweredBy', language)} - {/* Right Visual */} - + {/* Right Visual - Interactive Robot */} +
{ + handControls.start({ + y: [-8, 8, -8], + rotate: [-3, 3, -3], + x: [-2, 2, -2], + transition: { + duration: 2.5, + repeat: Infinity, + ease: "easeInOut", + times: [0, 0.5, 1] + } + }) + }} + onMouseLeave={() => { + handControls.start({ + y: 0, + rotate: 0, + x: 0, + transition: { + duration: 0.6, + ease: "easeOut" + } + }) + }} + > + {/* Background Layer */} + + + {/* Hand Layer - Animated */} + +
diff --git a/web/src/components/landing/HowItWorksSection.tsx b/web/src/components/landing/HowItWorksSection.tsx index f33075e7..4fefa15d 100644 --- a/web/src/components/landing/HowItWorksSection.tsx +++ b/web/src/components/landing/HowItWorksSection.tsx @@ -1,5 +1,6 @@ import { motion } from 'framer-motion' import AnimatedSection from './AnimatedSection' +import { t, Language } from '../../i18n/translations' function StepCard({ number, title, description, delay }: any) { return ( @@ -24,25 +25,29 @@ function StepCard({ number, title, description, delay }: any) { ) } -export default function HowItWorksSection() { +interface HowItWorksSectionProps { + language: Language +} + +export default function HowItWorksSection({ language }: HowItWorksSectionProps) { return (

- 如何开始使用 NOFX + {t('howToStart', language)}

- 四个简单步骤,开启 AI 自动交易之旅 + {t('fourSimpleSteps', language)}

{[ - { number: 1, title: '拉取 GitHub 仓库', description: 'git clone https://github.com/tinkle-community/nofx 并切换到 dev 分支测试新功能。' }, - { number: 2, title: '配置环境', description: '前端设置交易所 API(如 Binance、Hyperliquid)、AI 模型和自定义提示词。' }, - { number: 3, title: '部署与运行', description: '一键 Docker 部署,启动 AI 代理。注意:高风险市场,仅用闲钱测试。' }, - { number: 4, title: '优化与贡献', description: '监控交易,提交 PR 改进框架。加入 Telegram 分享策略。' }, + { number: 1, title: t('step1Title', language), description: t('step1Desc', language) }, + { number: 2, title: t('step2Title', language), description: t('step2Desc', language) }, + { number: 3, title: t('step3Title', language), description: t('step3Desc', language) }, + { number: 4, title: t('step4Title', language), description: t('step4Desc', language) }, ].map((step, index) => ( ))} @@ -61,10 +66,10 @@ export default function HowItWorksSection() {
- 重要风险提示 + {t('importantRiskWarning', language)}

- dev 分支不稳定,勿用无法承受损失的资金。NOFX 非托管,无官方策略。交易有风险,投资需谨慎。 + {t('riskWarningText', language)}

diff --git a/web/src/pages/LandingPage.tsx b/web/src/pages/LandingPage.tsx index 206e2b3b..1d835917 100644 --- a/web/src/pages/LandingPage.tsx +++ b/web/src/pages/LandingPage.tsx @@ -10,43 +10,71 @@ import CommunitySection from '../components/landing/CommunitySection' import AnimatedSection from '../components/landing/AnimatedSection' import LoginModal from '../components/landing/LoginModal' import FooterSection from '../components/landing/FooterSection' +import { useAuth } from '../contexts/AuthContext' +import { useLanguage } from '../contexts/LanguageContext' +import { t } from '../i18n/translations' export function LandingPage() { const [showLoginModal, setShowLoginModal] = useState(false) + const { user, logout } = useAuth() + const { language, setLanguage } = useLanguage() + const isLoggedIn = !!user + + console.log('LandingPage - user:', user, 'isLoggedIn:', isLoggedIn); return ( -
- setShowLoginModal(true)} /> - - - - - + <> + setShowLoginModal(true)} + isLoggedIn={isLoggedIn} + isHomePage={true} + language={language} + onLanguageChange={setLanguage} + user={user} + onLogout={logout} + onPageChange={(page) => { + console.log('LandingPage onPageChange called with:', page); + if (page === 'competition') { + window.location.href = '/competition'; + } else if (page === 'traders') { + window.location.href = '/traders'; + } else if (page === 'trader') { + window.location.href = '/dashboard'; + } + }} + /> +
+ + + + + {/* CTA */}
- 准备好定义 AI 交易的未来吗? + {t('readyToDefine', language)} - 从加密市场起步,扩展到 TradFi。NOFX 是 AgentFi 的基础架构。 + {t('startWithCrypto', language)}
setShowLoginModal(true)} className='flex items-center gap-2 px-10 py-4 rounded-lg font-semibold text-lg' style={{ background: 'var(--brand-yellow)', color: 'var(--brand-black)' }} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}> - 立即开始 + {t('getStartedNow', language)} - - 查看源码 + + {t('viewSourceCode', language)}
- {showLoginModal && setShowLoginModal(false)} />} - -
+ {showLoginModal && setShowLoginModal(false)} language={language} />} + +
+ ) } From b6cc1d2644442a491df8a6e6851bd47bef4a17b3 Mon Sep 17 00:00:00 2001 From: icy Date: Sun, 2 Nov 2025 06:22:16 +0800 Subject: [PATCH 04/32] =?UTF-8?q?feat(i18n):=20internationalize=20footer?= =?UTF-8?q?=20and=20login=20modal=20components=20-=20Update=20FooterSectio?= =?UTF-8?q?n=20to=20use=20language=20prop=20pattern=20instead=20of=20useLa?= =?UTF-8?q?nguage=20hook=20-=20Add=20language=20prop=20support=20to=20Logi?= =?UTF-8?q?nModal=20component=20-=20Ensure=20consistent=20internationaliza?= =?UTF-8?q?tion=20approach=20across=20UI=20components=20-=20Maintain=20pro?= =?UTF-8?q?per=20prop=20interfaces=20for=20language=20handling=20?= =?UTF-8?q?=F0=9F=A4=96=20Generated=20with=20[Claude=20Code](https://claud?= =?UTF-8?q?e.ai/code)=20Co-Authored-By:=20tinkle-community=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/landing/FooterSection.tsx | 28 +++++++++++--------- web/src/components/landing/LoginModal.tsx | 16 +++++++---- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/web/src/components/landing/FooterSection.tsx b/web/src/components/landing/FooterSection.tsx index 6ff3faa9..598409fe 100644 --- a/web/src/components/landing/FooterSection.tsx +++ b/web/src/components/landing/FooterSection.tsx @@ -1,20 +1,22 @@ -import { useLanguage } from '../../contexts/LanguageContext' -import { t } from '../../i18n/translations' +import { t, Language } from '../../i18n/translations' -export default function FooterSection() { - const { language } = useLanguage() +interface FooterSectionProps { + language: Language +} + +export default function FooterSection({ language }: FooterSectionProps) { return ( -