import React, { useState, useEffect, useRef } from 'react';
import axios from 'axios';
import { motion, AnimatePresence } from 'framer-motion';
import { Send, Bot, User, Info, Wifi, AlertTriangle, CheckCircle2 } from 'lucide-react';
import ReactMarkdown from 'react-markdown';
const API_BASE = import.meta.env.VITE_API_BASE || '';
const OrchestrationLog = ({ steps }) => {
if (!steps || !Array.isArray(steps)) return null;
return (
Agent Reasoning & Path
{steps.map((step, i) => (
-
{step}
))}
);
};
const ResultCards = ({ data }) => {
if (!data || !Array.isArray(data)) return null;
return (
{data.map((item, idx) => {
const status = item.status || (item.anomaly === 1 ? 'Critical' : 'Normal');
let pillClass = 'pill-green';
let Icon = CheckCircle2;
if (status === 'Critical') { pillClass = 'pill-red'; Icon = AlertTriangle; }
else if (status === 'Warning') { pillClass = 'pill-yellow'; Icon = Info; }
else if (status === 'No Data') { pillClass = 'pill-white'; Icon = Wifi; }
return (
{item.name || item.asset || item.asset_name || 'Asset'}
{status}
{item.value !== undefined && Value: {item.value}}
{item.latest_mse !== undefined && MSE: {item.latest_mse.toFixed(5)}}
{item.mse !== undefined && MSE: {item.mse.toFixed(5)}}
);
})}
);
};
const TrendChartRenderer = ({ chart }) => {
if (!chart || !chart.series || chart.series.length === 0) return null;
const [hoveredPoint, setHoveredPoint] = useState(null);
// Dimensions
const width = 520;
const height = 240;
const paddingLeft = 45;
const paddingRight = 20;
const paddingTop = 25;
const paddingBottom = 35;
// Get all values to compute range
let allValues = [];
chart.series.forEach(s => {
if (s.points) {
s.points.forEach(p => allValues.push(p.value));
}
});
if (allValues.length === 0) return null;
let minVal = Math.min(...allValues);
let maxVal = Math.max(...allValues);
if (minVal === maxVal) {
minVal = minVal - 1;
maxVal = maxVal + 1;
}
const valRange = maxVal - minVal;
const yMin = minVal - valRange * 0.1;
const yMax = maxVal + valRange * 0.1;
// Helper functions for scaling
const getX = (index, total) => {
if (total <= 1) return paddingLeft + (width - paddingLeft - paddingRight) / 2;
return paddingLeft + (index / (total - 1)) * (width - paddingLeft - paddingRight);
};
const getY = (value) => {
return paddingTop + (1 - (value - yMin) / (yMax - yMin)) * (height - paddingTop - paddingBottom);
};
// Gridlines (Y-axis)
const gridCount = 4;
const gridLines = [];
for (let i = 0; i <= gridCount; i++) {
gridLines.push(yMin + (i / gridCount) * (yMax - yMin));
}
// X-axis labels (render up to 5 labels to avoid clutter)
const maxLabelCount = 5;
const totalPoints = chart.series[0]?.points?.length || 0;
const labelIndices = [];
if (totalPoints > 0) {
const step = Math.max(1, Math.floor(totalPoints / maxLabelCount));
for (let i = 0; i < totalPoints; i += step) {
labelIndices.push(i);
}
if (labelIndices[labelIndices.length - 1] !== totalPoints - 1 && totalPoints > 1) {
labelIndices.push(totalPoints - 1);
}
}
const isLine = chart.chart_type === 'line';
return (
{chart.title || 'Telemetry Trend'}
{chart.y_label || 'Values'} over {chart.x_label || 'Time'}
{/* Tooltip Overlay */}
{hoveredPoint && (
{hoveredPoint.seriesLabel}
{hoveredPoint.value.toFixed(2)}
{hoveredPoint.label}
)}
{/* Legend */}
{chart.series.length > 0 && (
{chart.series.map((s, sIdx) => (
{s.label}
))}
)}
);
};
const MessageBubble = ({ message }) => {
const isAI = message.sender === 'ai';
const results = message.result;
const analysis = message.analysis;
const trendChart = analysis?.trend_chart || (analysis?.trend_charts && analysis.trend_charts[0]);
return (
{isAI ? : }
{isAI ? (
{message.text}
{message.orchestration && }
{trendChart && }
{results && }
) : (
{message.text}
)}
);
};
const ChatPanel = () => {
const [messages, setMessages] = useState([
{
id: 1,
text: "Hello! I am **The Foreman**, your Industrial Multi-Agent Supervisor. I can help with real-time telemetry, historical analysis, and anomaly detection. How can I assist you today?",
sender: 'ai'
}
]);
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
const scrollRef = useRef(null);
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [messages, loading]);
const handleSend = async (e) => {
e.preventDefault();
if (!input.trim()) return;
const userMsg = { id: Date.now(), text: input, sender: 'user' };
setMessages(prev => [...prev, userMsg]);
setInput('');
setLoading(true);
try {
const { data } = await axios.post(`${API_BASE}/api/v1/chat/message`, { message: input });
const aiMsg = {
id: Date.now() + 1,
text: data.answer || data.reply,
sender: 'ai',
result: data.data,
intent: data.intent,
analysis: data.analysis,
orchestration: data.orchestration
};
setMessages(prev => [...prev, aiMsg]);
} catch (err) {
console.error("Gateway error:", err);
const errorDetail = err.response ? ` (${err.response.status}: ${JSON.stringify(err.response.data)})` : ` (${err.message})`;
setMessages(prev => [...prev, {
id: Date.now(),
text: `The Foreman is currently unable to reach the gateway${errorDetail}. Please check your network connection.`,
sender: 'ai'
}]);
} finally {
setLoading(false);
}
};
return (
{/* Topbar */}
The Foreman
Supervising Industrial Systems
All systems reporting
{/* Messages */}
{/* Guide Card */}
Operational Guide
To analyze specific systems, use these naming conventions:
- Coldrooms: "coldroom 1" through "coldroom 9"
- Refinery: "tank 1" to "tank 13", no tank 10
{messages.map((m) => (
))}
{loading && (
)}
{/* Input */}
);
};
export default ChatPanel;