import React, { useState, useEffect } from 'react'; import { Globe, Ship, AlertTriangle, ShieldCheck, Loader2, X } from 'lucide-react'; import { ChatGoogleGenerativeAI } from '@langchain/google-genai'; import { HumanMessage } from '@langchain/core/messages'; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; const newsHeadlines = [ { source: 'Right-Leaning', headline: 'Supply Chain Bottlenecks Expose Dependence on Foreign Imports', sentiment: 'negative' }, { source: 'Left-Leaning', headline: 'Global Trade Disruptions Threaten Consumer Price Stability', sentiment: 'negative' }, { source: 'Financial Times', headline: 'Port Congestion Peaks as Holiday Inventory Arrives Early', sentiment: 'neutral' } ]; // Fallback data in case the ArcGIS API fails due to CORS or downtime const mockPortData = Array.from({ length: 30 }, (_, i) => ({ date: new Date(Date.now() - (29 - i) * 24 * 60 * 60 * 1000).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), calls: Math.floor(12000 + Math.random() * 3000 + (i > 20 ? -2000 : 0)) // Simulating a recent dip })); export default function MacroTracker() { const [isOpen, setIsOpen] = useState(false); const [analysis, setAnalysis] = useState(null); const [loading, setLoading] = useState(false); const [chartData, setChartData] = useState([]); const [isLive, setIsLive] = useState(false); useEffect(() => { const fetchPortWatchData = async () => { try { const apiUrl = "https://services9.arcgis.com/weJ1QsnbMYJlCHdG/arcgis/rest/services/Daily_Trade_Data/FeatureServer/0/query?where=1=1&outFields=date,calls&orderByFields=date DESC&resultRecordCount=30&f=json"; const response = await fetch(apiUrl); if (!response.ok) throw new Error('Network response was not ok'); const data = await response.json(); if (data && data.features && data.features.length > 0) { const parsedData = data.features.map(f => ({ date: new Date(f.attributes.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), calls: f.attributes.calls || 0 })).reverse(); setChartData(parsedData); setIsLive(true); } else { throw new Error('No features returned'); } } catch (error) { console.warn("Failed to fetch live ArcGIS data. Falling back to mock dataset.", error); setChartData(mockPortData); setIsLive(false); } }; fetchPortWatchData(); }, []); const synthesizeNews = async () => { setLoading(true); setAnalysis(null); const apiKey = import.meta.env.VITE_GEMINI_API_KEY; if (apiKey && apiKey.length > 10) { try { const llm = new ChatGoogleGenerativeAI({ apiKey: apiKey, modelName: 'gemini-1.5-flash', maxOutputTokens: 2048, }); const recentDataSummary = chartData.slice(-5).map(d => `${d.date}: ${d.calls} calls`).join(", "); const promptText = ` You are an expert, calming financial advisor speaking to a novice investor. The user is looking at a custom dashboard of global maritime trade data (port calls). Here is the raw data for the last 5 days indicating recent activity levels: [${recentDataSummary}]. Additionally, read these recent news headlines causing panic: 1. ${newsHeadlines[0].headline} 2. ${newsHeadlines[1].headline} 3. ${newsHeadlines[2].headline} Provide a grounded, jargon-free explanation (max 3-4 sentences) that synthesizes this numerical data and the news. Explain why this data represents normal market noise and why they should not panic sell their portfolio. `; const message = new HumanMessage({ content: [{ type: "text", text: promptText }] }); const response = await llm.invoke([message]); setAnalysis(response.content); setLoading(false); } catch (error) { console.error("Gemini Error:", error); runMockAnalysis(); } } else { runMockAnalysis(); } }; const runMockAnalysis = () => { setTimeout(() => { setAnalysis("While the news highlights supply chain bottlenecks, the port data shows that daily ship calls remain within normal seasonal ranges, despite a slight recent dip. This indicates that global trade is still flowing actively. Temporary disruptions and the resulting inflation spikes rarely derail a well-diversified, long-term portfolio. Stay the course and avoid making emotional decisions based on short-term headlines."); setLoading(false); }, 2000); }; const handleOpen = () => { setIsOpen(true); if (!analysis) { synthesizeNews(); } }; return ( <> {isOpen && (
Contextualizing geopolitical noise with real-world data to keep you grounded.
Daily maritime trade volume index based on global port calls.
{analysis}
) : ( )}