mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-11 06:46:59 +08:00
refactor(web): restructure AITradersPage into modular architecture (#1023)
* refactor(web): restructure AITradersPage into modular architecture Refactored the massive 2652-line AITradersPage.tsx into a clean, modular architecture following React best practices. **Changes:** - Decomposed 2652-line component into 12 focused modules - Introduced Zustand stores for config and modal state management - Extracted all business logic into useTraderActions custom hook (633 lines) - Created reusable section components (PageHeader, TradersGrid, etc.) - Separated complex modal logic into dedicated components - Added TraderConfig type, eliminated all any types - Fixed critical bugs in configuredExchanges logic and getState() usage **File Structure:** - Main page reduced from 2652 → 234 lines (91% reduction) - components/traders/: 7 UI components + 5 section components - stores/: tradersConfigStore, tradersModalStore - hooks/: useTraderActions (all business logic) Co-Authored-By: tinkle-community <tinklefund@gmail.com> * chore: ignore PR_DESCRIPTION.md * fix(web): restore trader dashboard navigation functionality Fixed missing navigation logic in refactored AITradersPage. The "查看" (View) button now correctly navigates to the trader dashboard. **Root Cause:** During refactoring, the `useNavigate` hook and default navigation logic were inadvertently omitted from the main page component. **Changes:** - Added `useNavigate` import from react-router-dom - Implemented `handleTraderSelect` function with fallback navigation - Restored original behavior: use `onTraderSelect` prop if provided, otherwise navigate to `/dashboard?trader=${traderId}` **Testing:** - ✅ Click "查看" button navigates to trader dashboard - ✅ Query parameter correctly passed to dashboard Co-Authored-By: tinkle-community <tinklefund@gmail.com> * fix(web): correct type definitions for trader configuration Fixed TypeScript build errors by using the correct `TraderConfigData` type instead of the incorrect `TraderConfig` type. **Root Cause:** During refactoring, a new `TraderConfig` type was incorrectly created that extended `CreateTraderRequest` (with fields like `name`, `ai_model_id`). However, the `TraderConfigModal` component and API responses actually use `TraderConfigData` (with fields like `trader_name`, `ai_model`). **Changes:** - Replaced all `TraderConfig` references with `TraderConfigData`: - stores/tradersModalStore.ts - hooks/useTraderActions.ts - lib/api.ts - Removed incorrect `TraderConfig` type definition from types.ts - Added null check for `editingTrader.trader_id` to satisfy TypeScript **Build Status:** - ✅ TypeScript compilation: PASS - ✅ Vite production build: PASS Co-Authored-By: tinkle-community <tinklefund@gmail.com> --------- Co-authored-by: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
248
web/src/pages/AITradersPage.tsx
Normal file
248
web/src/pages/AITradersPage.tsx
Normal file
@@ -0,0 +1,248 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import useSWR from 'swr'
|
||||
import { api } from '../lib/api'
|
||||
import { useLanguage } from '../contexts/LanguageContext'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
import { useTradersConfigStore, useTradersModalStore } from '../stores'
|
||||
import { useTraderActions } from '../hooks/useTraderActions'
|
||||
import { TraderConfigModal } from '../components/TraderConfigModal'
|
||||
import {
|
||||
SignalSourceModal,
|
||||
ModelConfigModal,
|
||||
ExchangeConfigModal,
|
||||
} from '../components/traders'
|
||||
import { PageHeader } from '../components/traders/sections/PageHeader'
|
||||
import { SignalSourceWarning } from '../components/traders/sections/SignalSourceWarning'
|
||||
import { AIModelsSection } from '../components/traders/sections/AIModelsSection'
|
||||
import { ExchangesSection } from '../components/traders/sections/ExchangesSection'
|
||||
import { TradersGrid } from '../components/traders/sections/TradersGrid'
|
||||
|
||||
interface AITradersPageProps {
|
||||
onTraderSelect?: (traderId: string) => void
|
||||
}
|
||||
|
||||
export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
const { language } = useLanguage()
|
||||
const { user, token } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
|
||||
// Zustand stores
|
||||
const {
|
||||
allModels,
|
||||
allExchanges,
|
||||
supportedModels,
|
||||
supportedExchanges,
|
||||
configuredModels,
|
||||
configuredExchanges,
|
||||
userSignalSource,
|
||||
loadConfigs,
|
||||
setAllModels,
|
||||
setAllExchanges,
|
||||
setUserSignalSource,
|
||||
} = useTradersConfigStore()
|
||||
|
||||
const {
|
||||
showCreateModal,
|
||||
showEditModal,
|
||||
showModelModal,
|
||||
showExchangeModal,
|
||||
showSignalSourceModal,
|
||||
editingModel,
|
||||
editingExchange,
|
||||
editingTrader,
|
||||
setShowCreateModal,
|
||||
setShowEditModal,
|
||||
setShowModelModal,
|
||||
setShowExchangeModal,
|
||||
setShowSignalSourceModal,
|
||||
setEditingModel,
|
||||
setEditingExchange,
|
||||
setEditingTrader,
|
||||
} = useTradersModalStore()
|
||||
|
||||
// SWR for traders data
|
||||
const { data: traders, mutate: mutateTraders } = useSWR(
|
||||
user && token ? 'traders' : null,
|
||||
api.getTraders,
|
||||
{ refreshInterval: 5000 }
|
||||
)
|
||||
|
||||
// Load configurations
|
||||
useEffect(() => {
|
||||
loadConfigs(user, token)
|
||||
}, [user, token, loadConfigs])
|
||||
|
||||
// Business logic hook
|
||||
const {
|
||||
isModelInUse,
|
||||
isExchangeInUse,
|
||||
handleCreateTrader,
|
||||
handleEditTrader,
|
||||
handleSaveEditTrader,
|
||||
handleDeleteTrader,
|
||||
handleToggleTrader,
|
||||
handleAddModel,
|
||||
handleAddExchange,
|
||||
handleModelClick,
|
||||
handleExchangeClick,
|
||||
handleSaveModel,
|
||||
handleDeleteModel,
|
||||
handleSaveExchange,
|
||||
handleDeleteExchange,
|
||||
handleSaveSignalSource,
|
||||
} = useTraderActions({
|
||||
traders,
|
||||
allModels,
|
||||
allExchanges,
|
||||
supportedModels,
|
||||
supportedExchanges,
|
||||
language,
|
||||
mutateTraders,
|
||||
setAllModels,
|
||||
setAllExchanges,
|
||||
setUserSignalSource,
|
||||
setShowCreateModal,
|
||||
setShowEditModal,
|
||||
setShowModelModal,
|
||||
setShowExchangeModal,
|
||||
setShowSignalSourceModal,
|
||||
setEditingModel,
|
||||
setEditingExchange,
|
||||
editingTrader,
|
||||
setEditingTrader,
|
||||
})
|
||||
|
||||
// 计算派生状态
|
||||
const enabledModels = allModels?.filter((m) => m.enabled) || []
|
||||
const enabledExchanges =
|
||||
allExchanges?.filter((e) => {
|
||||
if (!e.enabled) return false
|
||||
if (e.id === 'aster') {
|
||||
return e.asterUser?.trim() && e.asterSigner?.trim()
|
||||
}
|
||||
if (e.id === 'hyperliquid') {
|
||||
return e.hyperliquidWalletAddr?.trim()
|
||||
}
|
||||
return true
|
||||
}) || []
|
||||
|
||||
// 检查是否需要显示信号源警告
|
||||
const showSignalWarning =
|
||||
traders?.some((t) => t.use_coin_pool || t.use_oi_top) &&
|
||||
!userSignalSource.coinPoolUrl &&
|
||||
!userSignalSource.oiTopUrl
|
||||
|
||||
// 处理交易员查看
|
||||
const handleTraderSelect = (traderId: string) => {
|
||||
if (onTraderSelect) {
|
||||
onTraderSelect(traderId)
|
||||
} else {
|
||||
navigate(`/dashboard?trader=${traderId}`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4 md:space-y-6 animate-fade-in">
|
||||
{/* Header */}
|
||||
<PageHeader
|
||||
language={language}
|
||||
tradersCount={traders?.length || 0}
|
||||
configuredModelsCount={configuredModels.length}
|
||||
configuredExchangesCount={configuredExchanges.length}
|
||||
onAddModel={handleAddModel}
|
||||
onAddExchange={handleAddExchange}
|
||||
onConfigureSignalSource={() => setShowSignalSourceModal(true)}
|
||||
onCreateTrader={() => setShowCreateModal(true)}
|
||||
/>
|
||||
|
||||
{/* Signal Source Warning */}
|
||||
{showSignalWarning && (
|
||||
<SignalSourceWarning
|
||||
language={language}
|
||||
onConfigure={() => setShowSignalSourceModal(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Configuration Status */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6">
|
||||
<AIModelsSection
|
||||
language={language}
|
||||
configuredModels={configuredModels}
|
||||
isModelInUse={isModelInUse}
|
||||
onModelClick={handleModelClick}
|
||||
/>
|
||||
|
||||
<ExchangesSection
|
||||
language={language}
|
||||
configuredExchanges={configuredExchanges}
|
||||
isExchangeInUse={isExchangeInUse}
|
||||
onExchangeClick={handleExchangeClick}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Traders Grid */}
|
||||
<TradersGrid
|
||||
language={language}
|
||||
traders={traders}
|
||||
onTraderSelect={handleTraderSelect}
|
||||
onEditTrader={handleEditTrader}
|
||||
onDeleteTrader={handleDeleteTrader}
|
||||
onToggleTrader={handleToggleTrader}
|
||||
/>
|
||||
|
||||
{/* Modals */}
|
||||
<TraderConfigModal
|
||||
isOpen={showCreateModal}
|
||||
onClose={() => setShowCreateModal(false)}
|
||||
isEditMode={false}
|
||||
availableModels={enabledModels}
|
||||
availableExchanges={enabledExchanges}
|
||||
onSave={handleCreateTrader}
|
||||
/>
|
||||
|
||||
<TraderConfigModal
|
||||
isOpen={showEditModal}
|
||||
onClose={() => setShowEditModal(false)}
|
||||
isEditMode={true}
|
||||
traderData={editingTrader}
|
||||
availableModels={enabledModels}
|
||||
availableExchanges={enabledExchanges}
|
||||
onSave={handleSaveEditTrader}
|
||||
/>
|
||||
|
||||
{showModelModal && (
|
||||
<ModelConfigModal
|
||||
allModels={supportedModels}
|
||||
configuredModels={allModels}
|
||||
editingModelId={editingModel}
|
||||
onSave={handleSaveModel}
|
||||
onDelete={handleDeleteModel}
|
||||
onClose={() => setShowModelModal(false)}
|
||||
language={language}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showExchangeModal && (
|
||||
<ExchangeConfigModal
|
||||
allExchanges={supportedExchanges}
|
||||
editingExchangeId={editingExchange}
|
||||
onSave={handleSaveExchange}
|
||||
onDelete={handleDeleteExchange}
|
||||
onClose={() => setShowExchangeModal(false)}
|
||||
language={language}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showSignalSourceModal && (
|
||||
<SignalSourceModal
|
||||
coinPoolUrl={userSignalSource.coinPoolUrl}
|
||||
oiTopUrl={userSignalSource.oiTopUrl}
|
||||
onSave={handleSaveSignalSource}
|
||||
onClose={() => setShowSignalSourceModal(false)}
|
||||
language={language}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user