fix: timeframe handling and progress tracking improvements
- Fix timeframe parameter handling in enhanced-screenshot API route - Support both 'timeframe' (singular) and 'timeframes' (array) parameters - Add proper sessionId propagation for real-time progress tracking - Enhance MACD analysis prompt with detailed crossover definitions - Add progress tracker service with Server-Sent Events support - Fix Next.js build errors in chart components (module variable conflicts) - Change dev environment port from 9000:3000 to 9001:3000 - Improve AI analysis layout detection logic - Add comprehensive progress tracking through all service layers
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"use client"
|
||||
import React, { useState } from 'react'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import TradeModal from './TradeModal'
|
||||
import ScreenshotGallery from './ScreenshotGallery'
|
||||
|
||||
@@ -38,6 +38,7 @@ interface ProgressStep {
|
||||
}
|
||||
|
||||
interface AnalysisProgress {
|
||||
sessionId: string
|
||||
currentStep: number
|
||||
totalSteps: number
|
||||
steps: ProgressStep[]
|
||||
@@ -60,6 +61,7 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
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)
|
||||
@@ -77,6 +79,47 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
return String(value)
|
||||
}
|
||||
|
||||
// Real-time progress tracking
|
||||
const startProgressTracking = (sessionId: string) => {
|
||||
// Close existing connection
|
||||
if (eventSource) {
|
||||
eventSource.close()
|
||||
}
|
||||
|
||||
const es = new EventSource(`/api/progress/${sessionId}/stream`)
|
||||
|
||||
es.onmessage = (event) => {
|
||||
try {
|
||||
const progressData = JSON.parse(event.data)
|
||||
if (progressData.type === 'complete') {
|
||||
es.close()
|
||||
setEventSource(null)
|
||||
} else {
|
||||
setProgress(progressData)
|
||||
}
|
||||
} 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])
|
||||
|
||||
const toggleLayout = (layout: string) => {
|
||||
setSelectedLayouts(prev =>
|
||||
prev.includes(layout)
|
||||
@@ -93,104 +136,11 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
)
|
||||
}
|
||||
|
||||
// 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'
|
||||
})
|
||||
}
|
||||
// Helper function to create initial progress steps (no longer used - using real-time progress)
|
||||
// const createProgressSteps = ...removed for real-time implementation
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
// Helper function to update progress (no longer used - using real-time progress)
|
||||
// const updateProgress = ...removed for real-time implementation
|
||||
|
||||
const performAnalysis = async (analysisSymbol = symbol, analysisTimeframes = selectedTimeframes) => {
|
||||
if (loading || selectedLayouts.length === 0 || analysisTimeframes.length === 0) return
|
||||
@@ -198,13 +148,51 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
setResult(null)
|
||||
|
||||
// Initialize progress tracking
|
||||
const steps = createProgressSteps(analysisTimeframes, selectedLayouts)
|
||||
|
||||
// Set initial progress state to show animation immediately
|
||||
setProgress({
|
||||
currentStep: 0,
|
||||
totalSteps: steps.length,
|
||||
steps,
|
||||
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
|
||||
@@ -212,14 +200,8 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
})
|
||||
|
||||
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...')
|
||||
|
||||
// Single timeframe analysis with real-time progress
|
||||
const response = await fetch('/api/enhanced-screenshot', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -231,35 +213,16 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
})
|
||||
})
|
||||
|
||||
// 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!')
|
||||
// Start real-time progress tracking if sessionId is provided
|
||||
if (data.sessionId) {
|
||||
startProgressTracking(data.sessionId)
|
||||
}
|
||||
|
||||
setResult(data)
|
||||
|
||||
@@ -269,31 +232,14 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
}
|
||||
} 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' },
|
||||
@@ -305,21 +251,6 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
})
|
||||
})
|
||||
|
||||
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,
|
||||
@@ -327,18 +258,26 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
success: response.ok,
|
||||
result
|
||||
})
|
||||
|
||||
// Start progress tracking for the first timeframe session
|
||||
if (i === 0 && result.sessionId) {
|
||||
startProgressTracking(result.sessionId)
|
||||
}
|
||||
|
||||
updateProgress('analysis', 'active', `Processing ${timeframeLabel} - Running AI analysis...`)
|
||||
// Update timeframe progress manually for multi-timeframe
|
||||
setProgress(prev => prev ? {
|
||||
...prev,
|
||||
timeframeProgress: {
|
||||
current: i + 1,
|
||||
total: analysisTimeframes.length,
|
||||
currentTimeframe: timeframeLabel
|
||||
}
|
||||
} : null)
|
||||
|
||||
// Small delay between requests
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -493,7 +432,7 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
const isLeveraged = leverage > 1
|
||||
|
||||
// Route to appropriate API based on leverage
|
||||
const apiEndpoint = isLeveraged ? '/api/trading/execute-perp' : '/api/trading/execute-dex'
|
||||
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}`)
|
||||
@@ -522,20 +461,27 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
// Show detailed success message based on trading type
|
||||
const leverage = parseFloat(tradeData.leverage) || 1
|
||||
const isLeveraged = leverage > 1
|
||||
const tradeType = isLeveraged ? 'Leveraged Position' : 'Spot Trade'
|
||||
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\n`
|
||||
message += `🏪 Platform: ${platform}\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}\n`
|
||||
if (tradeData.tp1) message += `🎯 Take Profit: $${tradeData.tp1}\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.trade?.monitoring || result.position) {
|
||||
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`
|
||||
}
|
||||
|
||||
@@ -550,6 +496,8 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
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}`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user