Files
trading_bot_v3/components/AIAnalysisPanel.tsx
mindesbunister 45202cabe7 🚀 Major optimization: Dual-session screenshot service + Docker build speed improvements
 Key Achievements:
- Fixed DIY module screenshot failures - now works 100%
- Optimized Docker builds for i7-4790K (4 cores/8 threads)
- Implemented true parallel dual-session screenshot capture
- Enhanced error diagnostics and navigation timeout handling

🔧 Technical Improvements:
- Enhanced screenshot service with robust parallel session management
- Optimized navigation with 90s timeout and domcontentloaded strategy
- Added comprehensive error handling with browser state capture
- Docker build optimizations: 8-thread npm installs, parallel downloads
- Improved layer caching and reduced build context
- Added fast-build.sh script for optimal CPU utilization

📸 Screenshot Service:
- Parallel AI + DIY module capture working flawlessly
- Enhanced error reporting for debugging navigation issues
- Improved chart loading detection and retry logic
- Better session cleanup and resource management

🐳 Docker Optimizations:
- CPU usage increased from 40% to 80-90% during builds
- Build time reduced from 5-10min to 2-3min
- Better caching and parallel package installation
- Optimized .dockerignore for faster build context

🧪 Testing Infrastructure:
- API-driven test scripts for Docker compatibility
- Enhanced monitoring and diagnostic tools
- Comprehensive error logging and debugging

Ready for AI analysis integration fixes next.
2025-07-13 17:26:49 +02:00

464 lines
19 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'
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' },
]
export default function AIAnalysisPanel() {
const [symbol, setSymbol] = useState('BTCUSD')
const [selectedLayouts, setSelectedLayouts] = useState<string[]>([layouts[0]])
const [timeframe, setTimeframe] = useState('60')
const [loading, setLoading] = useState(false)
const [result, setResult] = useState<any>(null)
const [error, setError] = useState<string | null>(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 performAnalysis = async (analysisSymbol = symbol, analysisTimeframe = timeframe) => {
if (loading || selectedLayouts.length === 0) return
setLoading(true)
setError(null)
setResult(null)
try {
const response = await fetch('/api/enhanced-screenshot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol: analysisSymbol,
timeframe: analysisTimeframe,
layouts: selectedLayouts
})
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Analysis failed')
}
setResult(data)
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to perform analysis')
} finally {
setLoading(false)
}
}
const quickAnalyze = async (coinSymbol: string) => {
setSymbol(coinSymbol)
if (!loading) {
await performAnalysis(coinSymbol)
}
}
const quickTimeframeTest = async (testTimeframe: string) => {
setTimeframe(testTimeframe)
if (!loading && symbol) {
await performAnalysis(symbol, testTimeframe)
}
}
const testAllTimeframes = async () => {
if (loading) return
setLoading(true)
setError(null)
const results = []
try {
for (const tf of timeframes) {
console.log(`🧪 Testing timeframe: ${tf.label}`)
setTimeframe(tf.value)
const response = await fetch('/api/enhanced-screenshot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol,
timeframe: tf.value,
layouts: selectedLayouts
})
})
const result = await response.json()
results.push({ timeframe: tf.label, success: response.ok, result })
// Small delay between tests
await new Promise(resolve => setTimeout(resolve, 2000))
}
setResult({
type: 'timeframe_test',
summary: `Tested ${results.length} timeframes`,
results
})
} catch (err) {
setError(err instanceof Error ? err.message : 'Timeframe testing failed')
} finally {
setLoading(false)
}
}
async function handleAnalyze() {
await performAnalysis()
}
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 Selection */}
<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">Click any coin for instant analysis</span>
</div>
<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}
className={`group relative p-4 rounded-xl border transition-all duration-300 ${
symbol === coin.symbol
? 'border-cyan-500 bg-cyan-500/10 shadow-lg shadow-cyan-500/20'
: 'border-gray-700 bg-gray-800/50 hover:border-gray-600 hover:bg-gray-800'
} ${loading ? 'opacity-50 cursor-not-allowed' : 'hover:scale-105 hover:shadow-lg'}`}
>
<div className={`w-10 h-10 bg-gradient-to-br ${coin.color} rounded-lg flex items-center justify-center mb-3 mx-auto text-white font-bold`}>
{coin.icon}
</div>
<div className="text-xs font-semibold text-white">{coin.name}</div>
<div className="text-xs text-gray-400 mt-1">{coin.symbol}</div>
{symbol === coin.symbol && (
<div className="absolute top-2 right-2 w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div>
)}
</button>
))}
</div>
</div>
{/* Advanced Controls */}
<div className="border-t border-gray-700 pt-6">
<h3 className="text-sm font-semibold text-gray-300 mb-4 flex items-center">
<span className="w-4 h-4 bg-purple-500 rounded-full mr-2"></span>
Advanced Analysis
</h3>
{/* Symbol and Timeframe */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div className="md:col-span-2">
<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>
<label className="block text-xs font-medium text-gray-400 mb-2">Timeframe</label>
<select
className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white focus:border-cyan-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/20 transition-all"
value={timeframe}
onChange={e => setTimeframe(e.target.value)}
>
{timeframes.map(tf => (
<option key={tf.value} value={tf.value} className="bg-gray-800">
{tf.label}
</option>
))}
</select>
</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 Testing */}
<div className="mb-6">
<label className="block text-xs font-medium text-gray-400 mb-3">Quick Timeframe Tests</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 ${
timeframe === 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>
<button
onClick={testAllTimeframes}
disabled={loading || selectedLayouts.length === 0 || !symbol}
className={`w-full 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'
}`}
>
{loading ? '🔄 Testing...' : '🧪 Test All Timeframes'}
</button>
</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 && (
<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 {timeframe} timeframe...
</p>
</div>
</div>
</div>
)}
{result && (
<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>
{result.layoutsAnalyzed && (
<div className="text-xs text-gray-400">
Layouts: {result.layoutsAnalyzed.join(', ')}
</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.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.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.recommendation)}</p>
{result.confidence && (
<p className="text-cyan-300 text-xs mt-1">{safeRender(result.confidence)}% confidence</p>
)}
</div>
</div>
{/* Trading Levels */}
{result.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.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.keyLevels.support?.join(', ') || 'None identified'}
</p>
</div>
</div>
)}
{/* Trading Setup */}
{(result.entry || result.stopLoss || result.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.entry && (
<div>
<span className="text-xs text-gray-400">Entry Point</span>
<p className="text-yellow-300 font-mono font-semibold">
${safeRender(result.entry.price || result.entry)}
</p>
{result.entry.rationale && (
<p className="text-xs text-gray-300 mt-1">{safeRender(result.entry.rationale)}</p>
)}
</div>
)}
{result.stopLoss && (
<div>
<span className="text-xs text-gray-400">Stop Loss</span>
<p className="text-red-300 font-mono font-semibold">
${safeRender(result.stopLoss.price || result.stopLoss)}
</p>
{result.stopLoss.rationale && (
<p className="text-xs text-gray-300 mt-1">{safeRender(result.stopLoss.rationale)}</p>
)}
</div>
)}
{result.takeProfits && (
<div>
<span className="text-xs text-gray-400">Take Profit</span>
<p className="text-green-300 font-mono font-semibold">
{typeof result.takeProfits === 'object'
? Object.values(result.takeProfits).map(tp => `$${safeRender(tp)}`).join(', ')
: `$${safeRender(result.takeProfits)}`}
</p>
</div>
)}
</div>
</div>
)}
</div>
</div>
)}
</div>
)
}