import { useState } from 'react' import { Plus, X, Database, TrendingUp, List, Link, AlertCircle, Ban } from 'lucide-react' import type { CoinSourceConfig } from '../../types' // Default API URLs for data sources const DEFAULT_COIN_POOL_API_URL = 'http://nofxaios.com:30006/api/ai500/list?auth=cm_568c67eae410d912c54c' const DEFAULT_OI_TOP_API_URL = 'http://nofxaios.com:30006/api/oi/top-ranking?limit=20&duration=1h&auth=cm_568c67eae410d912c54c' interface CoinSourceEditorProps { config: CoinSourceConfig onChange: (config: CoinSourceConfig) => void disabled?: boolean language: string } export function CoinSourceEditor({ config, onChange, disabled, language, }: CoinSourceEditorProps) { const [newCoin, setNewCoin] = useState('') const [newExcludedCoin, setNewExcludedCoin] = useState('') const t = (key: string) => { const translations: Record> = { sourceType: { zh: '数据来源类型', en: 'Source Type' }, static: { zh: '静态列表', en: 'Static List' }, coinpool: { zh: 'AI500 数据源', en: 'AI500 Data Provider' }, oi_top: { zh: 'OI Top 持仓增长', en: 'OI Top' }, mixed: { zh: '混合模式', en: 'Mixed Mode' }, staticCoins: { zh: '自定义币种', en: 'Custom Coins' }, addCoin: { zh: '添加币种', en: 'Add Coin' }, useCoinPool: { zh: '启用 AI500 数据源', en: 'Enable AI500 Data Provider' }, coinPoolLimit: { zh: '数据源数量上限', en: 'Data Provider Limit' }, coinPoolApiUrl: { zh: 'AI500 API URL', en: 'AI500 API URL' }, coinPoolApiUrlPlaceholder: { zh: '输入 AI500 数据源 API 地址...', en: 'Enter AI500 data provider API URL...' }, useOITop: { zh: '启用 OI Top 数据', en: 'Enable OI Top' }, oiTopLimit: { zh: 'OI Top 数量上限', en: 'OI Top Limit' }, oiTopApiUrl: { zh: 'OI Top API URL', en: 'OI Top API URL' }, oiTopApiUrlPlaceholder: { zh: '输入 OI Top 持仓数据 API 地址...', en: 'Enter OI Top API URL...' }, staticDesc: { zh: '手动指定交易币种列表', en: 'Manually specify trading coins' }, coinpoolDesc: { zh: '使用 AI500 智能筛选的热门币种', en: 'Use AI500 smart-filtered popular coins', }, oiTopDesc: { zh: '使用持仓量增长最快的币种', en: 'Use coins with fastest OI growth', }, mixedDesc: { zh: '组合多种数据源,AI500 + OI Top + 自定义', en: 'Combine multiple sources: AI500 + OI Top + Custom', }, apiUrlRequired: { zh: '需要填写 API URL 才能获取数据', en: 'API URL required to fetch data' }, dataSourceConfig: { zh: '数据源配置', en: 'Data Source Configuration' }, fillDefault: { zh: '填入默认', en: 'Fill Default' }, excludedCoins: { zh: '排除币种', en: 'Excluded Coins' }, excludedCoinsDesc: { zh: '这些币种将从所有数据源中排除,不会被交易', en: 'These coins will be excluded from all sources and will not be traded' }, addExcludedCoin: { zh: '添加排除', en: 'Add Excluded' }, } return translations[key]?.[language] || key } const sourceTypes = [ { value: 'static', icon: List, color: '#848E9C' }, { value: 'coinpool', icon: Database, color: '#F0B90B' }, { value: 'oi_top', icon: TrendingUp, color: '#0ECB81' }, { value: 'mixed', icon: Database, color: '#60a5fa' }, ] as const // xyz dex assets (stocks, forex, commodities) - should NOT get USDT suffix const xyzDexAssets = new Set([ // Stocks 'TSLA', 'NVDA', 'AAPL', 'MSFT', 'META', 'AMZN', 'GOOGL', 'AMD', 'COIN', 'NFLX', 'PLTR', 'HOOD', 'INTC', 'MSTR', 'TSM', 'ORCL', 'MU', 'RIVN', 'COST', 'LLY', 'CRCL', 'SKHX', 'SNDK', // Forex 'EUR', 'JPY', // Commodities 'GOLD', 'SILVER', // Index 'XYZ100', ]) const isXyzDexAsset = (symbol: string): boolean => { const base = symbol.toUpperCase().replace(/^XYZ:/, '').replace(/USDT$|USD$|-USDC$/, '') return xyzDexAssets.has(base) } const handleAddCoin = () => { if (!newCoin.trim()) return const symbol = newCoin.toUpperCase().trim() // For xyz dex assets (stocks, forex, commodities), use xyz: prefix without USDT let formattedSymbol: string if (isXyzDexAsset(symbol)) { // Remove xyz: prefix (case-insensitive) and any USD suffixes const base = symbol.replace(/^xyz:/i, '').replace(/USDT$|USD$|-USDC$/i, '') formattedSymbol = `xyz:${base}` } else { formattedSymbol = symbol.endsWith('USDT') ? symbol : `${symbol}USDT` } const currentCoins = config.static_coins || [] if (!currentCoins.includes(formattedSymbol)) { onChange({ ...config, static_coins: [...currentCoins, formattedSymbol], }) } setNewCoin('') } const handleRemoveCoin = (coin: string) => { onChange({ ...config, static_coins: (config.static_coins || []).filter((c) => c !== coin), }) } const handleAddExcludedCoin = () => { if (!newExcludedCoin.trim()) return const symbol = newExcludedCoin.toUpperCase().trim() // For xyz dex assets, use xyz: prefix without USDT let formattedSymbol: string if (isXyzDexAsset(symbol)) { const base = symbol.replace(/^xyz:/i, '').replace(/USDT$|USD$|-USDC$/i, '') formattedSymbol = `xyz:${base}` } else { formattedSymbol = symbol.endsWith('USDT') ? symbol : `${symbol}USDT` } const currentExcluded = config.excluded_coins || [] if (!currentExcluded.includes(formattedSymbol)) { onChange({ ...config, excluded_coins: [...currentExcluded, formattedSymbol], }) } setNewExcludedCoin('') } const handleRemoveExcludedCoin = (coin: string) => { onChange({ ...config, excluded_coins: (config.excluded_coins || []).filter((c) => c !== coin), }) } return (
{/* Source Type Selector */}
{sourceTypes.map(({ value, icon: Icon, color }) => ( ))}
{/* Static Coins */} {(config.source_type === 'static' || config.source_type === 'mixed') && (
{(config.static_coins || []).map((coin) => ( {coin} {!disabled && ( )} ))}
{!disabled && (
setNewCoin(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleAddCoin()} placeholder="BTC, ETH, SOL..." className="flex-1 px-4 py-2 rounded-lg" style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF', }} />
)}
)} {/* Excluded Coins */}

