mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
feat(beginner): protect default AI model and prevent repeated onboarding (#1444)
Co-authored-by: Dean <afei.wuhao@gmail.com>
This commit is contained in:
+2
-2
@@ -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)}
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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'))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user