mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-06 20:41:14 +08:00
fix: improve trading and UI
This commit is contained in:
@@ -25,6 +25,8 @@ import {
|
||||
Plus,
|
||||
Users,
|
||||
Pencil,
|
||||
Eye,
|
||||
EyeOff,
|
||||
} from 'lucide-react'
|
||||
import { confirmToast } from '../lib/notify'
|
||||
import { toast } from 'sonner'
|
||||
@@ -337,6 +339,23 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggleCompetition = async (traderId: string, currentShowInCompetition: boolean) => {
|
||||
try {
|
||||
const newValue = !currentShowInCompetition
|
||||
await toast.promise(api.toggleCompetition(traderId, newValue), {
|
||||
loading: '正在更新…',
|
||||
success: newValue ? '已在竞技场显示' : '已在竞技场隐藏',
|
||||
error: '更新失败',
|
||||
})
|
||||
|
||||
// Immediately refresh traders list to update status
|
||||
await mutateTraders()
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle competition visibility:', error)
|
||||
toast.error(t('operationFailed', language))
|
||||
}
|
||||
}
|
||||
|
||||
const handleModelClick = (modelId: string) => {
|
||||
if (!isModelInUse(modelId)) {
|
||||
setEditingModel(modelId)
|
||||
@@ -1069,6 +1088,29 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
: t('start', language)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleToggleCompetition(trader.trader_id, trader.show_in_competition ?? true)}
|
||||
className="px-2 md:px-3 py-1.5 md:py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 whitespace-nowrap flex items-center gap-1"
|
||||
style={
|
||||
trader.show_in_competition !== false
|
||||
? {
|
||||
background: 'rgba(14, 203, 129, 0.1)',
|
||||
color: '#0ECB81',
|
||||
}
|
||||
: {
|
||||
background: 'rgba(132, 142, 156, 0.1)',
|
||||
color: '#848E9C',
|
||||
}
|
||||
}
|
||||
title={trader.show_in_competition !== false ? '在竞技场显示' : '在竞技场隐藏'}
|
||||
>
|
||||
{trader.show_in_competition !== false ? (
|
||||
<Eye className="w-3 h-3 md:w-4 md:h-4" />
|
||||
) : (
|
||||
<EyeOff className="w-3 h-3 md:w-4 md:h-4" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleDeleteTrader(trader.trader_id)}
|
||||
className="px-2 md:px-3 py-1.5 md:py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105"
|
||||
|
||||
@@ -32,6 +32,7 @@ interface FormState {
|
||||
exchange_id: string
|
||||
strategy_id: string
|
||||
is_cross_margin: boolean
|
||||
show_in_competition: boolean
|
||||
scan_interval_minutes: number
|
||||
initial_balance?: number
|
||||
}
|
||||
@@ -62,6 +63,7 @@ export function TraderConfigModal({
|
||||
exchange_id: '',
|
||||
strategy_id: '',
|
||||
is_cross_margin: true,
|
||||
show_in_competition: true,
|
||||
scan_interval_minutes: 3,
|
||||
})
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
@@ -109,6 +111,7 @@ export function TraderConfigModal({
|
||||
exchange_id: availableExchanges[0]?.id || '',
|
||||
strategy_id: '',
|
||||
is_cross_margin: true,
|
||||
show_in_competition: true,
|
||||
scan_interval_minutes: 3,
|
||||
})
|
||||
}
|
||||
@@ -162,6 +165,7 @@ export function TraderConfigModal({
|
||||
exchange_id: formData.exchange_id,
|
||||
strategy_id: formData.strategy_id || undefined,
|
||||
is_cross_margin: formData.is_cross_margin,
|
||||
show_in_competition: formData.show_in_competition,
|
||||
scan_interval_minutes: formData.scan_interval_minutes,
|
||||
}
|
||||
|
||||
@@ -439,6 +443,40 @@ export function TraderConfigModal({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Competition visibility */}
|
||||
<div>
|
||||
<label className="text-sm text-[#EAECEF] block mb-2">
|
||||
竞技场显示
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleInputChange('show_in_competition', true)}
|
||||
className={`flex-1 px-3 py-2 rounded text-sm ${
|
||||
formData.show_in_competition
|
||||
? 'bg-[#F0B90B] text-black'
|
||||
: 'bg-[#0B0E11] text-[#848E9C] border border-[#2B3139]'
|
||||
}`}
|
||||
>
|
||||
显示
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleInputChange('show_in_competition', false)}
|
||||
className={`flex-1 px-3 py-2 rounded text-sm ${
|
||||
!formData.show_in_competition
|
||||
? 'bg-[#F0B90B] text-black'
|
||||
: 'bg-[#0B0E11] text-[#848E9C] border border-[#2B3139]'
|
||||
}`}
|
||||
>
|
||||
隐藏
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-[#848E9C] mt-1">
|
||||
隐藏后将不在竞技场页面显示此交易员
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Initial Balance (Edit mode only) */}
|
||||
{isEditMode && (
|
||||
<div>
|
||||
|
||||
@@ -11,20 +11,20 @@ export default function HowItWorksSection({ language }: HowItWorksSectionProps)
|
||||
{
|
||||
icon: Download,
|
||||
number: '01',
|
||||
title: language === 'zh' ? '克隆项目' : 'Clone Project',
|
||||
title: language === 'zh' ? '一键部署' : 'One-Click Deploy',
|
||||
desc: language === 'zh'
|
||||
? 'git clone 项目到本地'
|
||||
: 'git clone the project locally',
|
||||
code: 'git clone https://github.com/NoFxAiOS/nofx.git',
|
||||
? '在你的服务器上运行一条命令即可完成部署'
|
||||
: 'Run a single command on your server to deploy',
|
||||
code: 'curl -fsSL https://raw.githubusercontent.com/NoFxAiOS/nofx/main/install.sh | bash',
|
||||
},
|
||||
{
|
||||
icon: Rocket,
|
||||
number: '02',
|
||||
title: language === 'zh' ? '启动服务' : 'Start Service',
|
||||
title: language === 'zh' ? '访问面板' : 'Access Dashboard',
|
||||
desc: language === 'zh'
|
||||
? 'Docker 一键启动所有服务'
|
||||
: 'Docker one-click start all services',
|
||||
code: './start.sh start --build',
|
||||
? '通过浏览器访问你的服务器'
|
||||
: 'Access your server via browser',
|
||||
code: 'http://YOUR_SERVER_IP:3000',
|
||||
},
|
||||
{
|
||||
icon: TrendingUp,
|
||||
@@ -33,7 +33,7 @@ export default function HowItWorksSection({ language }: HowItWorksSectionProps)
|
||||
desc: language === 'zh'
|
||||
? '创建交易员,让 AI 开始工作'
|
||||
: 'Create trader, let AI do the work',
|
||||
code: 'http://localhost:3000',
|
||||
code: language === 'zh' ? '配置模型 → 配置交易所 → 创建交易员' : 'Configure Model → Exchange → Create Trader',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Bot, BarChart3, Trash2, Pencil } from 'lucide-react'
|
||||
import { Bot, BarChart3, Trash2, Pencil, Eye, EyeOff } from 'lucide-react'
|
||||
import { t, type Language } from '../../../i18n/translations'
|
||||
import { getModelDisplayName } from '../index'
|
||||
import type { TraderInfo, Exchange } from '../../../types'
|
||||
@@ -12,6 +12,7 @@ interface TradersGridProps {
|
||||
onEditTrader: (traderId: string) => void
|
||||
onDeleteTrader: (traderId: string) => void
|
||||
onToggleTrader: (traderId: string, running: boolean) => void
|
||||
onToggleCompetition?: (traderId: string, showInCompetition: boolean) => void
|
||||
}
|
||||
|
||||
export function TradersGrid({
|
||||
@@ -22,6 +23,7 @@ export function TradersGrid({
|
||||
onEditTrader,
|
||||
onDeleteTrader,
|
||||
onToggleTrader,
|
||||
onToggleCompetition,
|
||||
}: TradersGridProps) {
|
||||
// Helper function to get exchange display name
|
||||
const getExchangeDisplayName = (exchangeId: string | undefined) => {
|
||||
@@ -166,6 +168,31 @@ export function TradersGrid({
|
||||
{trader.is_running ? t('stop', language) : t('start', language)}
|
||||
</button>
|
||||
|
||||
{onToggleCompetition && (
|
||||
<button
|
||||
onClick={() => onToggleCompetition(trader.trader_id, trader.show_in_competition ?? true)}
|
||||
className="px-2 md:px-3 py-1.5 md:py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 whitespace-nowrap flex items-center gap-1"
|
||||
style={
|
||||
trader.show_in_competition !== false
|
||||
? {
|
||||
background: 'rgba(14, 203, 129, 0.1)',
|
||||
color: '#0ECB81',
|
||||
}
|
||||
: {
|
||||
background: 'rgba(132, 142, 156, 0.1)',
|
||||
color: '#848E9C',
|
||||
}
|
||||
}
|
||||
title={trader.show_in_competition !== false ? '在竞技场显示' : '在竞技场隐藏'}
|
||||
>
|
||||
{trader.show_in_competition !== false ? (
|
||||
<Eye className="w-3 h-3 md:w-4 md:h-4" />
|
||||
) : (
|
||||
<EyeOff className="w-3 h-3 md:w-4 md:h-4" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={() => onDeleteTrader(trader.trader_id)}
|
||||
className="px-2 md:px-3 py-1.5 md:py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105"
|
||||
|
||||
Reference in New Issue
Block a user