import React, { useState, useRef, useEffect } from 'react'; import axios from 'axios'; import { Send, ChevronDown, ChevronRight, CheckCircle2, Circle } from 'lucide-react'; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, LineChart, Line, PieChart, Pie, Cell } from 'recharts'; const guardrails = ["who is", "poem", "joke", "weather", "translate"]; const suggestions = [ "Top billed products", "Trace billing doc 91150187", "Explain transaction 91150187", "Find fraud", "Incomplete flows", "Customer revenue", "Journal entries" ]; const ChatPanel = ({ cyInstance, sendRef, width }) => { const [query, setQuery] = useState(""); const [messages, setMessages] = useState([]); const [isThinking, setIsThinking] = useState(false); const endRef = useRef(null); useEffect(() => { if (document.startViewTransition) { document.startViewTransition(() => { endRef.current?.scrollIntoView({ behavior: 'smooth' }); }); } else { endRef.current?.scrollIntoView({ behavior: 'smooth' }); } }, [messages, isThinking]); const getBadgeColor = (intent) => { if (intent.includes('Trace')) return '#3b82f6'; if (intent.includes('Error') || intent.includes('Anomaly')) return '#f87171'; if (intent.includes('Aggregat')) return '#22c55e'; if (intent.includes('Lookup')) return '#f59e0b'; if (intent.includes('Scope')) return '#f87171'; if (intent.includes('Fraud')) return '#ef4444'; if (intent.includes('Explain')) return '#8b5cf6'; return '#1a1a1a'; }; // Backend API URL (Dynamic for Vercel/Render) const API_BASE = '/api'; const handleSend = async (overrideQ) => { const text = overrideQ || query; if (!text.trim() || isThinking) return; setQuery(""); setMessages(prev => [...prev, { role: 'user', content: text }]); setIsThinking(true); const qlow = text.toLowerCase(); // Guardrails if (guardrails.some(g => qlow.includes(g))) { setTimeout(() => { setIsThinking(false); setMessages(prev => [...prev, { role: 'system', intent: '🚫 Out of Scope', intentColor: '#f87171', content: "This system only answers questions about the Order to Cash dataset. Please ask about orders, deliveries, billing, or payments." }]); }, 800); return; } try { const res = await axios.post(`${API_BASE}/query`, { query: text }); const data = res.data; // Update Graph if (cyInstance) { if (data.highlights && data.highlights.length > 0) { cyInstance.elements().addClass('dimmed').removeClass('highlighted'); let collection = cyInstance.collection(); data.highlights.forEach(h => { cyInstance.nodes().forEach(n => { if (n.data('id') === h || n.data('label').toLowerCase().includes(h.toLowerCase().replace('s',''))) { n.removeClass('dimmed').addClass('highlighted'); collection = collection.union(n); n.connectedEdges().removeClass('dimmed'); } }); }); if (collection.length > 0) { cyInstance.animate({ fit: { eles: collection, padding: 50 } }, { duration: 800 }); } } else { // STEP 5: Clear highlights if not a graph intent cyInstance.elements().removeClass('dimmed').removeClass('highlighted'); } } const botMsg = { role: 'system', intent: data.intent || "🔍 Lookup", intentColor: getBadgeColor(data.intent || ""), content: data.answer || "Found matching records from your ERP system.", result: data.result, type: data.type }; setIsThinking(false); setMessages(prev => [...prev, botMsg]); } catch (error) { // Offline mock data setTimeout(() => { setIsThinking(false); if(qlow.includes("trace") || qlow.includes("91150187")) { if (cyInstance) { cyInstance.elements().addClass('dimmed').removeClass('highlighted'); let c = cyInstance.collection(); ['91150187', '80737721', '740506', '9400635958', '310000108'].forEach(id => { const n = cyInstance.getElementById(id); if(n.length) { n.removeClass('dimmed').addClass('highlighted'); n.connectedEdges().removeClass('dimmed'); c = c.union(n); } }); cyInstance.animate({ fit: { eles: c, padding: 50 } }, { duration: 800 }); } setMessages(prev => [...prev, { role: 'system', intent: '🔗 Flow Trace', intentColor: '#3b82f6', content: "Tracing billing document **91150187**. It originates from Sales Order 740506 via Delivery 80737721 and posts to Journal Entry 9400635958.", planSteps: ["Lookup BillingDoc 91150187", "Traverse to predecessor (Delivery)", "Traverse to root (Order)"], sql: "SELECT * FROM billing_document_headers WHERE billingDocument='91150187'" }]); } else { setMessages(prev => [...prev, { role: 'system', intent: '⚠️ API Error', intentColor: '#f87171', content: "Backend API is unreachable. Showing mocked offline response." }]); } }, 1000); } }; // Expose handleSend to parent via sendRef (for GraphPanel Explain button) useEffect(() => { if (sendRef) sendRef.current = handleSend; }, [sendRef]); // Reasoning and Table components removed per user request for a cleaner natural language UI. const renderFraudList = (data) => { if (!data || data.length === 0) return null; return (