Files
trading_bot_v3/components/AIAnalysisPanel.tsx
mindesbunister 348de31ac5 feat: Add 'Mark as Traded' functionality for manual trade tracking
- Add markAsTraded() function to create virtual positions for follow-up analysis
- Enable follow-up assistant for manually executed trades on external platforms
- Add blue 'Mark Traded' buttons next to existing green 'Trade' buttons
- Prompt for trade amount and confirm entry price before creating position
- Automatically store analysis data (entry, SL, TP) with virtual position
- Optional direct access to Trade Follow-up Assistant after marking
- Solves issue where manual Drift trades couldn't use follow-up analysis
- Maintains existing automated trade execution functionality
2025-07-17 16:06:32 +02:00

1651 lines
74 KiB
TypeScript
Raw Permalink 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, useRef } from 'react'
import TradeModal from './TradeModal'
import ScreenshotGallery from './ScreenshotGallery'
import TradeFollowUpPanel from './TradeFollowUpPanel'
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)
// Ref to prevent concurrent analysis calls
const analysisInProgress = useRef(false)
const [tradeModalOpen, setTradeModalOpen] = useState(false)
const [tradeModalData, setTradeModalData] = useState<any>(null)
const [followUpPanelOpen, setFollowUpPanelOpen] = useState(false)
// 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) => {
// Prevent concurrent analysis calls
if (loading || analysisInProgress.current || selectedLayouts.length === 0 || analysisTimeframes.length === 0) {
console.log('⚠️ Analysis blocked:', { loading, analysisInProgress: analysisInProgress.current, selectedLayouts: selectedLayouts.length, analysisTimeframes: analysisTimeframes.length })
return
}
analysisInProgress.current = true
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)
// Clear loading and progress state after successful single timeframe analysis
setLoading(false)
setProgress(null)
// Call the callback with analysis result if provided
if (onAnalysisComplete && data.analysis) {
onAnalysisComplete(data.analysis, analysisSymbol)
}
} else {
// Multiple timeframe analysis - use batch comparative analysis
console.log(`🎯 Starting batch comparative analysis for ${analysisTimeframes.length} timeframes`)
// Pre-generate sessionId for batch analysis
const sessionId = `batch_analysis_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
startProgressTracking(sessionId)
const response = await fetch('/api/batch-analysis', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol: analysisSymbol,
timeframes: analysisTimeframes,
layouts: selectedLayouts,
sessionId: sessionId
})
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Batch analysis failed')
}
console.log(`✅ Batch analysis completed: ${data.totalScreenshots} screenshots, ${data.timeframes.length} timeframes`)
// Transform batch result to match expected format for UI compatibility
const multiResult = {
type: 'batch_comparative',
symbol: analysisSymbol,
summary: data.summary || `Batch comparative analysis of ${data.timeframes?.length || analysisTimeframes.length} timeframes`,
analysis: data.analysis,
screenshots: data.screenshots,
timeframeBreakdown: data.timeframeBreakdown,
totalScreenshots: data.totalScreenshots,
timeframes: analysisTimeframes.map(tf => timeframes.find(t => t.value === tf)?.label || tf),
// Create results array for UI compatibility
results: analysisTimeframes.map(tf => ({
timeframe: tf,
timeframeLabel: timeframes.find(t => t.value === tf)?.label || tf,
success: true,
result: {
analysis: data.analysis, // Single comparative analysis for all timeframes
screenshots: data.screenshots.filter((s: string) => s.includes(`_${tf}_`)),
sessionId: sessionId
}
}))
}
setResult(multiResult)
// Clear loading and progress state after successful batch analysis
setLoading(false)
setProgress(null)
// Call the callback with analysis result if provided
if (onAnalysisComplete && data.analysis) {
onAnalysisComplete(data.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 {
// Always reset concurrency flag
analysisInProgress.current = false
}
}
const quickAnalyze = async (coinSymbol: string, quickTimeframes = selectedTimeframes) => {
setSymbol(coinSymbol)
if (!loading && !analysisInProgress.current) {
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 && !analysisInProgress.current && symbol && newTimeframes.length > 0) {
await performAnalysis(symbol, newTimeframes)
}
}
const testAllTimeframes = async () => {
if (loading || analysisInProgress.current) return
const allTimeframeValues = timeframes.map(tf => tf.value)
setSelectedTimeframes(allTimeframeValues)
if (!loading && !analysisInProgress.current && symbol) {
await performAnalysis(symbol, allTimeframeValues)
}
}
async function handleAnalyze() {
if (!analysisInProgress.current) {
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)
}
// Manual trade marking for follow-up (when trading manually on external platforms)
const markAsTraded = async (analysisData: any) => {
try {
console.log('📋 Marking analysis as manually traded for follow-up:', analysisData)
// Extract trade data from analysis
const analysis = analysisData.result?.analysis || analysisData.analysis
if (!analysis) {
alert('❌ No analysis data found to mark as traded')
return
}
// Create virtual position for follow-up tracking
const entryPrice = analysis.entry?.price || 0
const stopLoss = analysis.stopLoss?.price || null
const takeProfit = analysis.takeProfits?.tp1?.price || null
const side = analysis.recommendation === 'BUY' ? 'LONG' : analysis.recommendation === 'SELL' ? 'SHORT' : 'LONG'
if (!entryPrice) {
alert('❌ No entry price found in analysis')
return
}
// Prompt for trade details
const amount = prompt('Enter trade amount (size):')
if (!amount || parseFloat(amount) <= 0) return
const actualEntryPrice = prompt(`Confirm entry price (from analysis: $${entryPrice}):`, entryPrice.toString())
if (!actualEntryPrice) return
const response = await fetch('/api/trading/positions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'add',
symbol: symbol,
side: side,
amount: parseFloat(amount),
entryPrice: parseFloat(actualEntryPrice),
leverage: 1,
stopLoss: stopLoss,
takeProfit: takeProfit,
txId: `manual_${Date.now()}`,
notes: `Manual trade marked from AI analysis - ${analysis.summary?.substring(0, 100) || 'AI trading setup'}`
})
})
const result = await response.json()
if (result.success) {
alert(`✅ Position marked as traded!\n\n• ${side} ${amount} ${symbol}\n• Entry: $${actualEntryPrice}\n• Follow-up assistant now available`)
// Optional: Show follow-up panel
if (confirm('Open Trade Follow-up Assistant now?')) {
setFollowUpPanelOpen(true)
}
} else {
throw new Error(result.error || 'Failed to mark trade')
}
} catch (error) {
console.error('Error marking trade:', error)
alert(`❌ Failed to mark trade: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}
// 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">
{/* Analysis Controls Area */}
</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 || analysisInProgress.current)
? 'bg-gray-700 text-gray-400 cursor-not-allowed'
: 'btn-primary transform hover:scale-[1.02] active:scale-[0.98]'
}`}
onClick={handleAnalyze}
disabled={loading || analysisInProgress.current}
>
{(loading || analysisInProgress.current) ? (
<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>
{/* Trade Follow-up Button */}
<button
className="w-full py-3 px-6 rounded-lg font-semibold transition-all duration-300 bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 text-white transform hover:scale-[1.02] active:scale-[0.98] border border-green-500/30"
onClick={() => setFollowUpPanelOpen(true)}
>
<div className="flex items-center justify-center space-x-2">
<span>💬</span>
<span>Trade Follow-up Assistant</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' || result.type === 'batch_comparative') && (
<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>
<button
onClick={() => markAsTraded(timeframeResult)}
className="px-3 py-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white text-xs font-medium rounded hover:from-blue-600 hover:to-blue-700 transition-all transform hover:scale-105"
title="Mark as manually traded for follow-up analysis"
>
📋 Mark Traded
</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>
<button
onClick={() => markAsTraded({ result, timeframeLabel: selectedTimeframes.map(tf => timeframes.find(t => t.value === tf)?.label).join(', ') })}
className="px-4 py-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white text-sm font-medium rounded-lg hover:from-blue-600 hover:to-blue-700 transition-all transform hover:scale-105"
title="Mark as manually traded for follow-up analysis"
>
📋 Mark as Traded
</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 - Single Analysis Only */}
{result && result.screenshots && !(result.type === 'multi_timeframe' || result.type === 'batch_comparative') && (
<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.type === 'batch_comparative') && (
<ScreenshotGallery
screenshots={
result.type === 'batch_comparative' && result.screenshots
? result.screenshots // Use direct screenshots array for batch analysis
: result.results // Use nested structure for legacy multi-timeframe
?.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.type === 'batch_comparative' && result.timeframes
? result.timeframes // Use direct timeframes array for batch analysis
: result.results // Use nested structure for legacy multi-timeframe
?.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}
/>
{/* Trade Follow-up Panel */}
{followUpPanelOpen && (
<TradeFollowUpPanel
onClose={() => setFollowUpPanelOpen(false)}
/>
)}
</div>
)
}