feat(beginner): protect default AI model and prevent repeated onboarding (#1444)

Co-authored-by: Dean <afei.wuhao@gmail.com>
This commit is contained in:
deanokk
2026-03-30 21:04:43 +08:00
committed by GitHub
parent fb0bd13f51
commit 1d6e99c74a
5 changed files with 61 additions and 20 deletions
+2 -2
View File
@@ -23,7 +23,7 @@ import { AuthProvider, useAuth } from './contexts/AuthContext'
import { ConfirmDialogProvider } from './components/common/ConfirmDialog'
import { t } from './i18n/translations'
import { useSystemConfig } from './hooks/useSystemConfig'
import { getUserMode } from './lib/onboarding'
import { getUserMode, hasCompletedBeginnerOnboarding } from './lib/onboarding'
import { OFFICIAL_LINKS } from './constants/branding'
import type {
@@ -352,7 +352,7 @@ function App() {
}, [route])
const showBeginnerOnboarding =
route === '/welcome' && (!!user || hasPersistedAuth) && getUserMode() === 'beginner'
route === '/welcome' && (!!user || hasPersistedAuth) && getUserMode() === 'beginner' && !hasCompletedBeginnerOnboarding()
// Show loading spinner while checking auth or config
if (isLoading || configLoading) {
@@ -49,8 +49,12 @@ export function BeginnerGuideCards({
: 'Pay per call with Base USDC',
ready: claw402Ready,
actionLabel: claw402Ready
? isZh ? '已配置' : 'Configured'
: isZh ? '一键配置' : 'One-click setup',
? isZh
? '配置'
: 'Configured'
: isZh
? '一键配置'
: 'One-click setup',
onAction: onQuickSetupClaw402,
disabled: claw402Ready,
},
@@ -62,12 +66,20 @@ export function BeginnerGuideCards({
? '交易所接好以后,AI 才能真正下单。'
: 'Connect an exchange so the AI can actually place trades.',
meta: exchangeReady
? isZh ? '已准备好' : 'Ready'
: isZh ? 'Binance / OKX / Bybit / Hyperliquid' : 'Binance / OKX / Bybit / Hyperliquid',
? isZh
? '已准备好'
: 'Ready'
: isZh
? 'Binance / OKX / Bybit / Hyperliquid'
: 'Binance / OKX / Bybit / Hyperliquid',
ready: exchangeReady,
actionLabel: exchangeReady
? isZh ? '继续管理' : 'Manage'
: isZh ? '去配置' : 'Configure',
? isZh
? '继续管理'
: 'Manage'
: isZh
? '去配置'
: 'Configure',
onAction: onOpenExchange,
disabled: false,
},
@@ -79,8 +91,12 @@ export function BeginnerGuideCards({
? '先用默认策略也可以,后面再慢慢细调。'
: 'You can start with a default strategy and fine-tune later.',
meta: strategyReady
? isZh ? '已有策略可用' : 'Strategy ready'
: isZh ? '可选,但建议提前看一眼' : 'Optional, but worth a quick look',
? isZh
? '已有策略可用'
: 'Strategy ready'
: isZh
? '可选,但建议提前看一眼'
: 'Optional, but worth a quick look',
ready: strategyReady,
actionLabel: isZh ? '打开策略页' : 'Open strategy',
onAction: onOpenStrategy,
@@ -94,8 +110,12 @@ export function BeginnerGuideCards({
? '最后一步,把模型和交易所绑在一起,就能开始运行。'
: 'Last step: bind your model and exchange, then start running.',
meta: canCreateTrader
? isZh ? '已经可以创建' : 'Ready to create'
: isZh ? '先完成前两步' : 'Finish the first two steps first',
? isZh
? '已经可以创建'
: 'Ready to create'
: isZh
? '先完成前两步'
: 'Finish the first two steps first',
ready: canCreateTrader,
actionLabel: isZh ? '立即创建' : 'Create now',
onAction: onCreateTrader,
@@ -111,12 +131,14 @@ export function BeginnerGuideCards({
{isZh ? '新手引导' : 'Quickstart'}
</div>
<h2 className="mt-1 text-xl font-bold text-white">
{isZh ? '先按这 4 步走,最快上手' : 'Follow these 4 steps to get started fast'}
{isZh
? '先按这 4 步走,最快上手'
: 'Follow these 4 steps to get started fast'}
</h2>
</div>
<div className="rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs text-zinc-400">
{/* <div className="rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs text-zinc-400">
{isZh ? '老手模式不会看到这块' : 'Hidden in advanced mode'}
</div>
</div> */}
</div>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
@@ -138,11 +160,19 @@ export function BeginnerGuideCards({
: 'bg-zinc-800 text-zinc-400'
}`}
>
{card.ready ? (isZh ? '已就绪' : 'Ready') : (isZh ? '待完成' : 'Pending')}
{card.ready
? isZh
? '已就绪'
: 'Ready'
: isZh
? '待完成'
: 'Pending'}
</span>
</div>
<h3 className="mt-4 text-base font-semibold text-white">{card.title}</h3>
<h3 className="mt-4 text-base font-semibold text-white">
{card.title}
</h3>
<p className="mt-2 min-h-[72px] text-sm leading-6 text-zinc-400">
{card.desc}
</p>
@@ -13,7 +13,7 @@ import {
AI_PROVIDER_CONFIG,
getShortName,
} from './model-constants'
import { getBeginnerWalletAddress } from '../../lib/onboarding'
import { getBeginnerWalletAddress, getUserMode } from '../../lib/onboarding'
interface ModelConfigModalProps {
allModels: AIModel[]
@@ -84,6 +84,7 @@ export function ModelConfigModal({
const availableModels = allModels || []
const configuredIds = new Set(configuredModels?.map(m => m.id) || [])
const isClaw402Selected = selectedModel?.provider === 'claw402' || selectedModel?.id === 'claw402'
const isBeginnerDefaultModel = isClaw402Selected && getUserMode() === 'beginner'
const stepLabels = [
t('modelConfig.selectModel', language),
t(
@@ -117,7 +118,7 @@ export function ModelConfigModal({
</h3>
</div>
<div className="flex items-center gap-2">
{editingModelId && (
{editingModelId && !isBeginnerDefaultModel && (
<button
type="button"
onClick={() => onDelete(editingModelId)}
+9
View File
@@ -2,6 +2,7 @@ export type UserMode = 'beginner' | 'advanced'
const USER_MODE_KEY = 'nofx_user_mode'
const BEGINNER_WALLET_ADDRESS_KEY = 'nofx_beginner_wallet_address'
const BEGINNER_ONBOARDING_COMPLETED_KEY = 'nofx_beginner_onboarding_completed'
export function getUserMode(): UserMode | null {
const value = localStorage.getItem(USER_MODE_KEY)
@@ -26,3 +27,11 @@ export function setBeginnerWalletAddress(address: string) {
export function getBeginnerWalletAddress(): string | null {
return localStorage.getItem(BEGINNER_WALLET_ADDRESS_KEY)
}
export function hasCompletedBeginnerOnboarding(): boolean {
return localStorage.getItem(BEGINNER_ONBOARDING_COMPLETED_KEY) === 'true'
}
export function markBeginnerOnboardingCompleted() {
localStorage.setItem(BEGINNER_ONBOARDING_COMPLETED_KEY, 'true')
}
+2 -1
View File
@@ -12,7 +12,7 @@ import { toast } from 'sonner'
import { useLanguage } from '../contexts/LanguageContext'
import { api } from '../lib/api'
import type { BeginnerOnboardingResponse } from '../types'
import { setBeginnerWalletAddress } from '../lib/onboarding'
import { setBeginnerWalletAddress, markBeginnerOnboardingCompleted } from '../lib/onboarding'
export function BeginnerOnboardingPage() {
const { language } = useLanguage()
@@ -78,6 +78,7 @@ export function BeginnerOnboardingPage() {
}
const handleContinue = () => {
markBeginnerOnboardingCompleted()
window.history.pushState({}, '', '/traders')
window.dispatchEvent(new PopStateEvent('popstate'))
}