From b2e4be91523dc606be8708ec6602fecbbb0c20ea Mon Sep 17 00:00:00 2001 From: Ember <15190419+0xEmberZz@users.noreply.github.com> Date: Wed, 5 Nov 2025 22:39:42 +0800 Subject: [PATCH] Feature/faq (#546) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(web): add FAQ page with search, sidebar, and i18n integration; update navigation and routes; include user feedback analysis docs (faq.md) * docs: add filled frontend PR template for FAQ feature (PR_FRONTEND_FAQ.md) * docs(web): add Contributing & Tasks FAQ category near top with guidance on using GitHub Projects and PR contribution standards * feat(web,api): dynamically embed GitHub Projects roadmap in FAQ via /api/roadmap and RoadmapWidget; add env vars for GitHub token/org/project * chore(docker): pass GitHub roadmap env vars into backend container * docs(web): update FAQ with fork-based PR workflow, yellow links to roadmap/task dashboard, and contribution incentives; remove dynamic roadmap embed\n\nchore(api,docker): remove /api/roadmap endpoint and related env wiring * chore: revert unintended changes (.env.example, api/server.go, docker-compose.yml); remove local-only files (PR_FRONTEND_FAQ.md, web/faq.md) from PR * feat: 添加对重置密码页面的路由支持 --- web/src/App.tsx | 21 +- web/src/components/faq/FAQContent.tsx | 459 +++++++++++++++++++++++ web/src/components/faq/FAQLayout.tsx | 181 +++++++++ web/src/components/faq/FAQSearchBar.tsx | 51 +++ web/src/components/faq/FAQSidebar.tsx | 83 ++++ web/src/components/landing/HeaderBar.tsx | 187 +++++++-- web/src/data/faqData.ts | 268 +++++++++++++ web/src/i18n/translations.ts | 342 +++++++++++++++++ web/src/pages/FAQPage.tsx | 76 ++++ 9 files changed, 1628 insertions(+), 40 deletions(-) create mode 100644 web/src/components/faq/FAQContent.tsx create mode 100644 web/src/components/faq/FAQLayout.tsx create mode 100644 web/src/components/faq/FAQSearchBar.tsx create mode 100644 web/src/components/faq/FAQSidebar.tsx create mode 100644 web/src/data/faqData.ts create mode 100644 web/src/pages/FAQPage.tsx diff --git a/web/src/App.tsx b/web/src/App.tsx index 92b2e65c..ef7fff6a 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -8,6 +8,7 @@ import { RegisterPage } from './components/RegisterPage' import { ResetPasswordPage } from './components/ResetPasswordPage' import { CompetitionPage } from './components/CompetitionPage' import { LandingPage } from './pages/LandingPage' +import { FAQPage } from './pages/FAQPage' import HeaderBar from './components/landing/HeaderBar' import AILearning from './components/AILearning' import { LanguageProvider, useLanguage } from './contexts/LanguageContext' @@ -230,11 +231,14 @@ function App() { } if (route === '/register') { if (systemConfig?.admin_mode) { - window.history.pushState({}, '', '/login'); - return ; - } + window.history.pushState({}, '', '/login') + return + } return } + if (route === '/faq') { + return + } if (route === '/reset-password') { return } @@ -271,6 +275,10 @@ function App() { window.history.pushState({}, '', '/dashboard') setRoute('/dashboard') setCurrentPage('trader') + } else if (page === 'faq') { + console.log('Navigating to faq') + window.history.pushState({}, '', '/faq') + setRoute('/faq') } console.log( @@ -290,12 +298,12 @@ function App() { // Show landing page for root route if (route === '/' || route === '') { - return ; + return } // In admin mode, require authentication for any protected routes if (systemConfig?.admin_mode && (!user || !token)) { - return ; + return } // Show main app for authenticated users on other routes (non-admin mode) @@ -332,6 +340,9 @@ function App() { window.history.pushState({}, '', '/dashboard') setRoute('/dashboard') setCurrentPage('trader') + } else if (page === 'faq') { + window.history.pushState({}, '', '/faq') + setRoute('/faq') } }} /> diff --git a/web/src/components/faq/FAQContent.tsx b/web/src/components/faq/FAQContent.tsx new file mode 100644 index 00000000..c85ade12 --- /dev/null +++ b/web/src/components/faq/FAQContent.tsx @@ -0,0 +1,459 @@ +import { useEffect, useRef } from 'react' +import { t, type Language } from '../../i18n/translations' +import type { FAQCategory } from '../../data/faqData' +// RoadmapWidget 移除动态嵌入,按需仅展示外部链接 + +interface FAQContentProps { + categories: FAQCategory[] + language: Language + onActiveItemChange: (itemId: string) => void +} + +export function FAQContent({ + categories, + language, + onActiveItemChange, +}: FAQContentProps) { + const sectionRefs = useRef>(new Map()) + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const itemId = entry.target.getAttribute('data-item-id') + if (itemId) { + onActiveItemChange(itemId) + } + } + }) + }, + { + rootMargin: '-100px 0px -80% 0px', + threshold: 0, + } + ) + + sectionRefs.current.forEach((ref) => { + if (ref) observer.observe(ref) + }) + + return () => { + sectionRefs.current.forEach((ref) => { + if (ref) observer.unobserve(ref) + }) + } + }, [onActiveItemChange]) + + const setRef = (itemId: string, element: HTMLElement | null) => { + if (element) { + sectionRefs.current.set(itemId, element) + } else { + sectionRefs.current.delete(itemId) + } + } + + return ( +
+ {categories.map((category) => ( +
+ {/* Category Header */} +
+ +

+ {t(category.titleKey, language)} +

+
+ + {/* FAQ Items */} +
+ {category.items.map((item) => ( +
setRef(item.id, el)} + className="scroll-mt-24" + > + {/* Question */} +

+ {t(item.questionKey, language)} +

+ + {/* Answer */} +
+ {item.id === 'github-projects-tasks' ? ( +
+ +
    + {language === 'zh' ? ( + <> +
  1. + 打开以上链接,按标签筛选(good first issue / help + wanted / frontend / backend)。 +
  2. +
  3. + 打开任务,阅读描述与验收标准(Acceptance + Criteria)。 +
  4. +
  5. 评论“assign me”或自助分配(若权限允许)。
  6. +
  7. Fork 仓库到你的 GitHub 账户。
  8. +
  9. + 同步你的 fork 的 dev{' '} + 分支与上游保持一致: + + git remote add upstream + https://github.com/NoFxAiOS/nofx.git + +
    + git fetch upstream +
    + git checkout dev +
    + git rebase upstream/dev +
    + git push origin dev +
  10. +
  11. + 从你的 fork 的 dev 建立特性分支: + + git checkout -b feat/your-topic + +
  12. +
  13. + 推送到你的 fork: + + git push origin feat/your-topic + +
  14. +
  15. + 打开 PR:base 选择 NoFxAiOS/nofx:dev{' '} + ← compare 选择{' '} + 你的用户名/nofx:feat/your-topic。 +
  16. +
  17. + 在 PR 中关联 Issue(示例: + Closes #123 + ),选择正确 PR 模板;必要时与{' '} + upstream/dev{' '} + 同步(rebase)后继续推送。 +
  18. + + ) : ( + <> +
  19. + Open the links above and filter by labels (good + first issue / help wanted / frontend / backend). +
  20. +
  21. + Open the task and read the Description & + Acceptance Criteria. +
  22. +
  23. + Comment "assign me" or self-assign (if permitted). +
  24. +
  25. Fork the repository to your GitHub account.
  26. +
  27. + Sync your fork's dev with upstream: + + git remote add upstream + https://github.com/NoFxAiOS/nofx.git + +
    + git fetch upstream +
    + git checkout dev +
    + git rebase upstream/dev +
    + git push origin dev +
  28. +
  29. + Create a feature branch from your fork's{' '} + dev: + + git checkout -b feat/your-topic + +
  30. +
  31. + Push to your fork: + + git push origin feat/your-topic + +
  32. +
  33. + Open a PR: base NoFxAiOS/nofx:dev ← + compare{' '} + your-username/nofx:feat/your-topic. +
  34. +
  35. + In PR, reference the Issue (e.g.,{' '} + Closes #123) and + choose the proper PR template; rebase onto{' '} + upstream/dev as needed. +
  36. + + )} +
+ +
+ {language === 'zh' ? ( +
+ 提示:{' '} + 参与贡献将享有激励制度(如 + Bounty/奖金、荣誉徽章与鸣谢、优先 + Review/合并与内测资格 等)。 可在任务中优先选择带 + + bounty 标签 + + 的事项,或完成后提交 + + Bounty Claim + + 申请。 +
+ ) : ( +
+ Note:{' '} + Contribution incentives are available (e.g., cash + bounties, badges & shout-outs, priority + review/merge, beta access). Prefer tasks with + + bounty label + + , or file a + + Bounty Claim + + after completion. +
+ )} +
+
+ ) : item.id === 'contribute-pr-guidelines' ? ( +
+
+ {language === 'zh' ? '参考文档:' : 'References:'}{' '} + + CONTRIBUTING.md + + {' | '} + + PR_TITLE_GUIDE.md + +
+
    + {language === 'zh' ? ( + <> +
  1. + Fork 仓库后,从你的 fork 的 dev{' '} + 分支创建特性分支;避免直接向上游 main{' '} + 提交。 +
  2. +
  3. + 分支命名:feat/…、fix/…、docs/…;提交信息遵循 + Conventional Commits。 +
  4. +
  5. + 提交前运行检查: + + npm --prefix web run lint && npm --prefix web + run build + +
  6. +
  7. 涉及 UI 变更请附截图或短视频。
  8. +
  9. + 选择正确的 PR + 模板(frontend/backend/docs/general)。 +
  10. +
  11. + 在 PR 中关联 Issue(示例: + Closes #123),PR + 目标选择 NoFxAiOS/nofx:dev。 +
  12. +
  13. + 保持与 upstream/dev{' '} + 同步(rebase),确保 CI 通过;尽量保持 PR + 小而聚焦。 +
  14. + + ) : ( + <> +
  15. + After forking, branch from your fork's{' '} + dev; avoid direct commits to upstream{' '} + main. +
  16. +
  17. + Branch naming: feat/…, fix/…, docs/…; commit + messages follow Conventional Commits. +
  18. +
  19. + Run checks before PR: + + npm --prefix web run lint && npm --prefix web + run build + +
  20. +
  21. + For UI changes, attach screenshots or a short + video. +
  22. +
  23. + Choose the proper PR template + (frontend/backend/docs/general). +
  24. +
  25. + Link the Issue in PR (e.g.,{' '} + Closes #123) and + target NoFxAiOS/nofx:dev. +
  26. +
  27. + Keep rebasing onto upstream/dev, + ensure CI passes; prefer small and focused PRs. +
  28. + + )} +
+ +
+ {language === 'zh' ? ( +
+ 提示:{' '} + 我们为高质量贡献提供激励(Bounty/奖金、荣誉徽章与鸣谢、优先 + Review/合并与内测资格 等)。 详情可关注带 + + bounty 标签 + + 的任务,或使用 + + Bounty Claim 模板 + + 提交申请。 +
+ ) : ( +
+ Note:{' '} + We offer contribution incentives (bounties, badges, + shout-outs, priority review/merge, beta access). + Look for tasks with + + bounty label + + , or submit a + + Bounty Claim + + when ready. +
+ )} +
+
+ ) : ( +

{t(item.answerKey, language)}

+ )} +
+ + {/* Divider */} +
+
+ ))} +
+
+ ))} +
+ ) +} diff --git a/web/src/components/faq/FAQLayout.tsx b/web/src/components/faq/FAQLayout.tsx new file mode 100644 index 00000000..b4388427 --- /dev/null +++ b/web/src/components/faq/FAQLayout.tsx @@ -0,0 +1,181 @@ +import { useState, useMemo } from 'react' +import { HelpCircle } from 'lucide-react' +import { t, type Language } from '../../i18n/translations' +import { FAQSearchBar } from './FAQSearchBar' +import { FAQSidebar } from './FAQSidebar' +import { FAQContent } from './FAQContent' +import { faqCategories } from '../../data/faqData' +import type { FAQCategory } from '../../data/faqData' + +interface FAQLayoutProps { + language: Language +} + +export function FAQLayout({ language }: FAQLayoutProps) { + const [searchTerm, setSearchTerm] = useState('') + const [activeItemId, setActiveItemId] = useState(null) + + // Filter categories based on search term + const filteredCategories = useMemo(() => { + if (!searchTerm.trim()) { + return faqCategories + } + + const term = searchTerm.toLowerCase() + const filtered: FAQCategory[] = [] + + faqCategories.forEach((category) => { + const matchingItems = category.items.filter((item) => { + const question = t(item.questionKey, language).toLowerCase() + const answer = t(item.answerKey, language).toLowerCase() + return question.includes(term) || answer.includes(term) + }) + + if (matchingItems.length > 0) { + filtered.push({ + ...category, + items: matchingItems, + }) + } + }) + + return filtered + }, [searchTerm, language]) + + const handleItemClick = (_categoryId: string, itemId: string) => { + const element = document.getElementById(itemId) + if (element) { + const offset = 100 + const elementPosition = element.getBoundingClientRect().top + const offsetPosition = elementPosition + window.pageYOffset - offset + + window.scrollTo({ + top: offsetPosition, + behavior: 'smooth', + }) + } + } + + return ( +
+ {/* Page Header */} +
+
+
+ +
+
+

+ {t('faqTitle', language)} +

+

+ {t('faqSubtitle', language)} +

+ + {/* Search Bar */} +
+ +
+
+ + {/* Main Content */} +
+ {/* Sidebar - Hidden on mobile, visible on desktop */} + + + {/* Content Area */} +
+ {filteredCategories.length > 0 ? ( + + ) : ( +
+

+ {language === 'zh' + ? '没有找到匹配的问题' + : 'No matching questions found'} +

+ +
+ )} +
+
+ + {/* Contact Section */} +
+

+ {t('faqStillHaveQuestions', language)} +

+

+ {t('faqContactUs', language)} +

+ +
+
+ ) +} diff --git a/web/src/components/faq/FAQSearchBar.tsx b/web/src/components/faq/FAQSearchBar.tsx new file mode 100644 index 00000000..e4124e7f --- /dev/null +++ b/web/src/components/faq/FAQSearchBar.tsx @@ -0,0 +1,51 @@ +import { Search, X } from 'lucide-react' + +interface FAQSearchBarProps { + searchTerm: string + onSearchChange: (value: string) => void + placeholder?: string +} + +export function FAQSearchBar({ + searchTerm, + onSearchChange, + placeholder = 'Search FAQ...', +}: FAQSearchBarProps) { + return ( +
+ + onSearchChange(e.target.value)} + placeholder={placeholder} + className="w-full pl-12 pr-12 py-3 rounded-lg text-base transition-all focus:outline-none focus:ring-2" + style={{ + background: '#1E2329', + border: '1px solid #2B3139', + color: '#EAECEF', + }} + onFocus={(e) => { + e.target.style.borderColor = '#F0B90B' + e.target.style.boxShadow = '0 0 0 3px rgba(240, 185, 11, 0.1)' + }} + onBlur={(e) => { + e.target.style.borderColor = '#2B3139' + e.target.style.boxShadow = 'none' + }} + /> + {searchTerm && ( + + )} +
+ ) +} diff --git a/web/src/components/faq/FAQSidebar.tsx b/web/src/components/faq/FAQSidebar.tsx new file mode 100644 index 00000000..a87c7a3a --- /dev/null +++ b/web/src/components/faq/FAQSidebar.tsx @@ -0,0 +1,83 @@ +import { t, type Language } from '../../i18n/translations' +import type { FAQCategory } from '../../data/faqData' + +interface FAQSidebarProps { + categories: FAQCategory[] + activeItemId: string | null + language: Language + onItemClick: (categoryId: string, itemId: string) => void +} + +export function FAQSidebar({ + categories, + activeItemId, + language, + onItemClick, +}: FAQSidebarProps) { + return ( + + ) +} diff --git a/web/src/components/landing/HeaderBar.tsx b/web/src/components/landing/HeaderBar.tsx index 995205c8..6603ca2b 100644 --- a/web/src/components/landing/HeaderBar.tsx +++ b/web/src/components/landing/HeaderBar.tsx @@ -215,45 +215,127 @@ export default function HeaderBar({ {t('dashboardNav', language)} + + ) : ( // 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' && ( - - )} + <> + { + 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)} - + {t('realtimeNav', language)} + + + { + if (currentPage !== 'faq') { + e.currentTarget.style.color = 'var(--brand-yellow)' + } + }} + onMouseLeave={(e) => { + if (currentPage !== 'faq') { + e.currentTarget.style.color = 'var(--brand-light-gray)' + } + }} + > + {/* Background for selected state */} + {currentPage === 'faq' && ( + + )} + + {t('faqNav', language)} + + )} @@ -650,6 +732,41 @@ export default function HeaderBar({ {t('dashboardNav', language)} + )} diff --git a/web/src/data/faqData.ts b/web/src/data/faqData.ts new file mode 100644 index 00000000..705bd998 --- /dev/null +++ b/web/src/data/faqData.ts @@ -0,0 +1,268 @@ +import { + BookOpen, + Settings, + TrendingUp, + Wrench, + Bot, + Database, + GitBranch, +} from 'lucide-react' +import type { LucideIcon } from 'lucide-react' + +export interface FAQItem { + id: string + questionKey: string + answerKey: string +} + +export interface FAQCategory { + id: string + titleKey: string + icon: LucideIcon + items: FAQItem[] +} + +/** + * FAQ 数据配置 + * - titleKey: 分类标题的翻译键 + * - questionKey: 问题的翻译键 + * - answerKey: 答案的翻译键 + * + * 所有文本内容都通过翻译键从 i18n/translations.ts 获取 + */ +export const faqCategories: FAQCategory[] = [ + { + id: 'basics', + titleKey: 'faqCategoryBasics', + icon: BookOpen, + items: [ + { + id: 'what-is-nofx', + questionKey: 'faqWhatIsNOFX', + answerKey: 'faqWhatIsNOFXAnswer', + }, + { + id: 'supported-exchanges', + questionKey: 'faqSupportedExchanges', + answerKey: 'faqSupportedExchangesAnswer', + }, + { + id: 'is-profitable', + questionKey: 'faqIsProfitable', + answerKey: 'faqIsProfitableAnswer', + }, + { + id: 'multiple-traders', + questionKey: 'faqMultipleTraders', + answerKey: 'faqMultipleTradersAnswer', + }, + ], + }, + { + id: 'contributing', + titleKey: 'faqCategoryContributing', + icon: GitBranch, + items: [ + { + id: 'github-projects-tasks', + questionKey: 'faqGithubProjectsTasks', + answerKey: 'faqGithubProjectsTasksAnswer', + }, + { + id: 'contribute-pr-guidelines', + questionKey: 'faqContributePR', + answerKey: 'faqContributePRAnswer', + }, + ], + }, + { + id: 'setup', + titleKey: 'faqCategorySetup', + icon: Settings, + items: [ + { + id: 'system-requirements', + questionKey: 'faqSystemRequirements', + answerKey: 'faqSystemRequirementsAnswer', + }, + { + id: 'need-coding', + questionKey: 'faqNeedCoding', + answerKey: 'faqNeedCodingAnswer', + }, + { + id: 'get-api-keys', + questionKey: 'faqGetApiKeys', + answerKey: 'faqGetApiKeysAnswer', + }, + { + id: 'use-subaccount', + questionKey: 'faqUseSubaccount', + answerKey: 'faqUseSubaccountAnswer', + }, + { + id: 'docker-deployment', + questionKey: 'faqDockerDeployment', + answerKey: 'faqDockerDeploymentAnswer', + }, + { + id: 'balance-shows-zero', + questionKey: 'faqBalanceZero', + answerKey: 'faqBalanceZeroAnswer', + }, + { + id: 'testnet-issues', + questionKey: 'faqTestnet', + answerKey: 'faqTestnetAnswer', + }, + ], + }, + { + id: 'trading', + titleKey: 'faqCategoryTrading', + icon: TrendingUp, + items: [ + { + id: 'no-trades', + questionKey: 'faqNoTrades', + answerKey: 'faqNoTradesAnswer', + }, + { + id: 'decision-frequency', + questionKey: 'faqDecisionFrequency', + answerKey: 'faqDecisionFrequencyAnswer', + }, + { + id: 'custom-strategy', + questionKey: 'faqCustomStrategy', + answerKey: 'faqCustomStrategyAnswer', + }, + { + id: 'max-positions', + questionKey: 'faqMaxPositions', + answerKey: 'faqMaxPositionsAnswer', + }, + { + id: 'margin-insufficient', + questionKey: 'faqMarginInsufficient', + answerKey: 'faqMarginInsufficientAnswer', + }, + { + id: 'high-fees', + questionKey: 'faqHighFees', + answerKey: 'faqHighFeesAnswer', + }, + { + id: 'no-take-profit', + questionKey: 'faqNoTakeProfit', + answerKey: 'faqNoTakeProfitAnswer', + }, + ], + }, + { + id: 'technical', + titleKey: 'faqCategoryTechnical', + icon: Wrench, + items: [ + { + id: 'binance-api-failed', + questionKey: 'faqBinanceApiFailed', + answerKey: 'faqBinanceApiFailedAnswer', + }, + { + id: 'binance-position-mode', + questionKey: 'faqBinancePositionMode', + answerKey: 'faqBinancePositionModeAnswer', + }, + { + id: 'port-in-use', + questionKey: 'faqPortInUse', + answerKey: 'faqPortInUseAnswer', + }, + { + id: 'frontend-loading', + questionKey: 'faqFrontendLoading', + answerKey: 'faqFrontendLoadingAnswer', + }, + { + id: 'database-locked', + questionKey: 'faqDatabaseLocked', + answerKey: 'faqDatabaseLockedAnswer', + }, + { + id: 'ai-learning-failed', + questionKey: 'faqAiLearningFailed', + answerKey: 'faqAiLearningFailedAnswer', + }, + { + id: 'config-not-effective', + questionKey: 'faqConfigNotEffective', + answerKey: 'faqConfigNotEffectiveAnswer', + }, + ], + }, + { + id: 'ai', + titleKey: 'faqCategoryAI', + icon: Bot, + items: [ + { + id: 'which-models', + questionKey: 'faqWhichModels', + answerKey: 'faqWhichModelsAnswer', + }, + { + id: 'api-costs', + questionKey: 'faqApiCosts', + answerKey: 'faqApiCostsAnswer', + }, + { + id: 'multiple-models', + questionKey: 'faqMultipleModels', + answerKey: 'faqMultipleModelsAnswer', + }, + { + id: 'ai-learning', + questionKey: 'faqAiLearning', + answerKey: 'faqAiLearningAnswer', + }, + { + id: 'only-short-positions', + questionKey: 'faqOnlyShort', + answerKey: 'faqOnlyShortAnswer', + }, + { + id: 'model-selection', + questionKey: 'faqModelSelection', + answerKey: 'faqModelSelectionAnswer', + }, + ], + }, + { + id: 'data', + titleKey: 'faqCategoryData', + icon: Database, + items: [ + { + id: 'data-storage', + questionKey: 'faqDataStorage', + answerKey: 'faqDataStorageAnswer', + }, + { + id: 'api-key-security', + questionKey: 'faqApiKeySecurity', + answerKey: 'faqApiKeySecurityAnswer', + }, + { + id: 'export-history', + questionKey: 'faqExportHistory', + answerKey: 'faqExportHistoryAnswer', + }, + { + id: 'get-help', + questionKey: 'faqGetHelp', + answerKey: 'faqGetHelpAnswer', + }, + ], + }, +] diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts index 233adff4..dd24c846 100644 --- a/web/src/i18n/translations.ts +++ b/web/src/i18n/translations.ts @@ -20,6 +20,7 @@ export const translations = { realtimeNav: 'Live', configNav: 'Config', dashboardNav: 'Dashboard', + faqNav: 'FAQ', // Footer footerTitle: 'NOFX - AI Trading System', @@ -505,6 +506,176 @@ export const translations = { signalSourceWarningMessage: 'You have traders that enabled "Use Coin Pool" or "Use OI Top", but signal source API address is not configured yet. This will cause candidate coins count to be 0, and traders cannot work properly.', configureSignalSourceNow: 'Configure Signal Source Now', + + // FAQ Page + faqTitle: 'Frequently Asked Questions', + faqSubtitle: 'Find answers to common questions about NOFX', + faqStillHaveQuestions: 'Still Have Questions?', + faqContactUs: 'Join our community or check our GitHub for more help', + + // FAQ Categories + faqCategoryBasics: 'General Questions', + faqCategoryContributing: 'Contributing & Tasks', + faqCategorySetup: 'Setup & Configuration', + faqCategoryTrading: 'Trading Questions', + faqCategoryTechnical: 'Technical Issues', + faqCategoryAI: 'AI & Model Questions', + faqCategoryData: 'Data & Privacy', + + // FAQ Questions & Answers - General + faqWhatIsNOFX: 'What is NOFX?', + faqWhatIsNOFXAnswer: + 'NOFX is an AI-powered cryptocurrency trading bot that uses large language models (LLMs) to make trading decisions on futures markets.', + + faqSupportedExchanges: 'Which exchanges are supported?', + faqSupportedExchangesAnswer: + 'Binance Futures, Hyperliquid, Aster DEX, and OKX are supported. More exchanges coming soon.', + + faqIsProfitable: 'Is NOFX profitable?', + faqIsProfitableAnswer: + 'AI trading is experimental and not guaranteed to be profitable. Always start with small amounts and never invest more than you can afford to lose.', + + faqMultipleTraders: 'Can I run multiple traders simultaneously?', + faqMultipleTradersAnswer: + 'Yes! NOFX supports running multiple traders with different configurations, AI models, and trading strategies.', + + // Contributing & Community + faqGithubProjectsTasks: 'How to use GitHub Projects and pick up tasks?', + faqGithubProjectsTasksAnswer: + 'Roadmap: https://github.com/orgs/NoFxAiOS/projects/3 • Task Dashboard: https://github.com/orgs/NoFxAiOS/projects/5 • Steps: Open links → filter by labels (good first issue / help wanted / frontend / backend) → read Description & Acceptance Criteria → comment "assign me" or self-assign → Fork the repo → sync your fork\'s dev with upstream/dev → create a feature branch from your fork\'s dev → push to your fork → open PR (base: NoFxAiOS/nofx:dev ← compare: your-username/nofx:feat/your-topic) → reference Issue (Closes #123) and use the proper template.', + + faqContributePR: 'How to properly submit PRs and contribute?', + faqContributePRAnswer: + "Guidelines: • Fork first; branch from your fork's dev (avoid direct commits to upstream main) • Branch naming: feat/..., fix/..., docs/...; Conventional Commits • Run checks before PR: npm --prefix web run lint && npm --prefix web run build • For UI changes, attach screenshots or a short video • Choose the proper PR template (frontend/backend/docs/general) • Open PR from your fork to NoFxAiOS/nofx:dev and link Issue (Closes #123) • Keep rebasing onto upstream/dev; ensure CI passes; prefer small, focused PRs • Read CONTRIBUTING.md and .github/PR_TITLE_GUIDE.md", + + // Setup & Configuration + faqSystemRequirements: 'What are the system requirements?', + faqSystemRequirementsAnswer: + 'OS: Linux, macOS, or Windows (Docker recommended); RAM: 2GB minimum, 4GB recommended; Disk: 1GB for application + logs; Network: Stable internet connection.', + + faqNeedCoding: 'Do I need coding experience?', + faqNeedCodingAnswer: + 'No! NOFX has a web UI for all configuration. However, basic command line knowledge helps with setup and troubleshooting.', + + faqGetApiKeys: 'How do I get API keys?', + faqGetApiKeysAnswer: + 'For Binance: Account → API Management → Create API → Enable Futures. For Hyperliquid: Visit Hyperliquid App → API Settings.', + + faqUseSubaccount: 'Should I use a subaccount?', + faqUseSubaccountAnswer: + 'Recommended: Yes, use a subaccount dedicated to NOFX for better risk isolation. However, note that some subaccounts have restrictions (e.g., 5x max leverage on Binance).', + + faqDockerDeployment: 'Docker deployment keeps failing', + faqDockerDeploymentAnswer: + 'Common issues: Network connection problems, dependency installation failures, insufficient memory (needs at least 2C2G). If stuck at "go build", try: docker compose down && docker compose build --no-cache && docker compose up -d', + + faqBalanceZero: 'Account balance shows 0', + faqBalanceZeroAnswer: + 'Funds are likely in spot account instead of futures account, or locked in savings products. You need to manually transfer funds to futures account in Binance.', + + faqTestnet: 'Can I use testnet for testing?', + faqTestnetAnswer: + 'Binance testnet exists but is not well maintained. Prices often stay flat and data quality is poor. We recommend using real trading with small amounts (10-50 USDT) for testing instead.', + + // Trading Questions + faqNoTrades: "Why isn't my trader making any trades?", + faqNoTradesAnswer: + 'Common reasons: AI decided to "wait" due to market conditions; Insufficient balance or margin; Position limits reached (default: max 3 positions); Check troubleshooting guide for detailed diagnostics.', + + faqDecisionFrequency: 'How often does the AI make decisions?', + faqDecisionFrequencyAnswer: + 'Configurable! Default is every 3-5 minutes. Too frequent = overtrading, too slow = missed opportunities.', + + faqCustomStrategy: 'Can I customize the trading strategy?', + faqCustomStrategyAnswer: + 'Yes! You can adjust leverage settings, modify coin selection pool, change decision intervals, and customize system prompts (advanced).', + + faqMaxPositions: "What's the maximum number of concurrent positions?", + faqMaxPositionsAnswer: + 'Default: 3 positions. This is a soft limit defined in the AI prompt, not hard-coded.', + + faqMarginInsufficient: 'Margin is insufficient error (code=-2019)', + faqMarginInsufficientAnswer: + 'Common causes: Funds not transferred to futures account; Leverage set too high (default 20-50x); Existing positions using margin; Need to transfer USDT from spot to futures account first.', + + faqHighFees: 'Trading fees are too high', + faqHighFeesAnswer: + 'NOFX default 3-minute scan interval can cause frequent trading. Solutions: Increase decision interval to 5-10 minutes; Optimize system prompt to reduce overtrading; Adjust leverage to reduce position sizes.', + + faqNoTakeProfit: "AI doesn't close profitable positions", + faqNoTakeProfitAnswer: + 'AI may believe the trend will continue. The system lacks trailing stop-loss feature currently. You can manually close positions or adjust the system prompt to be more conservative with profit-taking.', + + // Technical Issues + faqBinanceApiFailed: 'Binance API call failed (code=-2015)', + faqBinanceApiFailedAnswer: + 'Error: "Invalid API-key, IP, or permissions for action". Solutions: Add server IP to Binance API whitelist; Check API permissions (needs Read + Futures Trading); Ensure using futures API not unified account API; VPN IP might be unstable.', + + faqBinancePositionMode: 'Binance Position Mode Error (code=-4061)', + faqBinancePositionModeAnswer: + 'Error: "Order\'s position side does not match user\'s setting". Solution: Switch to Hedge Mode (双向持仓) in Binance Futures settings. You must close all positions first before switching.', + + faqPortInUse: "Backend won't start / Port already in use", + faqPortInUseAnswer: + 'Check what\'s using port 8080 with "lsof -i :8080" and change the port in your .env file with NOFX_BACKEND_PORT=8081.', + + faqFrontendLoading: 'Frontend shows "Loading..." forever', + faqFrontendLoadingAnswer: + 'Check if backend is running with "curl http://localhost:8080/api/health". Should return {"status":"ok"}. If not, check the troubleshooting guide.', + + faqDatabaseLocked: 'Database locked error', + faqDatabaseLockedAnswer: + 'Stop all NOFX processes with "docker compose down" or "pkill nofx", then restart with "docker compose up -d".', + + faqAiLearningFailed: 'AI learning data failed to load', + faqAiLearningFailedAnswer: + 'Causes: TA-Lib library not properly installed; Insufficient historical data (need completed trades); Environment configuration issues. Install TA-Lib: pip install TA-Lib or check system dependencies.', + + faqConfigNotEffective: 'Configuration changes not taking effect', + faqConfigNotEffectiveAnswer: + 'For Docker: Need to rebuild with "docker compose down && docker compose up -d --build". For PM2: Restart with "pm2 restart all". Check configuration file format and path are correct.', + + // AI & Model Questions + faqWhichModels: 'Which AI models are supported?', + faqWhichModelsAnswer: + 'DeepSeek (recommended for cost/performance), Qwen (Alibaba Cloud), and Custom OpenAI-compatible APIs (can be used for OpenAI, Claude via proxy, or other providers).', + + faqApiCosts: 'How much do API calls cost?', + faqApiCostsAnswer: + 'Depends on your model and decision frequency: DeepSeek: ~$0.10-0.50 per day (1 trader, 5min intervals); Qwen: ~$0.20-0.80 per day; Custom API (e.g., OpenAI GPT-4): ~$2-5 per day. Estimates based on typical usage.', + + faqMultipleModels: 'Can I use multiple AI models?', + faqMultipleModelsAnswer: + 'Yes! Each trader can use a different AI model. You can even A/B test different models.', + + faqAiLearning: 'Does the AI learn from its mistakes?', + faqAiLearningAnswer: + 'Yes, to some extent. NOFX provides historical performance feedback in each decision prompt, allowing the AI to adjust its strategy.', + + faqOnlyShort: 'AI only opens short positions, no long positions', + faqOnlyShortAnswer: + 'The default system prompt contains "Don\'t have a long bias! Shorting is one of your core tools" which may cause this. Also affected by 4-hour timeframe data and model training bias. You can modify the system prompt to be more balanced.', + + faqModelSelection: 'Which DeepSeek version should I use?', + faqModelSelectionAnswer: + "DeepSeek V3 is recommended for best performance. Alternatives: DeepSeek R1 (reasoning model, slower but better logic), SiliconFlow's DeepSeek (alternative API provider). Most users report good results with V3.", + + // Data & Privacy + faqDataStorage: 'Where is my data stored?', + faqDataStorageAnswer: + 'All data is stored locally on your machine in SQLite databases: config.db (trader configurations), trading.db (trade history), and decision_logs/ (AI decision records).', + + faqApiKeySecurity: 'Is my API key secure?', + faqApiKeySecurityAnswer: + 'API keys are stored in local databases. Never share your databases or .env files. We recommend using API keys with IP whitelist restrictions.', + + faqExportHistory: 'Can I export my trading history?', + faqExportHistoryAnswer: + 'Yes! Trading data is in SQLite format. You can query it directly with: sqlite3 trading.db "SELECT * FROM trades;"', + + faqGetHelp: 'Where can I get help?', + faqGetHelpAnswer: + 'Check GitHub Discussions, join our Telegram Community, or open an issue on GitHub.', }, zh: { // Header @@ -525,6 +696,7 @@ export const translations = { realtimeNav: '实时', configNav: '配置', dashboardNav: '看板', + faqNav: '常见问题', // Footer footerTitle: 'NOFX - AI交易系统', @@ -971,6 +1143,176 @@ export const translations = { signalSourceWarningMessage: '您有交易员启用了"使用币种池"或"使用OI Top",但尚未配置信号源API地址。这将导致候选币种数量为0,交易员无法正常工作。', configureSignalSourceNow: '立即配置信号源', + + // FAQ Page + faqTitle: '常见问题', + faqSubtitle: '查找关于 NOFX 的常见问题解答', + faqStillHaveQuestions: '还有其他问题?', + faqContactUs: '加入我们的社区或查看 GitHub 获取更多帮助', + + // FAQ Categories + faqCategoryBasics: '基础问题', + faqCategoryContributing: '贡献与任务', + faqCategorySetup: '安装与配置', + faqCategoryTrading: '交易问题', + faqCategoryTechnical: '技术问题', + faqCategoryAI: 'AI与模型问题', + faqCategoryData: '数据与隐私', + + // FAQ Questions & Answers - General + faqWhatIsNOFX: 'NOFX 是什么?', + faqWhatIsNOFXAnswer: + 'NOFX 是一个 AI 驱动的加密货币交易机器人,使用大语言模型(LLM)在期货市场进行交易决策。', + + faqSupportedExchanges: '支持哪些交易所?', + faqSupportedExchangesAnswer: + '支持币安合约(Binance Futures)、Hyperliquid、Aster DEX 和 OKX。更多交易所开发中。', + + faqIsProfitable: 'NOFX 能盈利吗?', + faqIsProfitableAnswer: + 'AI 交易是实验性的,不保证盈利。请始终用小额资金测试,不要投入超过您承受能力的资金。', + + faqMultipleTraders: '可以同时运行多个交易员吗?', + faqMultipleTradersAnswer: + '可以!NOFX 支持运行多个交易员,每个可配置不同的 AI 模型和交易策略。', + + // Contributing & Community + faqGithubProjectsTasks: '如何在 GitHub Projects 中领取任务?', + faqGithubProjectsTasksAnswer: + '路线图:https://github.com/orgs/NoFxAiOS/projects/3 | 任务看板:https://github.com/orgs/NoFxAiOS/projects/5 | 步骤:打开链接 → 按标签筛选(good first issue / help wanted / frontend / backend)→ 阅读描述与验收标准 → 评论“assign me”或自助分配 → Fork 仓库 → 同步你 fork 的 dev 与 upstream/dev → 从你 fork 的 dev 创建特性分支 → 推送到你的 fork → 打开 PR(base:NoFxAiOS/nofx:dev ← compare:你的用户名/nofx:feat/your-topic)→ 关联 Issue(Closes #123)并选择正确模板。', + + faqContributePR: '如何规范地提交 PR 并参与贡献?', + faqContributePRAnswer: + '规范:• 先 Fork;在你的 fork 的 dev 分支上创建特性分支(避免直接向上游 main 提交)• 分支命名:feat/...、fix/...、docs/...;提交信息遵循 Conventional Commits • PR 前运行检查:npm --prefix web run lint && npm --prefix web run build • 涉及 UI 变更请附截图/短视频 • 选择正确 PR 模板(frontend/backend/docs/general)• 从你的 fork 发起到 NoFxAiOS/nofx:dev,并在 PR 中关联 Issue(Closes #123)• 持续 rebase 到 upstream/dev,确保 CI 通过;尽量保持 PR 小而聚焦 • 参考 CONTRIBUTING.md 与 .github/PR_TITLE_GUIDE.md', + + // Setup & Configuration + faqSystemRequirements: '系统要求是什么?', + faqSystemRequirementsAnswer: + '操作系统:Linux、macOS 或 Windows(推荐 Docker);内存:最低 2GB,推荐 4GB;硬盘:应用 + 日志需要 1GB;网络:稳定的互联网连接。', + + faqNeedCoding: '需要编程经验吗?', + faqNeedCodingAnswer: + '不需要!NOFX 有 Web 界面进行所有配置。但基础的命令行知识有助于安装和故障排查。', + + faqGetApiKeys: '如何获取 API 密钥?', + faqGetApiKeysAnswer: + '币安:账户 → API 管理 → 创建 API → 启用合约。Hyperliquid:访问 Hyperliquid App → API 设置。', + + faqUseSubaccount: '应该使用子账户吗?', + faqUseSubaccountAnswer: + '推荐:是的,使用专门的子账户运行 NOFX 可以更好地隔离风险。但请注意,某些子账户有限制(例如币安子账户最高 5 倍杠杆)。', + + faqDockerDeployment: 'Docker 部署一直失败', + faqDockerDeploymentAnswer: + '常见问题:网络连接问题、依赖安装失败、内存不足(需要至少 2C2G)。如果卡在 "go build" 不动,尝试:docker compose down && docker compose build --no-cache && docker compose up -d', + + faqBalanceZero: '账户余额显示为 0', + faqBalanceZeroAnswer: + '资金可能在现货账户而非合约账户,或被理财功能锁定。您需要在币安手动将资金划转到合约账户。', + + faqTestnet: '可以使用测试网测试吗?', + faqTestnetAnswer: + '币安测试网存在但维护不佳,价格经常横盘,数据质量差。我们建议使用真实交易但小额资金(10-50 USDT)进行测试。', + + // Trading Questions + faqNoTrades: '为什么我的交易员不开仓?', + faqNoTradesAnswer: + '常见原因:AI 根据市场情况决定"等待";余额或保证金不足;达到持仓上限(默认最多 3 个仓位);查看故障排查指南了解详细诊断。', + + faqDecisionFrequency: 'AI 多久做一次决策?', + faqDecisionFrequencyAnswer: + '可配置!默认是每 3-5 分钟。太频繁 = 过度交易,太慢 = 错过机会。', + + faqCustomStrategy: '可以自定义交易策略吗?', + faqCustomStrategyAnswer: + '可以!您可以调整杠杆设置、修改币种选择池、更改决策间隔、自定义系统提示词(高级)。', + + faqMaxPositions: '最多可以同时持有多少个仓位?', + faqMaxPositionsAnswer: + '默认:3 个仓位。这是 AI 提示词中的软限制,不是硬编码。', + + faqMarginInsufficient: '保证金不足错误 (code=-2019)', + faqMarginInsufficientAnswer: + '常见原因:资金未划转到合约账户;杠杆倍数设置过高(默认 20-50 倍);已有持仓占用保证金;需要先从现货账户划转 USDT 到合约账户。', + + faqHighFees: '交易手续费太高', + faqHighFeesAnswer: + 'NOFX 默认 3 分钟扫描间隔会导致频繁交易。解决方案:将决策间隔增加到 5-10 分钟;优化系统提示词减少过度交易;调整杠杆降低仓位大小。', + + faqNoTakeProfit: 'AI 不平掉盈利的仓位', + faqNoTakeProfitAnswer: + 'AI 可能认为趋势会继续。系统目前缺少移动止盈功能。您可以手动平仓或调整系统提示词使其在获利时更保守。', + + // Technical Issues + faqBinanceApiFailed: '币安 API 调用失败 (code=-2015)', + faqBinanceApiFailedAnswer: + '错误:"Invalid API-key, IP, or permissions for action"。解决方案:将服务器 IP 添加到币安 API 白名单;检查 API 权限(需要读取 + 合约交易);确保使用合约 API 而非统一账户 API;VPN IP 可能不稳定。', + + faqBinancePositionMode: '币安持仓模式错误 (code=-4061)', + faqBinancePositionModeAnswer: + '错误信息:"Order\'s position side does not match user\'s setting"。解决方法:切换为双向持仓模式。登录币安合约 → 点击右上角偏好设置 → 选择持仓模式 → 双向持仓。注意:先平掉所有持仓。', + + faqPortInUse: '后端无法启动 / 端口被占用', + faqPortInUseAnswer: + '使用 "lsof -i :8080" 查看占用端口的进程,在 .env 中修改端口:NOFX_BACKEND_PORT=8081。', + + faqFrontendLoading: '前端一直显示"加载中..."', + faqFrontendLoadingAnswer: + '使用 "curl http://localhost:8080/api/health" 检查后端是否运行。应该返回 {"status":"ok"}。如果不是,查看故障排查指南。', + + faqDatabaseLocked: '数据库锁定错误', + faqDatabaseLockedAnswer: + '使用 "docker compose down" 或 "pkill nofx" 停止所有 NOFX 进程,然后使用 "docker compose up -d" 重启。', + + faqAiLearningFailed: 'AI 学习数据加载失败', + faqAiLearningFailedAnswer: + '原因:TA-Lib 库未正确安装;历史数据不足(需要完成交易);环境配置问题。安装 TA-Lib:pip install TA-Lib 或检查系统依赖。', + + faqConfigNotEffective: '配置文件修改不生效', + faqConfigNotEffectiveAnswer: + 'Docker 需要重新构建:"docker compose down && docker compose up -d --build"。PM2 需要重启:"pm2 restart all"。检查配置文件格式和路径是否正确。', + + // AI & Model Questions + faqWhichModels: '支持哪些 AI 模型?', + faqWhichModelsAnswer: + 'DeepSeek(推荐性价比)、Qwen(阿里云通义千问)、自定义 OpenAI 兼容 API(可用于 OpenAI、通过代理的 Claude 或其他提供商)。', + + faqApiCosts: 'API 调用成本是多少?', + faqApiCostsAnswer: + '取决于您的模型和决策频率:DeepSeek:每天约 $0.10-0.50(1 个交易员,5 分钟间隔);Qwen:每天约 $0.20-0.80;自定义 API(例如 OpenAI GPT-4):每天约 $2-5。基于典型使用的估算。', + + faqMultipleModels: '可以使用多个 AI 模型吗?', + faqMultipleModelsAnswer: + '可以!每个交易员可以使用不同的 AI 模型。您甚至可以 A/B 测试不同模型。', + + faqAiLearning: 'AI 会从错误中学习吗?', + faqAiLearningAnswer: + '会的,在一定程度上。NOFX 在每次决策提示中提供历史表现反馈,允许 AI 调整策略。', + + faqOnlyShort: 'AI 只开空单,不开多单', + faqOnlyShortAnswer: + '默认系统提示词包含"不要有做多偏见!做空是你的核心工具之一",可能导致此问题。还受 4 小时周期数据和模型训练偏向性影响。您可以修改系统提示词使其更平衡。', + + faqModelSelection: '应该使用哪个 DeepSeek 版本?', + faqModelSelectionAnswer: + '推荐使用 DeepSeek V3 以获得最佳性能。备选:DeepSeek R1(推理模型,较慢但逻辑更好)、SiliconFlow 的 DeepSeek(备用 API 提供商)。大多数用户反馈 V3 效果良好。', + + // Data & Privacy + faqDataStorage: '我的数据存储在哪里?', + faqDataStorageAnswer: + '所有数据都本地存储在您的机器上,使用 SQLite 数据库:config.db(交易员配置)、trading.db(交易历史)、decision_logs/(AI 决策记录)。', + + faqApiKeySecurity: 'API 密钥安全吗?', + faqApiKeySecurityAnswer: + 'API 密钥存储在本地数据库中。永远不要分享您的数据库或 .env 文件。我们建议使用带 IP 白名单限制的 API 密钥。', + + faqExportHistory: '可以导出交易历史吗?', + faqExportHistoryAnswer: + '可以!交易数据是 SQLite 格式。您可以直接查询:sqlite3 trading.db "SELECT * FROM trades;"', + + faqGetHelp: '在哪里可以获得帮助?', + faqGetHelpAnswer: + '查看 GitHub Discussions、加入 Telegram 社区或在 GitHub 上提出 issue。', }, } diff --git a/web/src/pages/FAQPage.tsx b/web/src/pages/FAQPage.tsx new file mode 100644 index 00000000..d306ebeb --- /dev/null +++ b/web/src/pages/FAQPage.tsx @@ -0,0 +1,76 @@ +import HeaderBar from '../components/landing/HeaderBar' +import { FAQLayout } from '../components/faq/FAQLayout' +import { useLanguage } from '../contexts/LanguageContext' +import { useAuth } from '../contexts/AuthContext' +import { useSystemConfig } from '../hooks/useSystemConfig' +import { t } from '../i18n/translations' + +/** + * FAQ 页面 + * + * 这个页面只是组件的集合,负责: + * - 组装 HeaderBar 和 FAQLayout + * - 提供全局状态(语言、用户、系统配置) + * - 处理页面级别的导航 + * + * 所有 FAQ 相关的逻辑都在子组件中: + * - FAQLayout: 整体布局和搜索逻辑 + * - FAQSearchBar: 搜索框 + * - FAQSidebar: 左侧目录 + * - FAQContent: 右侧内容区 + * + * FAQ 数据配置在 data/faqData.ts + */ +export function FAQPage() { + const { language, setLanguage } = useLanguage() + const { user, logout } = useAuth() + const { config: systemConfig } = useSystemConfig() + + return ( +
+ { + if (page === 'competition') { + window.history.pushState({}, '', '/competition') + window.location.href = '/competition' + } else if (page === 'traders') { + window.history.pushState({}, '', '/traders') + window.location.href = '/traders' + } else if (page === 'trader') { + window.history.pushState({}, '', '/dashboard') + window.location.href = '/dashboard' + } else if (page === 'faq') { + window.history.pushState({}, '', '/faq') + window.location.href = '/faq' + } + }} + /> + + + + {/* Footer */} + +
+ ) +}