mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-12 15:26:55 +08:00
* 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) 🤖 Generated with [Claude Code](https://claude.com/claude-code) 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: tinkle-community <tinklefund@gmail.com> --------- Co-authored-by: tinkle-community <tinklefund@gmail.com>
249 lines
6.8 KiB
TypeScript
249 lines
6.8 KiB
TypeScript
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>
|
|
)
|
|
}
|