From aa8ca9846b8e42357bb9a684e355f7ada5d17969 Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Mon, 14 Jul 2025 00:34:13 +0200 Subject: [PATCH] feat: Add trade execution and screenshot gallery to AI analysis - Create TradeModal component for executing trades with entry, TP, SL - Add ScreenshotGallery component with click-to-enlarge functionality - Integrate trade buttons in both single and multi-timeframe analysis results - Add screenshot gallery that displays captured TradingView charts - Parse analysis data to pre-fill trade modal with AI recommendations - Support trade execution via /api/trading endpoint - Add visual indicators and smooth transitions for better UX Trade button features: - Pre-filled entry, take profit, and stop loss from AI analysis - Configurable position size and leverage - Real-time validation and error handling Screenshot gallery features: - Grid layout with hover effects - Click to enlarge in full-screen modal - Support for both single and multi-timeframe results - Chart information overlay with timeframe labels --- components/AIAnalysisPanel.tsx | 164 ++++++++++++++++++++++----- components/ScreenshotGallery.tsx | 134 ++++++++++++++++++++++ components/TradeModal.tsx | 183 +++++++++++++++++++++++++++++++ 3 files changed, 454 insertions(+), 27 deletions(-) create mode 100644 components/ScreenshotGallery.tsx create mode 100644 components/TradeModal.tsx diff --git a/components/AIAnalysisPanel.tsx b/components/AIAnalysisPanel.tsx index ef565c5..9946ec1 100644 --- a/components/AIAnalysisPanel.tsx +++ b/components/AIAnalysisPanel.tsx @@ -1,5 +1,8 @@ "use client" import React, { useState } from 'react' +import Modal from './Modal' +import TradeModal from './TradeModal' +import ScreenshotGallery from './ScreenshotGallery' const layouts = (process.env.NEXT_PUBLIC_TRADINGVIEW_LAYOUTS || 'ai,Diy module').split(',').map(l => l.trim()) const timeframes = [ @@ -54,6 +57,11 @@ export default function AIAnalysisPanel() { const [result, setResult] = useState(null) const [error, setError] = useState(null) const [progress, setProgress] = useState(null) + const [modalOpen, setModalOpen] = useState(false) + const [modalData, setModalData] = useState(null) + const [enlargedScreenshot, setEnlargedScreenshot] = useState(null) + const [tradeModalOpen, setTradeModalOpen] = useState(false) + const [tradeModalData, setTradeModalData] = useState(null) // Helper function to safely render any value const safeRender = (value: any): string => { @@ -390,6 +398,59 @@ export default function AIAnalysisPanel() { await performAnalysis() } + // Trade initiation handler + const handleTradeClick = (tfResult: any) => { + const analysis = tfResult?.result?.analysis || tfResult?.analysis || {} + + setTradeModalData({ + entry: analysis.entry?.price || analysis.entry || '', + tp: analysis.takeProfits?.tp1?.price || analysis.takeProfits?.tp1 || analysis.takeProfits || '', + sl: analysis.stopLoss?.price || analysis.stopLoss || '', + symbol: symbol, + timeframe: tfResult?.timeframeLabel || tfResult?.timeframe || '', + }) + setTradeModalOpen(true) + } + + // Trade execution API call + const executeTrade = async (tradeData: any) => { + try { + const response = await fetch('/api/trading', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + symbol: tradeData.symbol, + side: 'buy', // Could be derived from analysis + size: parseFloat(tradeData.size), + price: parseFloat(tradeData.entry), + stopLoss: parseFloat(tradeData.sl), + takeProfit: parseFloat(tradeData.tp), + leverage: parseInt(tradeData.leverage), + timeframe: tradeData.timeframe + }) + }) + + const result = await response.json() + + if (response.ok) { + // Show success message + alert(`Trade executed successfully! Order ID: ${result.orderId || 'N/A'}`) + } else { + alert(`Trade failed: ${result.error || 'Unknown error'}`) + } + } catch (error) { + console.error('Trade execution failed:', error) + alert('Trade execution failed. Please check your connection.') + } + + setTradeModalOpen(false) + } + + // Screenshot gallery modal + const handleScreenshotClick = (src: string) => { + setEnlargedScreenshot(src) + } + return (
@@ -452,34 +513,31 @@ export default function AIAnalysisPanel() { ))}
- {/* Advanced Controls */} -
-

+ {/* Advanced Analysis Section */} +
+

Advanced Analysis

