Files
trading_bot_v3/components/AIAnalysisPanel.tsx
mindesbunister ac50d9622c Enhance trading system: real wallet validation, auto-discovery, and hot reloading
- Update trade validation to use real wallet balances from /api/wallet/balance
- Enhance wallet API to auto-discover all major SPL tokens (USDC, USDT, etc.)
- Improve AIAnalysisPanel to better extract and pass AI values to TradeModal
- Configure Docker Compose for hot reloading with proper volume mounts
- Remove hardcoded balance fallbacks in favor of live wallet data

Result: Trading validation now uses accurate real-time wallet balances
2025-07-16 11:37:20 +02:00

1488 lines
66 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 } 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())
const timeframes = [
{ label: '1m', value: '1' },
{ label: '5m', value: '5' },
{ label: '15m', value: '15' },
{ label: '1h', value: '60' },
{ label: '4h', value: '240' },
{ label: '1d', value: 'D' },
{ label: '1w', value: 'W' },
{ label: '1M', value: 'M' },
]
const popularCoins = [
{ name: 'Bitcoin', symbol: 'BTCUSD', icon: '₿', color: 'from-orange-400 to-orange-600' },
{ name: 'Ethereum', symbol: 'ETHUSD', icon: 'Ξ', color: 'from-blue-400 to-blue-600' },
{ name: 'Solana', symbol: 'SOLUSD', icon: '◎', color: 'from-purple-400 to-purple-600' },
{ name: 'Sui', symbol: 'SUIUSD', icon: '🔷', color: 'from-cyan-400 to-cyan-600' },
{ name: 'Avalanche', symbol: 'AVAXUSD', icon: '🔺', color: 'from-red-400 to-red-600' },
{ name: 'Cardano', symbol: 'ADAUSD', icon: '♠', color: 'from-indigo-400 to-indigo-600' },
{ name: 'Polygon', symbol: 'MATICUSD', icon: '🔷', color: 'from-violet-400 to-violet-600' },
{ name: 'Chainlink', symbol: 'LINKUSD', icon: '🔗', color: 'from-blue-400 to-blue-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 {
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']) // Default to both AI and DIY
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 [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)
}
const toggleLayout = (layout: string) => {
setSelectedLayouts(prev =>
prev.includes(layout)
? prev.filter(l => l !== layout)
: [...prev, layout]
)
}
const toggleTimeframe = (timeframe: string) => {
setSelectedTimeframes(prev =>
prev.includes(timeframe)
? prev.filter(tf => tf !== timeframe)
: [...prev, timeframe]
)
}
// Helper function to create initial progress steps
const createProgressSteps = (timeframes: string[], layouts: string[]): ProgressStep[] => {
const steps: ProgressStep[] = []
if (timeframes.length > 1) {
steps.push({
id: 'init',
title: 'Initializing Multi-Timeframe Analysis',
description: `Preparing to analyze ${timeframes.length} timeframes`,
status: 'pending'
})
} else {
steps.push({
id: 'init',
title: 'Initializing Analysis',
description: `Setting up screenshot service for ${layouts.join(', ')} layout(s)`,
status: 'pending'
})
}
steps.push({
id: 'browser',
title: 'Starting Browser Sessions',
description: `Launching ${layouts.length} browser session(s)`,
status: 'pending'
})
steps.push({
id: 'auth',
title: 'TradingView Authentication',
description: 'Logging into TradingView accounts',
status: 'pending'
})
steps.push({
id: 'navigation',
title: 'Chart Navigation',
description: 'Navigating to chart layouts and timeframes',
status: 'pending'
})
steps.push({
id: 'loading',
title: 'Chart Data Loading',
description: 'Waiting for chart data and indicators to load',
status: 'pending'
})
steps.push({
id: 'capture',
title: 'Screenshot Capture',
description: 'Capturing high-quality chart screenshots',
status: 'pending'
})
steps.push({
id: 'analysis',
title: 'AI Analysis',
description: 'Analyzing screenshots with AI for trading insights',
status: 'pending'
})
return steps
}
// Helper function to update progress
const updateProgress = (stepId: string, status: ProgressStep['status'], details?: string) => {
setProgress(prev => {
if (!prev) return null
const updatedSteps = prev.steps.map(step => {
if (step.id === stepId) {
const updatedStep = {
...step,
status,
details: details || step.details
}
if (status === 'active' && !step.startTime) {
updatedStep.startTime = Date.now()
} else if ((status === 'completed' || status === 'error') && !step.endTime) {
updatedStep.endTime = Date.now()
}
return updatedStep
}
return step
})
const currentStepIndex = updatedSteps.findIndex(step => step.status === 'active')
return {
...prev,
steps: updatedSteps,
currentStep: currentStepIndex >= 0 ? currentStepIndex + 1 : prev.currentStep
}
})
}
const performAnalysis = async (analysisSymbol = symbol, analysisTimeframes = selectedTimeframes) => {
if (loading || selectedLayouts.length === 0 || analysisTimeframes.length === 0) return
setLoading(true)
setError(null)
setResult(null)
// Initialize progress tracking
const steps = createProgressSteps(analysisTimeframes, selectedLayouts)
setProgress({
currentStep: 0,
totalSteps: steps.length,
steps,
timeframeProgress: analysisTimeframes.length > 1 ? {
current: 0,
total: analysisTimeframes.length
} : undefined
})
try {
updateProgress('init', 'active')
if (analysisTimeframes.length === 1) {
// Single timeframe analysis
await new Promise(resolve => setTimeout(resolve, 500)) // Brief pause for UI
updateProgress('init', 'completed')
updateProgress('browser', 'active', 'Starting browser session...')
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
})
})
// Since we can't track internal API progress in real-time, we'll simulate logical progression
await new Promise(resolve => setTimeout(resolve, 1000))
updateProgress('browser', 'completed')
updateProgress('auth', 'active', 'Authenticating with TradingView...')
await new Promise(resolve => setTimeout(resolve, 2000))
updateProgress('auth', 'completed')
updateProgress('navigation', 'active', `Navigating to ${analysisSymbol} chart...`)
await new Promise(resolve => setTimeout(resolve, 2000))
updateProgress('navigation', 'completed')
updateProgress('loading', 'active', 'Loading chart data and indicators...')
await new Promise(resolve => setTimeout(resolve, 3000))
updateProgress('loading', 'completed')
updateProgress('capture', 'active', 'Capturing screenshots...')
const data = await response.json()
if (!response.ok) {
updateProgress('capture', 'error', data.error || 'Screenshot capture failed')
throw new Error(data.error || 'Analysis failed')
}
updateProgress('capture', 'completed', `Captured ${data.screenshots?.length || 0} screenshot(s)`)
updateProgress('analysis', 'active', 'Running AI analysis...')
await new Promise(resolve => setTimeout(resolve, 1000))
updateProgress('analysis', 'completed', 'Analysis complete!')
setResult(data)
// Call the callback with analysis result if provided
if (onAnalysisComplete && data.analysis) {
onAnalysisComplete(data.analysis, analysisSymbol)
}
} else {
// Multiple timeframe analysis
await new Promise(resolve => setTimeout(resolve, 500))
updateProgress('init', 'completed', `Starting analysis for ${analysisTimeframes.length} timeframes`)
const results = []
for (let i = 0; i < analysisTimeframes.length; i++) {
const tf = analysisTimeframes[i]
const timeframeLabel = timeframes.find(t => t.value === tf)?.label || tf
// Update timeframe progress
setProgress(prev => prev ? {
...prev,
timeframeProgress: {
...prev.timeframeProgress!,
current: i + 1,
currentTimeframe: timeframeLabel
}
} : null)
console.log(`🧪 Analyzing timeframe: ${timeframeLabel}`)
if (i === 0) {
updateProgress('browser', 'active', `Processing ${timeframeLabel} - Starting browser...`)
}
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
})
})
if (i === 0) {
updateProgress('browser', 'completed')
updateProgress('auth', 'active', `Processing ${timeframeLabel} - Authenticating...`)
await new Promise(resolve => setTimeout(resolve, 1000))
updateProgress('auth', 'completed')
}
updateProgress('navigation', 'active', `Processing ${timeframeLabel} - Navigating to chart...`)
await new Promise(resolve => setTimeout(resolve, 1000))
updateProgress('loading', 'active', `Processing ${timeframeLabel} - Loading chart data...`)
await new Promise(resolve => setTimeout(resolve, 1500))
updateProgress('capture', 'active', `Processing ${timeframeLabel} - Capturing screenshots...`)
const result = await response.json()
results.push({
timeframe: tf,
timeframeLabel,
success: response.ok,
result
})
updateProgress('analysis', 'active', `Processing ${timeframeLabel} - Running AI analysis...`)
// Small delay between requests
await new Promise(resolve => setTimeout(resolve, 2000))
}
updateProgress('navigation', 'completed')
updateProgress('loading', 'completed')
updateProgress('capture', 'completed', `Captured screenshots for all ${analysisTimeframes.length} timeframes`)
updateProgress('analysis', 'completed', `Completed analysis for all timeframes!`)
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)
// 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 {
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 {
// Use real DEX trading for manual trades
const response = await fetch('/api/trading/execute-dex', {
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),
stopLoss: parseFloat(tradeData.sl),
takeProfit: parseFloat(tradeData.tp1), // Use TP1 as primary target
useRealDEX: true, // Enable real trading for manual execution
tradingPair: `${tradeData.symbol || symbol}/USDC`,
quickSwap: false
})
})
const result = await response.json()
if (response.ok && result.success) {
// Show detailed success message for DEX execution
let message = `✅ Real DEX Trade 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}\n`
message += `🏪 DEX: ${result.trade?.dex || 'Jupiter'}\n`
if (tradeData.sl) message += `🛑 Stop Loss: $${tradeData.sl}\n`
if (tradeData.tp1) message += `🎯 Take Profit: $${tradeData.tp1}\n`
if (result.trade?.monitoring) {
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 {
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', '60'])}
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, 1h)
</button>
<button
onClick={() => setSelectedTimeframes(['60', '240', 'D'])}
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, 4h, 1d)
</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>
{/* Coin Selection */}
<div className="grid grid-cols-2 md:grid-cols-4 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`}>
<span className="text-white font-bold text-sm">{coin.icon}</span>
</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>
{/* Timeframe Selection */}
<div className="mb-6">
<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>
{/* 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 => (
<label key={layout} className="group relative">
<input
type="checkbox"
checked={selectedLayouts.includes(layout)}
onChange={() => toggleLayout(layout)}
className="sr-only"
/>
<div className={`flex items-center p-3 rounded-lg border cursor-pointer transition-all ${
selectedLayouts.includes(layout)
? '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 ${
selectedLayouts.includes(layout)
? 'border-cyan-500 bg-cyan-500'
: 'border-gray-600'
}`}>
{selectedLayouts.includes(layout) && (
<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">{layout}</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.join(', ')}</span>
</div>
</div>
)}
</div>
{/* Quick Timeframe Actions */}
<div className="mb-6">
<label className="block text-xs font-medium text-gray-400 mb-3">Quick Actions</label>
<div className="grid grid-cols-4 gap-2 mb-3">
{timeframes.map(tf => (
<button
key={tf.value}
onClick={() => quickTimeframeTest(tf.value)}
disabled={loading || selectedLayouts.length === 0}
className={`py-2 px-3 rounded-lg text-xs font-medium transition-all ${
selectedTimeframes.includes(tf.value)
? 'bg-cyan-500 text-white shadow-lg'
: loading
? 'bg-gray-700 text-gray-500 cursor-not-allowed'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600 hover:text-white transform hover:scale-105'
}`}
>
{tf.label}
</button>
))}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<button
onClick={testAllTimeframes}
disabled={loading || selectedLayouts.length === 0 || !symbol}
className={`py-2 px-4 rounded-lg text-sm font-medium transition-all ${
loading
? 'bg-gray-700 text-gray-500 cursor-not-allowed'
: 'bg-gradient-to-r from-purple-500 to-pink-500 text-white hover:from-purple-600 hover:to-pink-600 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg'
}`}
>
🔍 Analyze All Timeframes
</button>
<button
onClick={() => setSelectedTimeframes([])}
disabled={loading || selectedTimeframes.length === 0}
className={`py-2 px-4 rounded-lg text-sm font-medium transition-all ${
loading || selectedTimeframes.length === 0
? 'bg-gray-700 text-gray-500 cursor-not-allowed'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600 hover:text-white transform hover:scale-[1.02] active:scale-[0.98]'
}`}
>
🗑 Clear Selection
</button>
</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.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.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).flatMap((r: any) => r.result.screenshots)}
symbol={symbol}
timeframes={result.results.filter((r: any) => r.success).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>
)
}