Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 125 additions & 1 deletion web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,12 @@ function App() {
// };

// 获取trader列表(仅在用户登录时)
const { data: traders } = useSWR<TraderInfo[]>(
const { data: traders, error: tradersError } = useSWR<TraderInfo[]>(
user && token ? 'traders' : null,
api.getTraders,
{
refreshInterval: 10000,
shouldRetryOnError: false, // 避免在后端未运行时无限重试
}
)

Expand Down Expand Up @@ -371,8 +372,14 @@ function App() {
lastUpdate={lastUpdate}
language={language}
traders={traders}
tradersError={tradersError}
selectedTraderId={selectedTraderId}
onTraderSelect={setSelectedTraderId}
onNavigateToTraders={() => {
window.history.pushState({}, '', '/traders')
setRoute('/traders')
setCurrentPage('traders')
}}
/>
)}
</main>
Expand Down Expand Up @@ -437,13 +444,17 @@ function TraderDetailsPage({
lastUpdate,
language,
traders,
tradersError,
selectedTraderId,
onTraderSelect,
onNavigateToTraders,
}: {
selectedTrader?: TraderInfo
traders?: TraderInfo[]
tradersError?: Error
selectedTraderId?: string
onTraderSelect: (traderId: string) => void
onNavigateToTraders: () => void
status?: SystemStatus
account?: AccountInfo
positions?: Position[]
Expand All @@ -452,6 +463,119 @@ function TraderDetailsPage({
lastUpdate: string
language: Language
}) {
// If API failed with error, show empty state (likely backend not running)
if (tradersError) {
return (
<div className="flex items-center justify-center min-h-[60vh]">
<div className="text-center max-w-md mx-auto px-6">
{/* Icon */}
<div
className="w-24 h-24 mx-auto mb-6 rounded-full flex items-center justify-center"
style={{
background: 'rgba(240, 185, 11, 0.1)',
border: '2px solid rgba(240, 185, 11, 0.3)',
}}
>
<svg
className="w-12 h-12"
style={{ color: '#F0B90B' }}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
/>
</svg>
</div>

{/* Title */}
<h2 className="text-2xl font-bold mb-3" style={{ color: '#EAECEF' }}>
{t('dashboardEmptyTitle', language)}
</h2>

{/* Description */}
<p className="text-base mb-6" style={{ color: '#848E9C' }}>
{t('dashboardEmptyDescription', language)}
</p>

{/* CTA Button */}
<button
onClick={onNavigateToTraders}
className="px-6 py-3 rounded-lg font-semibold transition-all hover:scale-105 active:scale-95"
style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
color: '#0B0E11',
boxShadow: '0 4px 12px rgba(240, 185, 11, 0.3)',
}}
>
{t('goToTradersPage', language)}
</button>
</div>
</div>
)
}

// If traders is loaded and empty, show empty state
if (traders && traders.length === 0) {
return (
<div className="flex items-center justify-center min-h-[60vh]">
<div className="text-center max-w-md mx-auto px-6">
{/* Icon */}
<div
className="w-24 h-24 mx-auto mb-6 rounded-full flex items-center justify-center"
style={{
background: 'rgba(240, 185, 11, 0.1)',
border: '2px solid rgba(240, 185, 11, 0.3)',
}}
>
<svg
className="w-12 h-12"
style={{ color: '#F0B90B' }}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
/>
</svg>
</div>

{/* Title */}
<h2 className="text-2xl font-bold mb-3" style={{ color: '#EAECEF' }}>
{t('dashboardEmptyTitle', language)}
</h2>

{/* Description */}
<p className="text-base mb-6" style={{ color: '#848E9C' }}>
{t('dashboardEmptyDescription', language)}
</p>

{/* CTA Button */}
<button
onClick={onNavigateToTraders}
className="px-6 py-3 rounded-lg font-semibold transition-all hover:scale-105 active:scale-95"
style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
color: '#0B0E11',
boxShadow: '0 4px 12px rgba(240, 185, 11, 0.3)',
}}
>
{t('goToTradersPage', language)}
</button>
</div>
</div>
)
}

// If traders is still loading or selectedTrader is not ready, show skeleton
if (!selectedTrader) {
return (
<div className="space-y-6">
Expand Down
12 changes: 7 additions & 5 deletions web/src/components/AITradersPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
AlertTriangle,
BookOpen,
HelpCircle,
Radio,
} from 'lucide-react'

// 获取友好的AI模型名称
Expand Down Expand Up @@ -691,7 +692,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
</div>
</div>

<div className="flex gap-2 md:gap-3 w-full md:w-auto overflow-x-auto flex-wrap md:flex-nowrap">
<div className="flex gap-2 md:gap-3 w-full md:w-auto overflow-hidden flex-wrap md:flex-nowrap">
<button
onClick={handleAddModel}
className="px-3 md:px-4 py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 flex items-center gap-1 md:gap-2 whitespace-nowrap"
Expand Down Expand Up @@ -720,14 +721,15 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {

<button
onClick={() => setShowSignalSourceModal(true)}
className="px-3 md:px-4 py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 whitespace-nowrap"
className="px-3 md:px-4 py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 flex items-center gap-1 md:gap-2 whitespace-nowrap"
style={{
background: '#2B3139',
color: '#EAECEF',
border: '1px solid #474D57',
}}
>
📡 {t('signalSource', language)}
<Radio className="w-3 h-3 md:w-4 md:h-4" />
{t('signalSource', language)}
</button>

<button
Expand Down Expand Up @@ -782,7 +784,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
<strong>{t('solutions', language)}</strong>
</p>
<ul className="list-disc list-inside space-y-1 ml-2 mt-1">
<li>点击"📡 {t('signalSource', language)}"按钮配置API地址</li>
<li>点击"{t('signalSource', language)}"按钮配置API地址</li>
<li>或在交易员配置中禁用"使用币种池"和"使用OI Top"</li>
<li>或在交易员配置中设置自定义币种列表</li>
</ul>
Expand Down Expand Up @@ -1281,7 +1283,7 @@ function SignalSourceModal({
style={{ background: '#1E2329' }}
>
<h3 className="text-xl font-bold mb-4" style={{ color: '#EAECEF' }}>
📡 {t('signalSourceConfig', language)}
{t('signalSourceConfig', language)}
</h3>

<form onSubmit={handleSubmit} className="space-y-4">
Expand Down
8 changes: 8 additions & 0 deletions web/src/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ export const translations = {
currentTraders: 'Current Traders',
noTraders: 'No AI Traders',
createFirstTrader: 'Create your first AI trader to get started',
dashboardEmptyTitle: 'No Traders Configured',
dashboardEmptyDescription:
"You haven't created any AI traders yet. Create your first trader to start automated trading.",
goToTradersPage: 'Go to Traders Page',
configureModelsFirst: 'Please configure AI models first',
configureExchangesFirst: 'Please configure exchanges first',
configureModelsAndExchangesFirst:
Expand Down Expand Up @@ -830,6 +834,10 @@ export const translations = {
currentTraders: '当前交易员',
noTraders: '暂无AI交易员',
createFirstTrader: '创建您的第一个AI交易员开始使用',
dashboardEmptyTitle: '暂无交易员',
dashboardEmptyDescription:
'您还未创建任何AI交易员,创建您的第一个交易员以开始自动化交易。',
goToTradersPage: '前往交易员页面',
configureModelsFirst: '请先配置AI模型',
configureExchangesFirst: '请先配置交易所',
configureModelsAndExchangesFirst: '请先配置AI模型和交易所',
Expand Down
Loading