{t('excludedCoinsDesc')}

{(config.excluded_coins || []).map((coin) => ( {coin} {!disabled && ( )} ))} {(config.excluded_coins || []).length === 0 && ( {language === 'zh' ? '无' : 'None'} )}
{!disabled && (
setNewExcludedCoin(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleAddExcludedCoin()} placeholder="BTC, ETH, DOGE..." className="flex-1 px-4 py-2 rounded-lg text-sm" style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF', }} />
)}
{/* Coin Pool Options */} {(config.source_type === 'coinpool' || config.source_type === 'mixed') && (
{t('dataSourceConfig')} - AI500
{config.use_coin_pool && (
{t('coinPoolLimit')}: !disabled && onChange({ ...config, coin_pool_limit: parseInt(e.target.value) || 10 }) } disabled={disabled} min={1} max={100} className="w-20 px-3 py-1.5 rounded" style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF', }} />
)}
{config.use_coin_pool && (
{!disabled && !config.coin_pool_api_url && ( )}
!disabled && onChange({ ...config, coin_pool_api_url: e.target.value }) } disabled={disabled} placeholder={t('coinPoolApiUrlPlaceholder')} className="w-full px-4 py-2.5 rounded-lg font-mono text-sm" style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF', }} /> {!config.coin_pool_api_url && (
{t('apiUrlRequired')}
)}
)}
)} {/* OI Top Options */} {(config.source_type === 'oi_top' || config.source_type === 'mixed') && (
{t('dataSourceConfig')} - OI Top
{config.use_oi_top && (
{t('oiTopLimit')}: !disabled && onChange({ ...config, oi_top_limit: parseInt(e.target.value) || 20 }) } disabled={disabled} min={1} max={50} className="w-20 px-3 py-1.5 rounded" style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF', }} />
)}
{config.use_oi_top && (
{!disabled && !config.oi_top_api_url && ( )}
!disabled && onChange({ ...config, oi_top_api_url: e.target.value }) } disabled={disabled} placeholder={t('oiTopApiUrlPlaceholder')} className="w-full px-4 py-2.5 rounded-lg font-mono text-sm" style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF', }} /> {!config.oi_top_api_url && (
{t('apiUrlRequired')}
)}
)}
)}
) }