@@ -834,9 +892,19 @@ export default function AIAnalysisPanel() { }`}> {timeframeResult.success ? '✅' : '❌'} {timeframeResult.timeframeLabel} Timeframe

- - {timeframeResult.success ? 'Analysis Complete' : 'Failed'} - +
+ {timeframeResult.success && timeframeResult.result.analysis && ( + + )} + + {timeframeResult.success ? 'Analysis Complete' : 'Failed'} + +
{timeframeResult.success && timeframeResult.result.analysis && ( @@ -927,11 +995,21 @@ export default function AIAnalysisPanel() { Analysis Complete - {result.screenshots && ( -
- Screenshots: {result.screenshots.length} captured -
- )} +
+ {result.analysis && ( + + )} + {result.screenshots && ( +
+ Screenshots: {result.screenshots.length} captured +
+ )} +
@@ -1267,6 +1345,38 @@ export default function AIAnalysisPanel() {
)} + + {/* Screenshot Gallery */} + {result && result.screenshots && ( + timeframes.find(t => t.value === tf)?.label || tf)} + enlargedImage={enlargedScreenshot} + onImageClick={handleScreenshotClick} + onClose={() => setEnlargedScreenshot(null)} + /> + )} + + {/* Multi-timeframe Screenshot Gallery */} + {result && result.type === 'multi_timeframe' && result.results && ( + r.success && r.result.screenshots).flatMap((r: any) => r.result.screenshots)} + symbol={symbol} + timeframes={result.results.filter((r: any) => r.success).map((r: any) => r.timeframeLabel)} + enlargedImage={enlargedScreenshot} + onImageClick={handleScreenshotClick} + onClose={() => setEnlargedScreenshot(null)} + /> + )} + + {/* Trade Modal */} + setTradeModalOpen(false)} + tradeData={tradeModalData} + onExecute={executeTrade} + /> ) } diff --git a/components/ScreenshotGallery.tsx b/components/ScreenshotGallery.tsx new file mode 100644 index 0000000..15d24a4 --- /dev/null +++ b/components/ScreenshotGallery.tsx @@ -0,0 +1,134 @@ +"use client" +import React from 'react' + +interface ScreenshotGalleryProps { + screenshots: string[] + symbol: string + timeframes: string[] + enlargedImage: string | null + onImageClick: (src: string) => void + onClose: () => void +} + +export default function ScreenshotGallery({ + screenshots, + symbol, + timeframes, + enlargedImage, + onImageClick, + onClose +}: ScreenshotGalleryProps) { + if (screenshots.length === 0) return null + + return ( + <> + {/* Gallery Grid */} +
+
+

+ + 📸 + + Chart Screenshots +

+
+ {screenshots.length} captured • Click to enlarge +
+
+ +
+ {screenshots.map((screenshot, index) => { + const filename = screenshot.split('/').pop() || '' + const timeframe = timeframes[index] || 'Unknown' + + return ( +
onImageClick(screenshot)} + > + {/* Preview Image */} +
+ {`${symbol} { + const target = e.target as HTMLImageElement + target.style.display = 'none' + target.nextElementSibling?.classList.remove('hidden') + }} + /> +
+
+
📊
+
Chart Preview
+
{filename}
+
+
+ + {/* Overlay */} +
+
+
+ 🔍 +
+
+
+
+ + {/* Image Info */} +
+
+
+
{symbol}
+
{timeframe} Timeframe
+
+
+ Click to view +
+
+
+
+ ) + })} +
+
+ + {/* Enlarged Image Modal */} + {enlargedImage && ( +
+
+ {/* Close Button */} + + + {/* Image */} + Enlarged chart + + {/* Image Info Overlay */} +
+
+
+
{symbol} Chart Analysis
+
AI analyzed screenshot • High resolution view
+
+
+ ESC to close +
+
+
+
+
+ )} + + ) +} diff --git a/components/TradeModal.tsx b/components/TradeModal.tsx new file mode 100644 index 0000000..f0a90e3 --- /dev/null +++ b/components/TradeModal.tsx @@ -0,0 +1,183 @@ +"use client" +import React, { useState } from 'react' + +interface TradeModalProps { + isOpen: boolean + onClose: () => void + tradeData: { + symbol: string + timeframe: string + entry: string + tp: string + sl: string + } | null + onExecute: (data: any) => void +} + +export default function TradeModal({ isOpen, onClose, tradeData, onExecute }: TradeModalProps) { + const [loading, setLoading] = useState(false) + const [formData, setFormData] = useState({ + entry: tradeData?.entry || '', + tp: tradeData?.tp || '', + sl: tradeData?.sl || '', + size: '0.1', + leverage: '1' + }) + + React.useEffect(() => { + if (tradeData) { + setFormData(prev => ({ + ...prev, + entry: tradeData.entry || '', + tp: tradeData.tp || '', + sl: tradeData.sl || '' + })) + } + }, [tradeData]) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true) + + try { + await onExecute({ + symbol: tradeData?.symbol, + timeframe: tradeData?.timeframe, + ...formData + }) + } catch (error) { + console.error('Trade execution failed:', error) + } finally { + setLoading(false) + } + } + + if (!isOpen || !tradeData) return null + + return ( +
+
+
+

+ + 💰 + + Execute Trade +

+ +
+ +
+
+
Symbol: {tradeData.symbol}
+
Timeframe: {tradeData.timeframe}
+
+
+ +
+
+ + setFormData(prev => ({ ...prev, entry: e.target.value }))} + className="w-full px-3 py-2 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:border-green-500 focus:outline-none" + placeholder="0.00" + required + /> +
+ +
+ + setFormData(prev => ({ ...prev, tp: e.target.value }))} + className="w-full px-3 py-2 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:border-green-500 focus:outline-none" + placeholder="0.00" + required + /> +
+ +
+ + setFormData(prev => ({ ...prev, sl: e.target.value }))} + className="w-full px-3 py-2 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:border-red-500 focus:outline-none" + placeholder="0.00" + required + /> +
+ +
+
+ + setFormData(prev => ({ ...prev, size: e.target.value }))} + className="w-full px-3 py-2 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:border-cyan-500 focus:outline-none" + placeholder="0.1" + required + /> +
+ +
+ + +
+
+ +
+ + +
+
+
+
+ ) +}