import { useParams, Link } from 'react-router-dom'; import { useMemo, useState, useEffect } from 'react'; import { ArrowLeft, Building2, TrendingUp, TrendingDown, Minus, AlertTriangle, Calendar, Activity, LineChart, Database, MessageSquare, FileText, Layers, RefreshCw, Play, Loader2 } from 'lucide-react'; import { NIFTY_50_STOCKS } from '../types'; import { sampleRecommendations, getStockHistory, getExtendedPriceHistory, getPredictionPointsWithPrices, getRawAnalysis } from '../data/recommendations'; import { DecisionBadge, ConfidenceBadge, RiskBadge } from '../components/StockCard'; import AIAnalysisPanel from '../components/AIAnalysisPanel'; import StockPriceChart from '../components/StockPriceChart'; import { PipelineOverview, AgentReportCard, DebateViewer, RiskDebateViewer, DataSourcesPanel } from '../components/pipeline'; import { api } from '../services/api'; import { useSettings } from '../contexts/SettingsContext'; import type { FullPipelineData, AgentType } from '../types/pipeline'; type TabType = 'overview' | 'pipeline' | 'debates' | 'data'; export default function StockDetail() { const { symbol } = useParams<{ symbol: string }>(); const [activeTab, setActiveTab] = useState('overview'); const [pipelineData, setPipelineData] = useState(null); const [isLoadingPipeline, setIsLoadingPipeline] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [lastRefresh, setLastRefresh] = useState(null); const [refreshMessage, setRefreshMessage] = useState(null); const { settings } = useSettings(); // Analysis state const [isAnalysisRunning, setIsAnalysisRunning] = useState(false); const [analysisStatus, setAnalysisStatus] = useState(null); const [analysisProgress, setAnalysisProgress] = useState(null); const stock = NIFTY_50_STOCKS.find(s => s.symbol === symbol); const latestRecommendation = sampleRecommendations[0]; const analysis = latestRecommendation?.analysis[symbol || '']; const history = symbol ? getStockHistory(symbol) : []; // Get price history and prediction points for the chart const priceHistory = useMemo(() => { return symbol ? getExtendedPriceHistory(symbol, 60) : []; }, [symbol]); const predictionPoints = useMemo(() => { return symbol && priceHistory.length > 0 ? getPredictionPointsWithPrices(symbol, priceHistory) : []; }, [symbol, priceHistory]); // Function to fetch pipeline data const fetchPipelineData = async (forceRefresh = false) => { if (!symbol || !latestRecommendation?.date) return; if (forceRefresh) { setIsRefreshing(true); } else { setIsLoadingPipeline(true); } try { const data = await api.getPipelineData(latestRecommendation.date, symbol, forceRefresh); setPipelineData(data); if (forceRefresh) { setLastRefresh(new Date().toLocaleTimeString()); const hasData = data.pipeline_steps?.length > 0 || Object.keys(data.agent_reports || {}).length > 0; setRefreshMessage(hasData ? `✓ Data refreshed for ${symbol}` : `No pipeline data found for ${symbol}`); setTimeout(() => setRefreshMessage(null), 3000); } console.log('Pipeline data fetched:', data); } catch (error) { console.error('Failed to fetch pipeline data:', error); if (forceRefresh) { setRefreshMessage(`✗ Failed to refresh: ${error}`); setTimeout(() => setRefreshMessage(null), 3000); } // Set empty pipeline data structure setPipelineData({ date: latestRecommendation.date, symbol: symbol, agent_reports: {}, debates: {}, pipeline_steps: [], data_sources: [], status: 'no_data' }); } finally { setIsLoadingPipeline(false); setIsRefreshing(false); } }; // Fetch pipeline data when tab changes or symbol changes useEffect(() => { if (activeTab === 'overview') return; // Don't fetch for overview tab fetchPipelineData(); }, [symbol, latestRecommendation?.date, activeTab]); // Refresh handler const handleRefresh = async () => { console.log('Refresh button clicked - fetching fresh data...'); await fetchPipelineData(true); console.log('Refresh complete - data updated'); }; // Run Analysis handler const handleRunAnalysis = async () => { if (!symbol || !latestRecommendation?.date) return; setIsAnalysisRunning(true); setAnalysisStatus('starting'); setAnalysisProgress('Starting analysis...'); try { // Trigger analysis with settings from context await api.runAnalysis(symbol, latestRecommendation.date, { deep_think_model: settings.deepThinkModel, quick_think_model: settings.quickThinkModel, provider: settings.provider, api_key: settings.provider === 'anthropic_api' ? settings.anthropicApiKey : undefined, max_debate_rounds: settings.maxDebateRounds }); setAnalysisStatus('running'); // Poll for status const pollInterval = setInterval(async () => { try { const status = await api.getAnalysisStatus(symbol); setAnalysisProgress(status.progress || 'Processing...'); if (status.status === 'completed') { clearInterval(pollInterval); setIsAnalysisRunning(false); setAnalysisStatus('completed'); setAnalysisProgress(`✓ Analysis complete: ${status.decision || 'Done'}`); // Refresh data to show results await fetchPipelineData(true); setTimeout(() => { setAnalysisProgress(null); setAnalysisStatus(null); }, 5000); } else if (status.status === 'error') { clearInterval(pollInterval); setIsAnalysisRunning(false); setAnalysisStatus('error'); setAnalysisProgress(`✗ Error: ${status.error}`); } } catch (err) { console.error('Failed to poll analysis status:', err); } }, 2000); // Poll every 2 seconds // Cleanup after 10 minutes max setTimeout(() => clearInterval(pollInterval), 600000); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error('Failed to start analysis:', errorMessage, error); setIsAnalysisRunning(false); setAnalysisStatus('error'); // More helpful error message if (errorMessage.includes('Failed to fetch') || errorMessage.includes('NetworkError')) { setAnalysisProgress(`✗ Network error: Cannot connect to backend at localhost:8000. Please check if the server is running.`); } else { setAnalysisProgress(`✗ Failed to start analysis: ${errorMessage}`); } } }; if (!stock) { return (

Stock Not Found

The stock "{symbol}" was not found in Nifty 50.

Back to Dashboard
); } const decisionIcon = { BUY: TrendingUp, SELL: TrendingDown, HOLD: Minus, }; const decisionColor = { BUY: 'from-green-500 to-green-600', SELL: 'from-red-500 to-red-600', HOLD: 'from-amber-500 to-amber-600', }; const DecisionIcon = analysis?.decision ? decisionIcon[analysis.decision] : Activity; const bgGradient = analysis?.decision ? decisionColor[analysis.decision] : 'from-gray-500 to-gray-600'; const TABS = [ { id: 'overview' as const, label: 'Overview', icon: LineChart }, { id: 'pipeline' as const, label: 'Analysis Pipeline', icon: Layers }, { id: 'debates' as const, label: 'Debates', icon: MessageSquare }, { id: 'data' as const, label: 'Data Sources', icon: Database }, ]; return (
{/* Back Button */} Back to Dashboard {/* Compact Stock Header */}

{stock.symbol}

{analysis?.decision && ( {analysis.decision} )}

{stock.company_name}

{stock.sector || 'N/A'}
{latestRecommendation?.date ? new Date(latestRecommendation.date).toLocaleDateString('en-IN', { month: 'short', day: 'numeric', }) : 'N/A'}
{/* Analysis Details - Inline */} {analysis && (
Decision:
Confidence:
Risk:
)}
{/* Tab Navigation */}
{TABS.map(tab => { const Icon = tab.icon; const isActive = activeTab === tab.id; return ( ); })} {/* Action Buttons - Show on non-overview tabs */} {activeTab !== 'overview' && (
{lastRefresh && ( Updated: {lastRefresh} )} {/* Run Analysis Button */} {/* Refresh Button */}
)}
{/* Analysis Progress Banner */} {analysisProgress && (
{isAnalysisRunning && } {analysisProgress}
)} {/* Refresh Notification */} {refreshMessage && !analysisProgress && (
{refreshMessage}
)} {/* Tab Content */} {activeTab === 'overview' && ( <> {/* Price Chart with Predictions */} {priceHistory.length > 0 && (

Price History & AI Predictions

)} {/* AI Analysis Panel */} {analysis && getRawAnalysis(symbol || '') && ( )} {/* Compact Stats Grid */}
{history.length}
Analyses
{history.filter((h: { decision: string }) => h.decision === 'BUY').length}
Buy
{history.filter((h: { decision: string }) => h.decision === 'HOLD').length}
Hold
{history.filter((h: { decision: string }) => h.decision === 'SELL').length}
Sell
{/* Analysis History */}

Recommendation History

{history.length > 0 ? (
{history.map((entry, idx) => (
{new Date(entry.date).toLocaleDateString('en-IN', { weekday: 'short', month: 'short', day: 'numeric', })}
))}
) : (

No history yet

)}
)} {activeTab === 'pipeline' && (
{/* Pipeline Overview */}

Analysis Pipeline

console.log('Step clicked:', step)} />
{/* Agent Reports Grid */}

Agent Reports

{(['market', 'news', 'social_media', 'fundamentals'] as AgentType[]).map(agentType => ( ))}
)} {activeTab === 'debates' && (
{/* Investment Debate */} {/* Risk Debate */}
)} {activeTab === 'data' && (
{/* No data message */} {!isLoadingPipeline && (!pipelineData?.data_sources || pipelineData.data_sources.length === 0) && (

No Data Source Logs Available

Data source logs will appear here when the analysis pipeline runs. This includes information about market data, news, and fundamental data fetched.

)}
)} {/* Top Pick / Avoid Status - Compact (visible on all tabs) */} {latestRecommendation && ( <> {latestRecommendation.top_picks.some(p => p.symbol === symbol) && (
Top Pick: {latestRecommendation.top_picks.find(p => p.symbol === symbol)?.reason}
)} {latestRecommendation.stocks_to_avoid.some(s => s.symbol === symbol) && (
Avoid: {latestRecommendation.stocks_to_avoid.find(s => s.symbol === symbol)?.reason}
)} )} {/* Compact Disclaimer */}

AI-generated recommendation for educational purposes only. Not financial advice.

); }