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