feat: Add detailed progress tracking to AI analysis

- Real-time progress tracking with 7 detailed steps
- Visual progress indicators with status icons and timing
- Multi-timeframe analysis progress with current timeframe display
- Step-by-step breakdown: Init → Browser → Auth → Navigation → Loading → Capture → Analysis
- Individual step timing and status (pending, active, completed, error)
- Overall progress percentage and progress bars
- Better visual feedback with color-coded status indicators

- Users can now see exactly what's happening in the background
- Clear indication of current step and estimated completion
- Separate progress tracking for multi-timeframe analysis
- Error handling with specific step failure details

- Animated progress indicators and status changes
- Gradient backgrounds and modern design
- Real-time step duration tracking
- Responsive layout for all screen sizes

No more wondering 'how long will this take?' - users now have full visibility!
This commit is contained in:
mindesbunister
2025-07-13 23:36:58 +02:00
parent f6e9c2eaa7
commit 368e4e43e0

View File

@@ -24,6 +24,28 @@ const popularCoins = [
{ 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
}
}
export default function AIAnalysisPanel() {
const [symbol, setSymbol] = useState('BTCUSD')
const [selectedLayouts, setSelectedLayouts] = useState<string[]>(['ai', 'diy']) // Default to both AI and DIY
@@ -31,6 +53,7 @@ export default function AIAnalysisPanel() {
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)
// Helper function to safely render any value
const safeRender = (value: any): string => {
@@ -59,6 +82,105 @@ export default function AIAnalysisPanel() {
)
}
// 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
@@ -66,9 +188,27 @@ export default function AIAnalysisPanel() {
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' },
@@ -80,19 +220,63 @@ export default function AIAnalysisPanel() {
})
})
// 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)
} else {
// Multiple timeframe analysis
await new Promise(resolve => setTimeout(resolve, 500))
updateProgress('init', 'completed', `Starting analysis for ${analysisTimeframes.length} timeframes`)
const results = []
for (const tf of analysisTimeframes) {
console.log(`🧪 Analyzing timeframe: ${timeframes.find(t => t.value === tf)?.label || tf}`)
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',
@@ -105,18 +289,40 @@ export default function AIAnalysisPanel() {
})
})
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: timeframes.find(t => t.value === tf)?.label || 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!`)
setResult({
type: 'multi_timeframe',
symbol: analysisSymbol,
@@ -125,7 +331,25 @@ export default function AIAnalysisPanel() {
})
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to perform analysis')
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)
}
@@ -441,18 +665,138 @@ export default function AIAnalysisPanel() {
</div>
)}
{loading && (
<div className="mt-6 p-4 bg-cyan-500/10 border border-cyan-500/30 rounded-lg">
<div className="flex items-center space-x-3">
<div className="spinner border-cyan-500"></div>
<div>
<h4 className="text-cyan-400 font-medium text-sm">AI Processing</h4>
<p className="text-cyan-300 text-xs mt-1 opacity-90">
Analyzing {symbol} on {selectedTimeframes.length === 1
? `${timeframes.find(tf => tf.value === selectedTimeframes[0])?.label} timeframe`
: `${selectedTimeframes.length} timeframes`
}...
</p>
{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>