feat: simplify Claw402 autopilot trading flow

This commit is contained in:
tinkle-community
2026-06-27 00:37:59 +08:00
parent 961e016d33
commit 24f6421a73
151 changed files with 7453 additions and 41117 deletions

View File

@@ -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}