Files
trading_bot_v3/components/AIAnalysisPanel.tsx
mindesbunister 142d271c2c Fix progress tracking synchronization issues
- Enhanced EventSource message handling to properly reset UI state on completion
- Added completion detection based on all steps being finished
- Extended session deletion timeout from 1s to 3s for better UI updates
- Separated loading state management for single vs multi-timeframe analysis
- Ensured loading state is only reset by progress tracking for single timeframe
- Added immediate UI reset on errors to prevent stuck loading states
- Improved completion logging and state management
2025-07-17 12:35:59 +02:00

1545 lines
68 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import React, { useState, useEffect } from 'react'
import TradeModal from './TradeModal'
import ScreenshotGallery from './ScreenshotGallery'
const layouts = (process.env.NEXT_PUBLIC_TRADINGVIEW_LAYOUTS || 'ai,Diy module').split(',').map(l => l.trim())
// Layout display names mapping
const layoutDisplayNames: { [key: string]: string } = {
'ai': 'ai',
'Diy module': 'Diy module'
}
const timeframes = [
{ label: '1m', value: '1' },
{ label: '5m', value: '5' },
{ label: '15m', value: '15' },
{ label: '30m', value: '30' },
{ label: '1h', value: '60' },
{ label: '2h', value: '120' },
{ label: '4h', value: '240' },
{ label: '1d', value: 'D' },
{ label: '1w', value: 'W' },
{ label: '1M', value: 'M' },
]
const popularCoins = [
{
name: 'Bitcoin',
symbol: 'BTCUSD',
icon: 'https://assets.coingecko.com/coins/images/1/large/bitcoin.png',
color: 'from-orange-400 to-orange-600'
},
{
name: 'Ethereum',
symbol: 'ETHUSD',
icon: 'https://assets.coingecko.com/coins/images/279/large/ethereum.png',
color: 'from-blue-400 to-blue-600'
},
{
name: 'Solana',
symbol: 'SOLUSD',
icon: 'https://assets.coingecko.com/coins/images/4128/large/solana.png',
color: 'from-purple-400 to-purple-600'
}
]
// Progress tracking interfaces
interface ProgressStep {
id: string
title: string
description: string
status: 'pending' | 'active' | 'completed' | 'error'
startTime?: number
endTime?: number
details?: string
}
interface AnalysisProgress {
sessionId: string
currentStep: number
totalSteps: number
steps: ProgressStep[]
timeframeProgress?: {
current: number
total: number
currentTimeframe?: string
}
}
interface AIAnalysisPanelProps {
onAnalysisComplete?: (analysis: any, symbol: string) => void
}
export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelProps = {}) {
const [symbol, setSymbol] = useState('BTCUSD')
const [selectedLayouts, setSelectedLayouts] = useState<string[]>(['ai', 'Diy module']) // Default to both layouts
const [selectedTimeframes, setSelectedTimeframes] = useState<string[]>(['60']) // Support multiple timeframes
const [loading, setLoading] = useState(false)
const [result, setResult] = useState<any>(null)
const [error, setError] = useState<string | null>(null)
const [progress, setProgress] = useState<AnalysisProgress | null>(null)
const [eventSource, setEventSource] = useState<EventSource | null>(null)
const [modalOpen, setModalOpen] = useState(false)
const [modalData, setModalData] = useState<any>(null)
const [enlargedScreenshot, setEnlargedScreenshot] = useState<string | null>(null)
const [tradeModalOpen, setTradeModalOpen] = useState(false)
const [tradeModalData, setTradeModalData] = useState<any>(null)
// Helper function to safely render any value
const safeRender = (value: any): string => {
if (typeof value === 'string') return value
if (typeof value === 'number') return value.toString()
if (Array.isArray(value)) return value.join(', ')
if (typeof value === 'object' && value !== null) {
return JSON.stringify(value)
}
return String(value)
}
// Real-time progress tracking
const startProgressTracking = (sessionId: string) => {
console.log(`🔍 Starting progress tracking for session: ${sessionId}`)
// Close existing connection
if (eventSource) {
eventSource.close()
}
const es = new EventSource(`/api/progress/${sessionId}/stream`)
es.onopen = () => {
console.log(`🔍 EventSource connection opened for ${sessionId}`)
}
es.onmessage = (event) => {
try {
const progressData = JSON.parse(event.data)
console.log(`🔍 Received progress update for ${sessionId}:`, progressData)
if (progressData.type === 'complete') {
console.log(`🔍 Analysis complete for ${sessionId}`)
es.close()
setEventSource(null)
// Reset UI state when analysis completes
setLoading(false)
setProgress(null)
} else if (progressData.type === 'connected') {
console.log(`🔍 EventSource connected for ${sessionId}`)
} else {
// Update progress state immediately
setProgress(progressData)
// Check if analysis is complete based on steps
if (progressData.steps && progressData.steps.length > 0) {
const allCompleted = progressData.steps.every((step: any) =>
step.status === 'completed' || step.status === 'error'
)
if (allCompleted) {
console.log(`🔍 All steps completed for ${sessionId}, resetting UI state`)
setTimeout(() => {
setLoading(false)
setProgress(null)
es.close()
setEventSource(null)
}, 2000) // Give 2 seconds to show final state
}
}
}
} catch (error) {
console.error('Error parsing progress data:', error)
}
}
es.onerror = (error) => {
console.error('EventSource error:', error)
es.close()
setEventSource(null)
}
setEventSource(es)
}
// Cleanup event source on unmount
React.useEffect(() => {
return () => {
if (eventSource) {
eventSource.close()
}
}
}, [eventSource])
// Normalize layout key to match backend expectations
const normalizeLayoutKey = (layout: string): string => {
// Keep the exact names as they appear in TradingView
if (layout === 'ai') return 'ai'
if (layout === 'Diy module') return 'Diy module'
return layout
}
// Get display name for layout (keep exact TradingView names)
const getLayoutDisplayName = (layout: string): string => {
return layoutDisplayNames[layout] || layout
}
const toggleLayout = (layout: string) => {
const normalizedLayout = normalizeLayoutKey(layout)
setSelectedLayouts(prev =>
prev.includes(normalizedLayout)
? prev.filter(l => l !== normalizedLayout)
: [...prev, normalizedLayout]
)
}
const toggleTimeframe = (timeframe: string) => {
setSelectedTimeframes(prev =>
prev.includes(timeframe)
? prev.filter(tf => tf !== timeframe)
: [...prev, timeframe]
)
}
// Helper function to create initial progress steps (no longer used - using real-time progress)
// const createProgressSteps = ...removed for real-time implementation
// Helper function to update progress (no longer used - using real-time progress)
// const updateProgress = ...removed for real-time implementation
const performAnalysis = async (analysisSymbol = symbol, analysisTimeframes = selectedTimeframes) => {
if (loading || selectedLayouts.length === 0 || analysisTimeframes.length === 0) return
setLoading(true)
setError(null)
setResult(null)
// Set initial progress state to show animation immediately
setProgress({
sessionId: 'initializing',
currentStep: 1,
totalSteps: 6,
steps: [
{
id: 'init',
title: 'Initializing Analysis',
description: 'Starting AI-powered trading analysis...',
status: 'active',
startTime: Date.now()
},
{
id: 'auth',
title: 'TradingView Authentication',
description: 'Logging into TradingView accounts',
status: 'pending'
},
{
id: 'navigation',
title: 'Chart Navigation',
description: 'Navigating to chart layouts',
status: 'pending'
},
{
id: 'loading',
title: 'Chart Data Loading',
description: 'Waiting for chart data and indicators',
status: 'pending'
},
{
id: 'capture',
title: 'Screenshot Capture',
description: 'Capturing high-quality screenshots',
status: 'pending'
},
{
id: 'analysis',
title: 'AI Analysis',
description: 'Analyzing screenshots with AI',
status: 'pending'
}
],
timeframeProgress: analysisTimeframes.length > 1 ? {
current: 0,
total: analysisTimeframes.length
} : undefined
})
try {
if (analysisTimeframes.length === 1) {
// Single timeframe analysis with real-time progress
// Pre-generate sessionId and start progress tracking BEFORE making the API call
const sessionId = `analysis_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
startProgressTracking(sessionId)
const response = await fetch('/api/enhanced-screenshot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol: analysisSymbol,
timeframe: analysisTimeframes[0],
layouts: selectedLayouts,
analyze: true,
sessionId: sessionId // Pass pre-generated sessionId
})
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Analysis failed')
}
setResult(data)
// For single timeframe analysis, don't set loading to false yet
// Let the progress tracking handle UI state reset
console.log(`🔍 API call completed for ${sessionId}, progress tracking will handle UI reset`)
// Call the callback with analysis result if provided
if (onAnalysisComplete && data.analysis) {
onAnalysisComplete(data.analysis, analysisSymbol)
}
} else {
// Multiple timeframe analysis
const results = []
for (let i = 0; i < analysisTimeframes.length; i++) {
const tf = analysisTimeframes[i]
const timeframeLabel = timeframes.find(t => t.value === tf)?.label || tf
console.log(`🧪 Analyzing timeframe: ${timeframeLabel}`)
const response = await fetch('/api/enhanced-screenshot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol: analysisSymbol,
timeframe: tf,
layouts: selectedLayouts,
analyze: true
})
})
const result = await response.json()
results.push({
timeframe: tf,
timeframeLabel,
success: response.ok,
result
})
// Start progress tracking for the first timeframe session
if (i === 0 && result.sessionId) {
startProgressTracking(result.sessionId)
}
// Update timeframe progress manually for multi-timeframe
setProgress(prev => prev ? {
...prev,
timeframeProgress: {
current: i + 1,
total: analysisTimeframes.length,
currentTimeframe: timeframeLabel
}
} : null)
// Small delay between requests
await new Promise(resolve => setTimeout(resolve, 1000))
}
const multiResult = {
type: 'multi_timeframe',
symbol: analysisSymbol,
summary: `Analyzed ${results.length} timeframes for ${analysisSymbol}`,
results
}
setResult(multiResult)
// Call the callback with the first successful analysis result if provided
if (onAnalysisComplete) {
const firstSuccessfulResult = results.find(r => r.success && r.result?.analysis)
if (firstSuccessfulResult) {
onAnalysisComplete(firstSuccessfulResult.result.analysis, analysisSymbol)
}
}
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to perform analysis'
setError(errorMessage)
// Reset loading state immediately on error
setLoading(false)
setProgress(null)
// Mark current active step as error
setProgress(prev => {
if (!prev) return null
const activeStepIndex = prev.steps.findIndex(step => step.status === 'active')
if (activeStepIndex >= 0) {
const updatedSteps = [...prev.steps]
updatedSteps[activeStepIndex] = {
...updatedSteps[activeStepIndex],
status: 'error',
details: errorMessage,
endTime: Date.now()
}
return { ...prev, steps: updatedSteps }
}
return prev
})
} finally {
// Only reset loading for multi-timeframe analysis
// Single timeframe analysis loading state is managed by progress tracking
if (analysisTimeframes.length > 1) {
setLoading(false)
}
}
}
const quickAnalyze = async (coinSymbol: string, quickTimeframes = selectedTimeframes) => {
setSymbol(coinSymbol)
if (!loading) {
await performAnalysis(coinSymbol, quickTimeframes)
}
}
const quickTimeframeTest = async (testTimeframe: string) => {
// Toggle the timeframe in selection instead of replacing
const newTimeframes = selectedTimeframes.includes(testTimeframe)
? selectedTimeframes.filter(tf => tf !== testTimeframe)
: [...selectedTimeframes, testTimeframe]
setSelectedTimeframes(newTimeframes)
if (!loading && symbol && newTimeframes.length > 0) {
await performAnalysis(symbol, newTimeframes)
}
}
const testAllTimeframes = async () => {
if (loading) return
const allTimeframeValues = timeframes.map(tf => tf.value)
setSelectedTimeframes(allTimeframeValues)
if (!loading && symbol) {
await performAnalysis(symbol, allTimeframeValues)
}
}
async function handleAnalyze() {
await performAnalysis()
}
// Trade initiation handler
const handleTradeClick = (tfResult: any) => {
console.log('🔥 AIAnalysisPanel handleTradeClick called with:', tfResult)
const analysis = tfResult?.result?.analysis || tfResult?.analysis || {}
console.log('🔥 Extracted analysis:', analysis)
// Enhanced data extraction with better fallbacks
let entryPrice = ''
let takeProfit1 = ''
let takeProfit2 = ''
let stopLoss = ''
// Extract entry price with multiple fallback options
if (analysis.entry?.price) {
entryPrice = analysis.entry.price.toString()
} else if (analysis.entry && typeof analysis.entry === 'number') {
entryPrice = analysis.entry.toString()
} else if (analysis.entry && typeof analysis.entry === 'string') {
entryPrice = analysis.entry
}
// Extract take profit 1 with multiple fallback options
if (analysis.takeProfits?.tp1?.price) {
takeProfit1 = analysis.takeProfits.tp1.price.toString()
} else if (analysis.takeProfits?.tp1 && typeof analysis.takeProfits.tp1 === 'number') {
takeProfit1 = analysis.takeProfits.tp1.toString()
} else if (analysis.takeProfits && typeof analysis.takeProfits === 'number') {
takeProfit1 = analysis.takeProfits.toString()
} else if (analysis.takeProfit?.price) {
takeProfit1 = analysis.takeProfit.price.toString()
} else if (analysis.takeProfit && typeof analysis.takeProfit === 'number') {
takeProfit1 = analysis.takeProfit.toString()
}
// Extract take profit 2 if available
if (analysis.takeProfits?.tp2?.price) {
takeProfit2 = analysis.takeProfits.tp2.price.toString()
} else if (analysis.takeProfits?.tp2 && typeof analysis.takeProfits.tp2 === 'number') {
takeProfit2 = analysis.takeProfits.tp2.toString()
}
// Extract stop loss with multiple fallback options
if (analysis.stopLoss?.price) {
stopLoss = analysis.stopLoss.price.toString()
} else if (analysis.stopLoss && typeof analysis.stopLoss === 'number') {
stopLoss = analysis.stopLoss.toString()
} else if (analysis.stopLoss && typeof analysis.stopLoss === 'string') {
stopLoss = analysis.stopLoss
}
const tradeData = {
entry: entryPrice,
tp: takeProfit1, // This maps to tp1 in the modal
tp2: takeProfit2, // This will be handled in the modal
sl: stopLoss,
symbol: symbol,
timeframe: tfResult?.timeframeLabel || tfResult?.timeframe || '',
}
console.log('🔥 Enhanced trade data extraction:', {
originalAnalysis: analysis,
extractedData: tradeData
})
setTradeModalData(tradeData)
console.log('🔥 Opening trade modal...')
setTradeModalOpen(true)
}
// Trade execution API call
const executeTrade = async (tradeData: any) => {
try {
// Determine if this is a leveraged position or spot trade
const leverage = parseFloat(tradeData.leverage) || 1
const isLeveraged = leverage > 1
// Route to appropriate API based on leverage
const apiEndpoint = isLeveraged ? '/api/trading/execute-drift' : '/api/trading/execute-dex'
const tradingMode = isLeveraged ? 'PERP' : 'SPOT'
console.log(`🎯 Executing ${tradingMode} trade with ${leverage}x leverage via ${apiEndpoint}`)
const response = await fetch(apiEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol: tradeData.symbol || symbol,
side: 'BUY', // Could be derived from analysis
amount: parseFloat(tradeData.positionSize) || parseFloat(tradeData.size),
amountUSD: parseFloat(tradeData.amountUSD || tradeData.positionSize || tradeData.size),
leverage: leverage,
stopLoss: parseFloat(tradeData.sl),
takeProfit: parseFloat(tradeData.tp1), // Use TP1 as primary target
useRealDEX: true, // Enable real trading for manual execution
tradingMode: tradingMode,
tradingPair: `${tradeData.symbol || symbol}/USDC`,
quickSwap: false
})
})
const result = await response.json()
if (response.ok && result.success) {
// Show detailed success message based on trading type
const leverage = parseFloat(tradeData.leverage) || 1
const isLeveraged = leverage > 1
const tradeType = isLeveraged ? 'Leveraged Perpetual Position' : 'Spot Trade'
const platform = isLeveraged ? 'Drift Protocol' : 'Jupiter DEX'
let message = `${tradeType} executed successfully!\n\n`
message += `📊 Transaction ID: ${result.trade?.txId || result.txId}\n`
message += `💰 Symbol: ${tradeData.symbol || symbol}\n`
message += `📈 Size: ${tradeData.positionSize || tradeData.size} USDC\n`
if (isLeveraged) {
message += `⚡ Leverage: ${leverage}x (via increased position size)\n`
message += `<EFBFBD> Actual Trade Size: $${(parseFloat(tradeData.positionSize || tradeData.size) * leverage).toFixed(2)}\n`
}
message += `<EFBFBD>🏪 Platform: ${platform}\n`
if (tradeData.sl) message += `🛑 Stop Loss: $${tradeData.sl} (Jupiter Trigger Order)\n`
if (tradeData.tp1) message += `🎯 Take Profit: $${tradeData.tp1} (Jupiter Trigger Order)\n`
if (result.triggerOrders?.status === 'CREATED') {
message += `\n🔄 Trigger Orders: ACTIVE\n`
if (result.triggerOrders.stopLossOrderId) message += `🛑 SL Order: ${result.triggerOrders.stopLossOrderId.substring(0, 8)}...\n`
if (result.triggerOrders.takeProfitOrderId) message += `🎯 TP Order: ${result.triggerOrders.takeProfitOrderId.substring(0, 8)}...\n`
} else if (result.trade?.monitoring || result.position) {
message += `\n🔄 Position monitoring: ACTIVE`
}
alert(message)
} else {
// Show detailed error message
const errorMsg = result.error || 'Unknown error occurred'
if (errorMsg.includes('not configured') || errorMsg.includes('Wallet not initialized')) {
alert(`❌ Trade Failed: Jupiter DEX Not Configured\n\nPlease configure your Jupiter DEX wallet in the settings before executing real trades.\n\nError: ${errorMsg}`)
} else if (errorMsg.includes('insufficient') || errorMsg.includes('balance')) {
alert(`❌ Trade Failed: Insufficient Balance\n\nPlease ensure you have enough tokens in your wallet.\n\nError: ${errorMsg}`)
} else if (errorMsg.includes('Real Jupiter Perpetuals trading not yet implemented')) {
alert(`❌ Real Trading Not Available\n\nReal Jupiter Perpetuals trading is still in development. This trade will be simulated instead.\n\nTo use real spot trading, reduce the leverage to 1x.`)
} else if (errorMsg.includes('Trigger API error') || errorMsg.includes('trigger orders failed')) {
alert(`⚠️ Trade Executed, But Trigger Orders Failed\n\nYour main trade was successful, but stop loss/take profit orders could not be created.\n\nError: ${errorMsg}\n\nPlease monitor your position manually.`)
} else {
alert(`❌ Trade Failed\n\nError: ${errorMsg}`)
}
}
} catch (error) {
console.error('Trade execution failed:', error)
alert('❌ Trade execution failed due to network error.\n\nPlease check your connection and try again.')
}
setTradeModalOpen(false)
}
// Screenshot gallery modal
const handleScreenshotClick = (src: string) => {
setEnlargedScreenshot(src)
}
return (
<div className="card card-gradient">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold text-white flex items-center">
<span className="w-8 h-8 bg-gradient-to-br from-cyan-400 to-blue-600 rounded-lg flex items-center justify-center mr-3">
🤖
</span>
AI Chart Analysis
</h2>
<div className="flex items-center space-x-2 text-sm text-gray-400">
<div className="w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div>
<span>AI Powered</span>
</div>
</div>
{/* Quick Coin & Timeframe Analysis */}
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-semibold text-gray-300 flex items-center">
<span className="w-4 h-4 bg-yellow-500 rounded-full mr-2"></span>
Quick Analysis
</h3>
<span className="text-xs text-gray-500">Select coin + timeframe combo for instant analysis</span>
</div>
{/* Quick Timeframe Presets */}
<div className="mb-4 p-3 bg-gray-800/30 rounded-lg">
<label className="block text-xs font-medium text-gray-400 mb-2">Quick Timeframe Presets</label>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
<button
onClick={() => setSelectedTimeframes(['5', '15', '30'])}
className="py-2 px-3 rounded-lg text-xs font-medium bg-purple-600/20 text-purple-300 hover:bg-purple-600/30 transition-all"
>
🕒 Scalping (5m, 15m, 30m)
</button>
<button
onClick={() => setSelectedTimeframes(['60', '120', '240'])}
className="py-2 px-3 rounded-lg text-xs font-medium bg-blue-600/20 text-blue-300 hover:bg-blue-600/30 transition-all"
>
📊 Day Trading (1h, 2h, 4h)
</button>
<button
onClick={() => setSelectedTimeframes(['240', 'D', 'W'])}
className="py-2 px-3 rounded-lg text-xs font-medium bg-green-600/20 text-green-300 hover:bg-green-600/30 transition-all"
>
📈 Swing (4h, 1d, 1w)
</button>
<button
onClick={() => setSelectedTimeframes(['D', 'W', 'M'])}
className="py-2 px-3 rounded-lg text-xs font-medium bg-orange-600/20 text-orange-300 hover:bg-orange-600/30 transition-all"
>
🎯 Position (1d, 1w, 1m)
</button>
</div>
</div>
{/* Timeframe Selection */}
<div className="mb-4">
<label className="block text-xs font-medium text-gray-400 mb-3">
Analysis Timeframes
<span className="text-xs text-cyan-400 ml-2">({selectedTimeframes.length} selected)</span>
</label>
<div className="grid grid-cols-4 gap-2">
{timeframes.map(tf => (
<label key={tf.value} className="group relative cursor-pointer">
<input
type="checkbox"
checked={selectedTimeframes.includes(tf.value)}
onChange={() => toggleTimeframe(tf.value)}
className="sr-only"
/>
<div className={`flex items-center justify-center p-3 rounded-lg border transition-all ${
selectedTimeframes.includes(tf.value)
? 'border-cyan-500 bg-cyan-500/10 text-cyan-300 shadow-lg shadow-cyan-500/20'
: 'border-gray-700 bg-gray-800/30 text-gray-400 hover:border-gray-600 hover:bg-gray-800/50 hover:text-gray-300'
}`}>
<span className="text-sm font-medium">{tf.label}</span>
{selectedTimeframes.includes(tf.value) && (
<div className="absolute top-1 right-1 w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div>
)}
</div>
</label>
))}
</div>
{selectedTimeframes.length > 0 && (
<div className="mt-3 p-3 bg-gray-800/30 rounded-lg">
<div className="text-xs text-gray-400">
Selected timeframes: <span className="text-cyan-400">
{selectedTimeframes.map(tf => timeframes.find(t => t.value === tf)?.label).join(', ')}
</span>
</div>
<div className="text-xs text-gray-500 mt-1">
💡 Multiple timeframes provide broader market outlook
</div>
</div>
)}
</div>
{/* Coin Selection */}
<div className="grid grid-cols-3 gap-3">
{popularCoins.map(coin => (
<button
key={coin.symbol}
onClick={() => quickAnalyze(coin.symbol)}
disabled={loading || selectedLayouts.length === 0}
className={`group relative p-3 rounded-lg border transition-all ${
loading || selectedLayouts.length === 0
? 'border-gray-700 bg-gray-800/30 cursor-not-allowed opacity-50'
: symbol === coin.symbol
? 'border-cyan-500 bg-cyan-500/10 text-cyan-300'
: 'border-gray-700 bg-gray-800/30 text-gray-300 hover:border-gray-600 hover:bg-gray-800/50 hover:text-white transform hover:scale-105'
}`}
>
<div className={`w-8 h-8 bg-gradient-to-br ${coin.color} rounded-lg flex items-center justify-center mx-auto mb-2 p-1`}>
<img
src={coin.icon}
alt={coin.name}
className="w-6 h-6 rounded-full"
/>
</div>
<div className="text-xs font-medium">{coin.name}</div>
<div className="text-xs text-gray-500">{coin.symbol}</div>
{symbol === coin.symbol && (
<div className="absolute top-1 right-1 w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div>
)}
</button>
))}
</div>
</div>
{/* Advanced Analysis Section */}
<div className="mb-8">
<h3 className="text-sm font-semibold text-gray-300 flex items-center mb-4">
<span className="w-4 h-4 bg-purple-500 rounded-full mr-2"></span>
Advanced Analysis
</h3>
{/* Symbol Input */}
<div className="grid grid-cols-1 gap-4 mb-6">
<div>
<label className="block text-xs font-medium text-gray-400 mb-2">Trading Pair</label>
<input
className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:border-cyan-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/20 transition-all"
value={symbol}
onChange={e => setSymbol(e.target.value.toUpperCase())}
placeholder="e.g., BTCUSD, ETHUSD"
/>
</div>
</div>
{/* Layout Selection */}
<div className="mb-6">
<label className="block text-xs font-medium text-gray-400 mb-3">Analysis Layouts</label>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{layouts.map(layout => {
const normalizedLayout = normalizeLayoutKey(layout)
const displayName = getLayoutDisplayName(layout)
const isSelected = selectedLayouts.includes(normalizedLayout)
return (
<label key={layout} className="group relative">
<input
type="checkbox"
checked={isSelected}
onChange={() => toggleLayout(layout)}
className="sr-only"
/>
<div className={`flex items-center p-3 rounded-lg border cursor-pointer transition-all ${
isSelected
? 'border-cyan-500 bg-cyan-500/10 text-cyan-300'
: 'border-gray-700 bg-gray-800/30 text-gray-300 hover:border-gray-600 hover:bg-gray-800/50'
}`}>
<div className={`w-4 h-4 rounded border-2 mr-3 flex items-center justify-center ${
isSelected
? 'border-cyan-500 bg-cyan-500'
: 'border-gray-600'
}`}>
{isSelected && (
<svg className="w-2 h-2 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
)}
</div>
<span className="text-sm font-medium">{displayName}</span>
</div>
</label>
)
})}
</div>
{selectedLayouts.length > 0 && (
<div className="mt-3 p-3 bg-gray-800/30 rounded-lg">
<div className="text-xs text-gray-400">
Selected layouts: <span className="text-cyan-400">
{selectedLayouts.map(layout => getLayoutDisplayName(layout)).join(', ')}
</span>
</div>
</div>
)}
</div>
{/* Analyze Button */}
<button
className={`w-full py-3 px-6 rounded-lg font-semibold transition-all duration-300 ${
loading
? 'bg-gray-700 text-gray-400 cursor-not-allowed'
: 'btn-primary transform hover:scale-[1.02] active:scale-[0.98]'
}`}
onClick={handleAnalyze}
disabled={loading}
>
{loading ? (
<div className="flex items-center justify-center space-x-2">
<div className="spinner"></div>
<span>Analyzing Chart...</span>
</div>
) : (
<div className="flex items-center justify-center space-x-2">
<span>🚀</span>
<span>Start AI Analysis</span>
</div>
)}
</button>
</div>
{/* Results Section */}
{error && (
<div className="mt-6 p-4 bg-red-500/10 border border-red-500/30 rounded-lg">
<div className="flex items-start space-x-3">
<div className="w-5 h-5 text-red-400 mt-0.5"></div>
<div>
<h4 className="text-red-400 font-medium text-sm">Analysis Error</h4>
<p className="text-red-300 text-xs mt-1 opacity-90">{error}</p>
</div>
</div>
</div>
)}
{loading && progress && (
<div className="mt-6 p-6 bg-gradient-to-br from-cyan-500/10 to-blue-500/10 border border-cyan-500/30 rounded-lg">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center space-x-3">
<div className="spinner border-cyan-500"></div>
<div>
<h4 className="text-cyan-400 font-medium text-lg">AI Analysis in Progress</h4>
<p className="text-cyan-300 text-sm opacity-90">
Analyzing {symbol} {selectedLayouts.map(layout => getLayoutDisplayName(layout)).join(', ')} layouts
</p>
</div>
</div>
{/* Overall Progress */}
<div className="text-right">
<div className="text-sm font-medium text-cyan-300">
Step {progress.currentStep} of {progress.totalSteps}
</div>
<div className="text-xs text-gray-400 mt-1">
{Math.round((progress.currentStep / progress.totalSteps) * 100)}% Complete
</div>
</div>
</div>
{/* Multi-timeframe progress */}
{progress.timeframeProgress && (
<div className="mb-6 p-4 bg-purple-800/20 rounded-lg border border-purple-500/30">
<div className="flex items-center justify-between mb-2">
<h5 className="text-purple-300 font-medium text-sm">Multi-Timeframe Analysis</h5>
<span className="text-xs text-purple-400">
{progress.timeframeProgress.current}/{progress.timeframeProgress.total} timeframes
</span>
</div>
<div className="w-full bg-purple-900/30 rounded-full h-2 mb-2">
<div
className="bg-gradient-to-r from-purple-500 to-purple-400 h-2 rounded-full transition-all duration-500"
style={{ width: `${(progress.timeframeProgress.current / progress.timeframeProgress.total) * 100}%` }}
/>
</div>
{progress.timeframeProgress.currentTimeframe && (
<p className="text-xs text-purple-300">
Current: {progress.timeframeProgress.currentTimeframe}
</p>
)}
</div>
)}
{/* Progress Steps */}
<div className="space-y-3">
{progress.steps.map((step, index) => {
const isActive = step.status === 'active'
const isCompleted = step.status === 'completed'
const isError = step.status === 'error'
const isPending = step.status === 'pending'
return (
<div
key={step.id}
className={`flex items-center space-x-4 p-3 rounded-lg transition-all duration-300 ${
isActive ? 'bg-cyan-500/20 border border-cyan-500/50' :
isCompleted ? 'bg-green-500/10 border border-green-500/30' :
isError ? 'bg-red-500/10 border border-red-500/30' :
'bg-gray-800/30 border border-gray-700/50'
}`}
>
{/* Step Icon */}
<div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium ${
isActive ? 'bg-cyan-500 text-white animate-pulse' :
isCompleted ? 'bg-green-500 text-white' :
isError ? 'bg-red-500 text-white' :
'bg-gray-600 text-gray-300'
}`}>
{isCompleted ? '✓' :
isError ? '✗' :
isActive ? '⟳' :
index + 1}
</div>
{/* Step Content */}
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<h6 className={`font-medium text-sm ${
isActive ? 'text-cyan-300' :
isCompleted ? 'text-green-300' :
isError ? 'text-red-300' :
'text-gray-400'
}`}>
{step.title}
</h6>
{/* Timing */}
{(step.startTime || step.endTime) && (
<span className="text-xs text-gray-500">
{step.endTime && step.startTime ?
`${((step.endTime - step.startTime) / 1000).toFixed(1)}s` :
isActive && step.startTime ?
`${((Date.now() - step.startTime) / 1000).toFixed(0)}s` :
''
}
</span>
)}
</div>
<p className={`text-xs mt-1 ${
isActive ? 'text-cyan-400' :
isCompleted ? 'text-green-400' :
isError ? 'text-red-400' :
'text-gray-500'
}`}>
{step.details || step.description}
</p>
</div>
{/* Active indicator */}
{isActive && (
<div className="flex-shrink-0">
<div className="w-3 h-3 bg-cyan-400 rounded-full animate-ping"></div>
</div>
)}
</div>
)
})}
</div>
{/* Overall Progress Bar */}
<div className="mt-6">
<div className="w-full bg-gray-700 rounded-full h-2">
<div
className="bg-gradient-to-r from-cyan-500 to-blue-500 h-2 rounded-full transition-all duration-500"
style={{ width: `${(progress.currentStep / progress.totalSteps) * 100}%` }}
/>
</div>
</div>
</div>
)}
{result && result.type === 'multi_timeframe' && (
<div className="mt-6 space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-bold text-white flex items-center">
<span className="w-6 h-6 bg-gradient-to-br from-purple-400 to-purple-600 rounded-lg flex items-center justify-center mr-2 text-sm">
📊
</span>
Multi-Timeframe Analysis
</h3>
<div className="text-xs text-gray-400">
{result.symbol} {result.results.length} timeframes
</div>
</div>
<div className="p-4 bg-gradient-to-r from-purple-800/30 to-purple-700/30 rounded-lg border border-purple-500/30">
<h4 className="text-sm font-semibold text-purple-300 mb-2">Analysis Summary</h4>
<p className="text-white text-sm">{result.summary}</p>
</div>
<div className="grid gap-4">
{result.results
.sort((a: any, b: any) => {
// Sort by timeframe order: 5m, 15m, 30m, 1h, 2h, 4h, 1D
const timeframeOrder: {[key: string]: number} = {
'5': 1, '5m': 1,
'15': 2, '15m': 2,
'30': 3, '30m': 3,
'60': 4, '1h': 4,
'120': 5, '2h': 5,
'240': 6, '4h': 6,
'D': 7, '1D': 7
}
const orderA = timeframeOrder[a.timeframe] || timeframeOrder[a.timeframeLabel] || 999
const orderB = timeframeOrder[b.timeframe] || timeframeOrder[b.timeframeLabel] || 999
return orderA - orderB
})
.map((timeframeResult: any, index: number) => (
<div key={index} className={`p-4 rounded-lg border ${
timeframeResult.success
? 'bg-green-500/5 border-green-500/30'
: 'bg-red-500/5 border-red-500/30'
}`}>
<div className="flex items-center justify-between mb-3">
<h5 className={`font-semibold ${
timeframeResult.success ? 'text-green-400' : 'text-red-400'
}`}>
{timeframeResult.success ? '✅' : '❌'} {timeframeResult.timeframeLabel} Timeframe
</h5>
<div className="flex items-center space-x-2">
{timeframeResult.success && timeframeResult.result.analysis && (
<button
onClick={() => handleTradeClick(timeframeResult)}
className="px-3 py-1 bg-gradient-to-r from-green-500 to-green-600 text-white text-xs font-medium rounded hover:from-green-600 hover:to-green-700 transition-all transform hover:scale-105"
>
💰 Trade
</button>
)}
<span className="text-xs text-gray-400">
{timeframeResult.success ? 'Analysis Complete' : 'Failed'}
</span>
</div>
</div>
{timeframeResult.success && timeframeResult.result.analysis && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div className="text-center p-3 bg-gray-800/30 rounded">
<div className="text-xs text-gray-400">Sentiment</div>
<div className="text-sm font-medium text-white">
{safeRender(timeframeResult.result.analysis.marketSentiment)}
</div>
</div>
<div className="text-center p-3 bg-gray-800/30 rounded">
<div className="text-xs text-gray-400">Recommendation</div>
<div className="text-sm font-medium text-white">
{safeRender(timeframeResult.result.analysis.recommendation)}
</div>
</div>
<div className="text-center p-3 bg-gray-800/30 rounded">
<div className="text-xs text-gray-400">Confidence</div>
<div className="text-sm font-medium text-white">
{safeRender(timeframeResult.result.analysis.confidence)}%
</div>
</div>
</div>
)}
{timeframeResult.success && timeframeResult.result.analysis?.entry && (
<div className="mt-3 p-3 bg-yellow-500/5 border border-yellow-500/20 rounded">
<div className="text-xs text-yellow-400 font-semibold mb-1">Entry Setup</div>
<div className="text-sm text-white">
📍 ${safeRender(timeframeResult.result.analysis.entry.price)}
{timeframeResult.result.analysis.entry.buffer && (
<span className="text-yellow-400 ml-1">
{safeRender(timeframeResult.result.analysis.entry.buffer)}
</span>
)}
</div>
{timeframeResult.result.analysis.stopLoss && (
<div className="text-sm text-red-300 mt-1">
🛑 SL: ${safeRender(timeframeResult.result.analysis.stopLoss.price)}
</div>
)}
{timeframeResult.result.analysis.takeProfits && (
<div className="text-sm text-green-300 mt-1 space-y-1">
{timeframeResult.result.analysis.takeProfits.tp1 && (
<div>
🎯 TP1: ${typeof timeframeResult.result.analysis.takeProfits.tp1.price !== 'undefined'
? safeRender(timeframeResult.result.analysis.takeProfits.tp1.price)
: safeRender(timeframeResult.result.analysis.takeProfits.tp1)}
</div>
)}
{timeframeResult.result.analysis.takeProfits.tp2 && (
<div>
🎯 TP2: ${typeof timeframeResult.result.analysis.takeProfits.tp2.price !== 'undefined'
? safeRender(timeframeResult.result.analysis.takeProfits.tp2.price)
: safeRender(timeframeResult.result.analysis.takeProfits.tp2)}
</div>
)}
{/* Fallback for simple take profit format */}
{!timeframeResult.result.analysis.takeProfits.tp1 && !timeframeResult.result.analysis.takeProfits.tp2 && (
<div>
🎯 TP: {typeof timeframeResult.result.analysis.takeProfits === 'object'
? Object.values(timeframeResult.result.analysis.takeProfits).map((tp: any) => `$${safeRender(tp)}`).join(', ')
: `$${safeRender(timeframeResult.result.analysis.takeProfits)}`}
</div>
)}
</div>
)}
</div>
)}
{!timeframeResult.success && (
<div className="text-red-300 text-sm">
Analysis failed for this timeframe
</div>
)}
</div>
))}
</div>
</div>
)}
{result && result.analysis && (
<div className="mt-6 space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-bold text-white flex items-center">
<span className="w-6 h-6 bg-gradient-to-br from-green-400 to-emerald-600 rounded-lg flex items-center justify-center mr-2 text-sm">
</span>
Analysis Complete
</h3>
<div className="flex items-center space-x-3">
{result.analysis && (
<button
onClick={() => handleTradeClick({ result, timeframeLabel: selectedTimeframes.map(tf => timeframes.find(t => t.value === tf)?.label).join(', ') })}
className="px-4 py-2 bg-gradient-to-r from-green-500 to-green-600 text-white text-sm font-medium rounded-lg hover:from-green-600 hover:to-green-700 transition-all transform hover:scale-105"
>
💰 Execute Trade
</button>
)}
{result.screenshots && (
<div className="text-xs text-gray-400">
Screenshots: {result.screenshots.length} captured
</div>
)}
</div>
</div>
<div className="grid gap-4">
{/* Summary */}
<div className="p-4 bg-gradient-to-r from-gray-800/50 to-gray-700/50 rounded-lg border border-gray-700">
<h4 className="text-sm font-semibold text-gray-300 mb-2 flex items-center">
<span className="w-4 h-4 bg-blue-500 rounded-full mr-2"></span>
Market Summary
</h4>
<p className="text-white text-sm leading-relaxed">{safeRender(result.analysis.summary)}</p>
</div>
{/* Key Metrics */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-4 bg-gradient-to-br from-green-500/10 to-emerald-500/10 border border-green-500/30 rounded-lg">
<h4 className="text-sm font-semibold text-green-400 mb-2">Market Sentiment</h4>
<p className="text-white font-medium">{safeRender(result.analysis.marketSentiment)}</p>
</div>
<div className="p-4 bg-gradient-to-br from-blue-500/10 to-cyan-500/10 border border-blue-500/30 rounded-lg">
<h4 className="text-sm font-semibold text-blue-400 mb-2">Recommendation</h4>
<p className="text-white font-medium">{safeRender(result.analysis.recommendation)}</p>
{result.analysis.confidence && (
<p className="text-cyan-300 text-xs mt-1">{safeRender(result.analysis.confidence)}% confidence</p>
)}
</div>
</div>
{/* Trading Levels */}
{result.analysis.keyLevels && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-4 bg-gradient-to-br from-red-500/10 to-rose-500/10 border border-red-500/30 rounded-lg">
<h4 className="text-sm font-semibold text-red-400 mb-2">Resistance Levels</h4>
<p className="text-red-300 font-mono text-sm">
{result.analysis.keyLevels.resistance?.join(', ') || 'None identified'}
</p>
</div>
<div className="p-4 bg-gradient-to-br from-green-500/10 to-emerald-500/10 border border-green-500/30 rounded-lg">
<h4 className="text-sm font-semibold text-green-400 mb-2">Support Levels</h4>
<p className="text-green-300 font-mono text-sm">
{result.analysis.keyLevels.support?.join(', ') || 'None identified'}
</p>
</div>
</div>
)}
{/* Trading Setup */}
{(result.analysis.entry || result.analysis.stopLoss || result.analysis.takeProfits) && (
<div className="p-4 bg-gradient-to-br from-purple-500/10 to-violet-500/10 border border-purple-500/30 rounded-lg">
<h4 className="text-sm font-semibold text-purple-400 mb-3">Trading Setup</h4>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
{result.analysis.entry && (
<div>
<span className="text-xs text-gray-400">Entry Point</span>
<p className="text-yellow-300 font-mono font-semibold">
📍 ${safeRender(result.analysis.entry.price || result.analysis.entry)}
{result.analysis.entry.buffer && (
<span className="text-yellow-400 text-xs ml-1">{safeRender(result.analysis.entry.buffer)}</span>
)}
</p>
{result.analysis.entry.rationale && (
<p className="text-xs text-gray-300 mt-1">💡 {safeRender(result.analysis.entry.rationale)}</p>
)}
</div>
)}
{result.analysis.stopLoss && (
<div>
<span className="text-xs text-gray-400">Stop Loss</span>
<p className="text-red-300 font-mono font-semibold">
🛑 ${safeRender(result.analysis.stopLoss.price || result.analysis.stopLoss)}
</p>
{result.analysis.stopLoss.rationale && (
<p className="text-xs text-gray-300 mt-1">💡 {safeRender(result.analysis.stopLoss.rationale)}</p>
)}
</div>
)}
{result.analysis.takeProfits && (
<div>
<span className="text-xs text-gray-400">Take Profit Targets</span>
<div className="space-y-2 mt-2">
{result.analysis.takeProfits.tp1 && (
<div className="p-2 bg-green-500/5 rounded border border-green-500/20">
<div className="flex items-center gap-2">
<span className="text-yellow-400">🥉</span>
<span className="text-green-300 font-mono font-semibold">
TP1: ${typeof result.analysis.takeProfits.tp1.price !== 'undefined'
? result.analysis.takeProfits.tp1.price
: safeRender(result.analysis.takeProfits.tp1)}
</span>
</div>
{result.analysis.takeProfits.tp1.description && (
<p className="text-xs text-green-200 mt-1">
📋 {safeRender(result.analysis.takeProfits.tp1.description)}
</p>
)}
{result.analysis.takeProfits.tp1.rsiExpectation && (
<p className="text-xs text-blue-200 mt-1">
📊 RSI: {safeRender(result.analysis.takeProfits.tp1.rsiExpectation)}
</p>
)}
{result.analysis.takeProfits.tp1.obvExpectation && (
<p className="text-xs text-purple-200 mt-1">
📈 OBV: {safeRender(result.analysis.takeProfits.tp1.obvExpectation)}
</p>
)}
</div>
)}
{result.analysis.takeProfits.tp2 && (
<div className="p-2 bg-green-500/5 rounded border border-green-500/20">
<div className="flex items-center gap-2">
<span className="text-gray-300">🥈</span>
<span className="text-green-300 font-mono font-semibold">
TP2: ${typeof result.analysis.takeProfits.tp2.price !== 'undefined'
? result.analysis.takeProfits.tp2.price
: safeRender(result.analysis.takeProfits.tp2)}
</span>
</div>
{result.analysis.takeProfits.tp2.description && (
<p className="text-xs text-green-200 mt-1">
📋 {safeRender(result.analysis.takeProfits.tp2.description)}
</p>
)}
{result.analysis.takeProfits.tp2.rsiExpectation && (
<p className="text-xs text-blue-200 mt-1">
📊 RSI: {safeRender(result.analysis.takeProfits.tp2.rsiExpectation)}
</p>
)}
{result.analysis.takeProfits.tp2.obvExpectation && (
<p className="text-xs text-purple-200 mt-1">
📈 OBV: {safeRender(result.analysis.takeProfits.tp2.obvExpectation)}
</p>
)}
</div>
)}
{/* Fallback for simple take profit format */}
{!result.analysis.takeProfits.tp1 && !result.analysis.takeProfits.tp2 && (
<p className="text-green-300 font-mono font-semibold">
{typeof result.analysis.takeProfits === 'object'
? Object.values(result.analysis.takeProfits).map(tp => `$${safeRender(tp)}`).join(', ')
: `$${safeRender(result.analysis.takeProfits)}`}
</p>
)}
</div>
</div>
)}
</div>
</div>
)}
{/* Risk Management & Confirmation */}
{(result.analysis.riskToReward || result.analysis.confirmationTrigger) && (
<div className="p-4 bg-gradient-to-br from-amber-500/10 to-orange-500/10 border border-amber-500/30 rounded-lg">
<h4 className="text-sm font-semibold text-amber-400 mb-3">Risk Management</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{result.analysis.riskToReward && (
<div>
<span className="text-xs text-amber-400">Risk/Reward Ratio</span>
<p className="text-amber-200 font-mono font-semibold"> {safeRender(result.analysis.riskToReward)}</p>
</div>
)}
{result.analysis.confirmationTrigger && (
<div>
<span className="text-xs text-amber-400">Confirmation Trigger</span>
<p className="text-amber-200 text-sm">🔔 {safeRender(result.analysis.confirmationTrigger)}</p>
</div>
)}
</div>
</div>
)}
{/* Timeframe Risk Assessment */}
{result.analysis.timeframeRisk && (
<div className="p-4 bg-gradient-to-br from-red-500/10 to-pink-500/10 border border-red-500/30 rounded-lg">
<h4 className="text-sm font-semibold text-red-400 mb-3"> Timeframe Risk Assessment</h4>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
{result.analysis.timeframeRisk.assessment && (
<div>
<span className="text-xs text-red-400">Risk Level</span>
<p className="text-red-200 font-semibold">📊 {safeRender(result.analysis.timeframeRisk.assessment)}</p>
</div>
)}
{result.analysis.timeframeRisk.positionSize && (
<div>
<span className="text-xs text-red-400">Position Size</span>
<p className="text-red-200 font-semibold">💼 {safeRender(result.analysis.timeframeRisk.positionSize)}</p>
</div>
)}
{result.analysis.timeframeRisk.leverageRecommendation && (
<div>
<span className="text-xs text-red-400">Leverage</span>
<p className="text-red-200 font-semibold">🎚 {safeRender(result.analysis.timeframeRisk.leverageRecommendation)}</p>
</div>
)}
</div>
</div>
)}
{/* Technical Indicators */}
{result.analysis.indicatorAnalysis && (
<div className="p-4 bg-gradient-to-br from-cyan-500/10 to-blue-500/10 border border-cyan-500/30 rounded-lg">
<h4 className="text-sm font-semibold text-cyan-400 mb-3">📈 Technical Indicators</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{result.analysis.indicatorAnalysis.rsi && (
<div className="p-2 bg-cyan-500/5 rounded border border-cyan-500/20">
<span className="text-xs text-cyan-400 font-semibold">📊 RSI</span>
<p className="text-cyan-200 text-xs mt-1">{safeRender(result.analysis.indicatorAnalysis.rsi)}</p>
</div>
)}
{result.analysis.indicatorAnalysis.vwap && (
<div className="p-2 bg-blue-500/5 rounded border border-blue-500/20">
<span className="text-xs text-blue-400 font-semibold">📈 VWAP</span>
<p className="text-blue-200 text-xs mt-1">{safeRender(result.analysis.indicatorAnalysis.vwap)}</p>
</div>
)}
{result.analysis.indicatorAnalysis.obv && (
<div className="p-2 bg-purple-500/5 rounded border border-purple-500/20">
<span className="text-xs text-purple-400 font-semibold">📊 OBV</span>
<p className="text-purple-200 text-xs mt-1">{safeRender(result.analysis.indicatorAnalysis.obv)}</p>
</div>
)}
{result.analysis.indicatorAnalysis.macd && (
<div className="p-2 bg-indigo-500/5 rounded border border-indigo-500/20">
<span className="text-xs text-indigo-400 font-semibold">📉 MACD</span>
<p className="text-indigo-200 text-xs mt-1">{safeRender(result.analysis.indicatorAnalysis.macd)}</p>
</div>
)}
</div>
</div>
)}
{/* Alternatives */}
{result.analysis.alternatives && (
<div className="p-4 bg-gradient-to-br from-violet-500/10 to-purple-500/10 border border-violet-500/30 rounded-lg">
<h4 className="text-sm font-semibold text-violet-400 mb-3">🔄 Alternative Strategies</h4>
<div className="space-y-2">
{result.analysis.alternatives.tigherStopOption && (
<div className="p-2 bg-violet-500/5 rounded border border-violet-500/20">
<span className="text-xs text-violet-400 font-semibold">🎯 Tighter Stop Option</span>
<p className="text-violet-200 text-xs mt-1">{safeRender(result.analysis.alternatives.tigherStopOption)}</p>
</div>
)}
{result.analysis.alternatives.scaledEntry && (
<div className="p-2 bg-purple-500/5 rounded border border-purple-500/20">
<span className="text-xs text-purple-400 font-semibold">📊 Scaled Entry</span>
<p className="text-purple-200 text-xs mt-1">{safeRender(result.analysis.alternatives.scaledEntry)}</p>
</div>
)}
{result.analysis.alternatives.invalidationScenario && (
<div className="p-2 bg-red-500/5 rounded border border-red-500/20">
<span className="text-xs text-red-400 font-semibold"> Invalidation Scenario</span>
<p className="text-red-200 text-xs mt-1">{safeRender(result.analysis.alternatives.invalidationScenario)}</p>
</div>
)}
</div>
</div>
)}
{/* Layout Comparison Section */}
{result.analysis.layoutComparison && (
<div className="p-4 bg-gradient-to-br from-indigo-500/10 to-blue-500/10 border border-indigo-500/30 rounded-lg">
<h4 className="text-sm font-semibold text-indigo-400 mb-3">Multi-Layout Analysis</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{result.analysis.layoutComparison.aiLayout && (
<div className="p-3 bg-blue-500/5 rounded border border-blue-500/20">
<span className="text-xs text-blue-400 font-semibold">AI Layout Insights</span>
<p className="text-xs text-blue-200 mt-1">{result.analysis.layoutComparison.aiLayout}</p>
</div>
)}
{result.analysis.layoutComparison.diyLayout && (
<div className="p-3 bg-green-500/5 rounded border border-green-500/20">
<span className="text-xs text-green-400 font-semibold">DIY Layout Insights</span>
<p className="text-xs text-green-200 mt-1">{result.analysis.layoutComparison.diyLayout}</p>
</div>
)}
</div>
{result.analysis.layoutComparison.consensus && (
<div className="mt-3 p-3 bg-emerald-500/5 rounded border border-emerald-500/20">
<span className="text-xs text-emerald-400 font-semibold">Layout Consensus</span>
<p className="text-xs text-emerald-200 mt-1">{result.analysis.layoutComparison.consensus}</p>
</div>
)}
{result.analysis.layoutComparison.divergences && (
<div className="mt-3 p-3 bg-amber-500/5 rounded border border-amber-500/20">
<span className="text-xs text-amber-400 font-semibold">Layout Divergences</span>
<p className="text-xs text-amber-200 mt-1">{result.analysis.layoutComparison.divergences}</p>
</div>
)}
</div>
)}
{/* Enhanced Indicator Analysis */}
{result.analysis.indicatorAnalysis?.crossLayoutConsensus && (
<div className="p-4 bg-gradient-to-br from-violet-500/10 to-purple-500/10 border border-violet-500/30 rounded-lg">
<h4 className="text-sm font-semibold text-violet-400 mb-2">Cross-Layout Consensus</h4>
<p className="text-violet-200 text-sm">{result.analysis.indicatorAnalysis.crossLayoutConsensus}</p>
</div>
)}
</div>
</div>
)}
{result && !result.analysis && result.screenshots && (
<div className="mt-6 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<h3 className="text-lg font-bold text-yellow-400 mb-2">Screenshots Captured</h3>
<p className="text-yellow-300 text-sm mb-2">
Screenshots were captured successfully, but AI analysis failed or was not requested.
</p>
<div className="text-xs text-gray-400">
Screenshots: {result.screenshots.length} captured
</div>
<div className="mt-2">
{result.screenshots.map((screenshot: string, index: number) => (
<div key={index} className="text-xs text-gray-500 font-mono">
{screenshot.split('/').pop()}
</div>
))}
</div>
</div>
)}
{/* Screenshot Gallery */}
{result && result.screenshots && (
<ScreenshotGallery
screenshots={result.screenshots}
symbol={symbol}
timeframes={selectedTimeframes.map(tf => timeframes.find(t => t.value === tf)?.label || tf)}
enlargedImage={enlargedScreenshot}
onImageClick={handleScreenshotClick}
onClose={() => setEnlargedScreenshot(null)}
/>
)}
{/* Multi-timeframe Screenshot Gallery */}
{result && result.type === 'multi_timeframe' && result.results && (
<ScreenshotGallery
screenshots={result.results
.filter((r: any) => r.success && r.result.screenshots)
.sort((a: any, b: any) => {
// Sort by timeframe order: 5m, 15m, 30m, 1h, 2h, 4h, 1D
const timeframeOrder: {[key: string]: number} = {
'5': 1, '5m': 1,
'15': 2, '15m': 2,
'30': 3, '30m': 3,
'60': 4, '1h': 4,
'120': 5, '2h': 5,
'240': 6, '4h': 6,
'D': 7, '1D': 7
}
const orderA = timeframeOrder[a.timeframe] || timeframeOrder[a.timeframeLabel] || 999
const orderB = timeframeOrder[b.timeframe] || timeframeOrder[b.timeframeLabel] || 999
return orderA - orderB
})
.flatMap((r: any) => r.result.screenshots)}
symbol={symbol}
timeframes={result.results
.filter((r: any) => r.success)
.sort((a: any, b: any) => {
// Sort by timeframe order: 5m, 15m, 30m, 1h, 2h, 4h, 1D
const timeframeOrder: {[key: string]: number} = {
'5': 1, '5m': 1,
'15': 2, '15m': 2,
'30': 3, '30m': 3,
'60': 4, '1h': 4,
'120': 5, '2h': 5,
'240': 6, '4h': 6,
'D': 7, '1D': 7
}
const orderA = timeframeOrder[a.timeframe] || timeframeOrder[a.timeframeLabel] || 999
const orderB = timeframeOrder[b.timeframe] || timeframeOrder[b.timeframeLabel] || 999
return orderA - orderB
})
.map((r: any) => r.timeframeLabel)}
enlargedImage={enlargedScreenshot}
onImageClick={handleScreenshotClick}
onClose={() => setEnlargedScreenshot(null)}
/>
)}
{/* Trade Modal */}
{console.log('🔥 About to render TradeModal with:', {
isOpen: tradeModalOpen,
tradeData: tradeModalData
})}
<TradeModal
isOpen={tradeModalOpen}
onClose={() => {
console.log('🔥 TradeModal onClose called')
setTradeModalOpen(false)
}}
tradeData={tradeModalData}
onExecute={executeTrade}
/>
</div>
)
}