mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-18 18:04:32 +08:00
feat: simplify Claw402 autopilot trading flow
This commit is contained in:
@@ -19,11 +19,8 @@ import { TelegramConfigModal } from './TelegramConfigModal'
|
||||
import { ModelConfigModal } from './ModelConfigModal'
|
||||
import { ConfigStatusGrid } from './ConfigStatusGrid'
|
||||
import { TradersList } from './TradersList'
|
||||
import {
|
||||
Bot,
|
||||
Plus,
|
||||
MessageCircle,
|
||||
} from 'lucide-react'
|
||||
import { AutopilotLaunchPanel } from './AutopilotLaunchPanel'
|
||||
import { Bot, Plus, MessageCircle } from 'lucide-react'
|
||||
import { confirmToast } from '../../lib/notify'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
@@ -45,11 +42,18 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
const [editingTrader, setEditingTrader] = useState<any>(null)
|
||||
const [allModels, setAllModels] = useState<AIModel[]>([])
|
||||
const [allExchanges, setAllExchanges] = useState<Exchange[]>([])
|
||||
const [exchangeAccountStates, setExchangeAccountStates] = useState<Record<string, ExchangeAccountState>>({})
|
||||
const [isExchangeAccountStatesLoading, setIsExchangeAccountStatesLoading] = useState(false)
|
||||
const [exchangeAccountStates, setExchangeAccountStates] = useState<
|
||||
Record<string, ExchangeAccountState>
|
||||
>({})
|
||||
const [isExchangeAccountStatesLoading, setIsExchangeAccountStatesLoading] =
|
||||
useState(false)
|
||||
const [supportedModels, setSupportedModels] = useState<AIModel[]>([])
|
||||
const [visibleTraderAddresses, setVisibleTraderAddresses] = useState<Set<string>>(new Set())
|
||||
const [visibleExchangeAddresses, setVisibleExchangeAddresses] = useState<Set<string>>(new Set())
|
||||
const [visibleTraderAddresses, setVisibleTraderAddresses] = useState<
|
||||
Set<string>
|
||||
>(new Set())
|
||||
const [visibleExchangeAddresses, setVisibleExchangeAddresses] = useState<
|
||||
Set<string>
|
||||
>(new Set())
|
||||
const [copiedId, setCopiedId] = useState<string | null>(null)
|
||||
|
||||
const loadConfigs = async () => {
|
||||
@@ -61,17 +65,13 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
|
||||
setIsExchangeAccountStatesLoading(true)
|
||||
try {
|
||||
const [
|
||||
modelConfigs,
|
||||
exchangeConfigs,
|
||||
models,
|
||||
accountStateResponse,
|
||||
] = await Promise.all([
|
||||
api.getModelConfigs(),
|
||||
api.getExchangeConfigs(),
|
||||
api.getSupportedModels(),
|
||||
api.getExchangeAccountState().catch(() => ({ states: {} })),
|
||||
])
|
||||
const [modelConfigs, exchangeConfigs, models, accountStateResponse] =
|
||||
await Promise.all([
|
||||
api.getModelConfigs(),
|
||||
api.getExchangeConfigs(),
|
||||
api.getSupportedModels(),
|
||||
api.getExchangeAccountState().catch(() => ({ states: {} })),
|
||||
])
|
||||
setAllModels(modelConfigs)
|
||||
setAllExchanges(exchangeConfigs)
|
||||
setExchangeAccountStates(accountStateResponse.states || {})
|
||||
@@ -83,7 +83,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
|
||||
// Toggle wallet address visibility for a trader
|
||||
const toggleTraderAddressVisibility = (traderId: string) => {
|
||||
setVisibleTraderAddresses(prev => {
|
||||
setVisibleTraderAddresses((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(traderId)) {
|
||||
next.delete(traderId)
|
||||
@@ -96,7 +96,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
|
||||
// Toggle wallet address visibility for an exchange
|
||||
const toggleExchangeAddressVisibility = (exchangeId: string) => {
|
||||
setVisibleExchangeAddresses(prev => {
|
||||
setVisibleExchangeAddresses((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(exchangeId)) {
|
||||
next.delete(exchangeId)
|
||||
@@ -107,7 +107,6 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Copy wallet address to clipboard
|
||||
const handleCopyAddress = async (id: string, address: string) => {
|
||||
try {
|
||||
@@ -119,27 +118,18 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const { data: traders, mutate: mutateTraders, isLoading: isTradersLoading } = useSWR<TraderInfo[]>(
|
||||
user && token ? 'traders' : null,
|
||||
api.getTraders,
|
||||
{ refreshInterval: 5000 }
|
||||
)
|
||||
const {
|
||||
data: traders,
|
||||
mutate: mutateTraders,
|
||||
isLoading: isTradersLoading,
|
||||
} = useSWR<TraderInfo[]>(user && token ? 'traders' : null, api.getTraders, {
|
||||
refreshInterval: 5000,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
loadConfigs()
|
||||
.catch((error) => {
|
||||
console.error('Failed to load configs:', error)
|
||||
})
|
||||
}, [user, token])
|
||||
|
||||
useEffect(() => {
|
||||
const handleRefresh = () => {
|
||||
loadConfigs().catch((error) => {
|
||||
console.error('Failed to refresh configs:', error)
|
||||
})
|
||||
}
|
||||
window.addEventListener('agent-config-refresh', handleRefresh)
|
||||
return () => window.removeEventListener('agent-config-refresh', handleRefresh)
|
||||
loadConfigs().catch((error) => {
|
||||
console.error('Failed to load configs:', error)
|
||||
})
|
||||
}, [user, token])
|
||||
|
||||
const configuredModels =
|
||||
@@ -192,7 +182,8 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
}
|
||||
|
||||
const getExchangeUsageInfo = (exchangeId: string) => {
|
||||
const usingTraders = traders?.filter((tr) => tr.exchange_id === exchangeId) || []
|
||||
const usingTraders =
|
||||
traders?.filter((tr) => tr.exchange_id === exchangeId) || []
|
||||
const runningCount = usingTraders.filter((tr) => tr.is_running).length
|
||||
const totalCount = usingTraders.length
|
||||
return { runningCount, totalCount, usingTraders }
|
||||
@@ -309,10 +300,10 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
try {
|
||||
if (running) {
|
||||
await api.stopTrader(traderId)
|
||||
toast.success(t('aiTradersToast.stopped', language))
|
||||
toast.success(t('aiTradersToast.stopped', language))
|
||||
} else {
|
||||
await api.startTrader(traderId)
|
||||
toast.success(t('aiTradersToast.started', language))
|
||||
toast.success(t('aiTradersToast.started', language))
|
||||
}
|
||||
|
||||
await mutateTraders()
|
||||
@@ -322,11 +313,18 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggleCompetition = async (traderId: string, currentShowInCompetition: boolean) => {
|
||||
const handleToggleCompetition = async (
|
||||
traderId: string,
|
||||
currentShowInCompetition: boolean
|
||||
) => {
|
||||
try {
|
||||
const newValue = !currentShowInCompetition
|
||||
await api.toggleCompetition(traderId, newValue)
|
||||
toast.success(newValue ? t('aiTradersToast.showInCompetition', language) : t('aiTradersToast.hideInCompetition', language))
|
||||
toast.success(
|
||||
newValue
|
||||
? t('aiTradersToast.showInCompetition', language)
|
||||
: t('aiTradersToast.hideInCompetition', language)
|
||||
)
|
||||
|
||||
await mutateTraders()
|
||||
} catch (error) {
|
||||
@@ -463,12 +461,12 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
allModels?.map((m) =>
|
||||
m.id === modelId
|
||||
? {
|
||||
...m,
|
||||
apiKey,
|
||||
customApiUrl: customApiUrl || '',
|
||||
customModelName: customModelName || '',
|
||||
enabled: true,
|
||||
}
|
||||
...m,
|
||||
apiKey,
|
||||
customApiUrl: customApiUrl || '',
|
||||
customModelName: customModelName || '',
|
||||
enabled: true,
|
||||
}
|
||||
: m
|
||||
) || []
|
||||
} else {
|
||||
@@ -571,6 +569,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
passphrase: passphrase || '',
|
||||
testnet: testnet || false,
|
||||
hyperliquid_wallet_addr: hyperliquidWalletAddr || '',
|
||||
hyperliquid_unified_account: exchangeType === 'hyperliquid',
|
||||
aster_user: asterUser || '',
|
||||
aster_signer: asterSigner || '',
|
||||
aster_private_key: asterPrivateKey || '',
|
||||
@@ -583,7 +582,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
}
|
||||
|
||||
await api.updateExchangeConfigsEncrypted(request)
|
||||
toast.success(t('aiTradersToast.exchangeConfigUpdated', language))
|
||||
toast.success(t('aiTradersToast.exchangeConfigUpdated', language))
|
||||
} else {
|
||||
const createRequest = {
|
||||
exchange_type: exchangeType,
|
||||
@@ -594,6 +593,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
passphrase: passphrase || '',
|
||||
testnet: testnet || false,
|
||||
hyperliquid_wallet_addr: hyperliquidWalletAddr || '',
|
||||
hyperliquid_unified_account: exchangeType === 'hyperliquid',
|
||||
aster_user: asterUser || '',
|
||||
aster_signer: asterSigner || '',
|
||||
aster_private_key: asterPrivateKey || '',
|
||||
@@ -604,7 +604,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
}
|
||||
|
||||
await api.createExchangeEncrypted(createRequest)
|
||||
toast.success(t('aiTradersToast.exchangeCreated', language))
|
||||
toast.success(t('aiTradersToast.exchangeCreated', language))
|
||||
}
|
||||
|
||||
const refreshedExchanges = await api.getExchangeConfigs()
|
||||
@@ -628,6 +628,10 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
setShowExchangeModal(true)
|
||||
}
|
||||
|
||||
const refreshLaunchState = async () => {
|
||||
await Promise.all([loadConfigs(), mutateTraders()])
|
||||
}
|
||||
|
||||
return (
|
||||
<DeepVoidBackground className="py-8" disableAnimation>
|
||||
<div className="w-full px-4 md:px-8 space-y-8 animate-fade-in">
|
||||
@@ -687,7 +691,10 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
|
||||
<button
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
disabled={configuredModels.length === 0 || configuredExchanges.length === 0}
|
||||
disabled={
|
||||
configuredModels.length === 0 ||
|
||||
configuredExchanges.length === 0
|
||||
}
|
||||
className="group relative px-6 py-2 rounded text-xs font-bold font-mono uppercase tracking-wider transition-all disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap overflow-hidden bg-nofx-gold text-black hover:bg-yellow-400 shadow-[0_0_20px_rgba(240,185,11,0.2)] hover:shadow-[0_0_30px_rgba(240,185,11,0.4)]"
|
||||
>
|
||||
<span className="relative z-10 flex items-center gap-2">
|
||||
@@ -718,6 +725,16 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
onCopyAddress={handleCopyAddress}
|
||||
/>
|
||||
|
||||
<AutopilotLaunchPanel
|
||||
models={allModels}
|
||||
exchanges={allExchanges}
|
||||
exchangeAccountStates={exchangeAccountStates}
|
||||
traders={traders || []}
|
||||
isLoggedIn={Boolean(user && token)}
|
||||
language={language}
|
||||
onRefresh={refreshLaunchState}
|
||||
/>
|
||||
|
||||
{/* Traders List */}
|
||||
<TradersList
|
||||
traders={traders}
|
||||
|
||||
Reference in New Issue
Block a user