gridops-ai / frontend /src /EventsDatabase.jsx
Aadithya
Feat/Chronos-2 Integrated,Tests added,Finetuned
a605406
Raw
History Blame Contribute Delete
10.9 kB
import React, { useState, useMemo } from 'react';
const RANDOM_EVENTS = [
{ event_type: "Texas Deep Freeze (Uri Pattern)", severity: "CRITICAL", description: "Unprecedented multi-day freezing temperatures causing severe grid strain and widespread generation failure.", demand_impact_pct: 25.4, grid_region: "PJM-South" },
{ event_type: "Summer Heatwave (Derecho)", severity: "HIGH", description: "Prolonged 100F+ temperatures across the midwest causing massive A/C load and transmission line sagging.", demand_impact_pct: 18.2, grid_region: "PJM-West" },
{ event_type: "Shoulder Month Cold Snap", severity: "MEDIUM", description: "Unexpected cold front during typical shoulder months causing unforecasted heating demand.", demand_impact_pct: 8.5, grid_region: "PJM-East" },
{ event_type: "Major Transmission Outage", severity: "HIGH", description: "Failure of critical 500kV interconnect causing localized demand spikes and rerouting.", demand_impact_pct: 12.0, grid_region: "PJM-West" },
{ event_type: "Industrial Shutdown", severity: "LOW", description: "Planned maintenance of major manufacturing hubs causing sudden drop in base load.", demand_impact_pct: -5.5, grid_region: "PJM-East" }
];
const SEVERITY_LEVELS = ['ALL', 'CRITICAL', 'HIGH', 'MEDIUM', 'LOW'];
const SEVERITY_COLORS = {
CRITICAL: 'var(--accent-rose)',
HIGH: '#f97316',
MEDIUM: 'var(--accent-amber)',
LOW: 'var(--accent-emerald)',
};
export function EventsDatabase({ events, onAddEvent, onBack }) {
const [form, setForm] = useState({ event_type: '', severity: 'MEDIUM', description: '', demand_impact_pct: 0, grid_region: 'PJM-West' });
const [msg, setMsg] = useState('');
const [loading, setLoading] = useState(false);
const [activeFilter, setActiveFilter] = useState('ALL');
const [searchText, setSearchText] = useState('');
// Count events per severity
const severityCounts = useMemo(() => {
const counts = { ALL: events.length, CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
events.forEach(ev => {
const sev = (ev.severity || '').toUpperCase();
if (counts[sev] !== undefined) counts[sev]++;
});
return counts;
}, [events]);
// Filter events
const filteredEvents = useMemo(() => {
let filtered = events;
// Severity filter
if (activeFilter !== 'ALL') {
filtered = filtered.filter(ev => (ev.severity || '').toUpperCase() === activeFilter);
}
// Text search
if (searchText.trim()) {
const query = searchText.toLowerCase();
filtered = filtered.filter(ev =>
(ev.event_type || '').toLowerCase().includes(query) ||
(ev.description || '').toLowerCase().includes(query) ||
(ev.grid_region || '').toLowerCase().includes(query)
);
}
return filtered;
}, [events, activeFilter, searchText]);
function autofill() {
const random = RANDOM_EVENTS[Math.floor(Math.random() * RANDOM_EVENTS.length)];
setForm(random);
setMsg('Autofilled random scenario.');
}
async function submit(e) {
e.preventDefault();
if (!form.event_type || !form.description) return;
setLoading(true); setMsg('');
try {
const res = await onAddEvent(form);
setMsg(res.message || 'Event embedded and added to Vector DB successfully.');
setForm({ event_type: '', severity: 'MEDIUM', description: '', demand_impact_pct: 0, grid_region: 'PJM-West' });
} catch (err) { setMsg('Failed: ' + err.message); }
setLoading(false);
}
return (
<div className="landing animation-fade-in" style={{ alignItems: 'flex-start' }}>
<div className="dash-header" style={{ width: '100%', marginBottom: '24px' }}>
<div>
<h1>Historical Event Database</h1>
<p className="muted-text">Manage the ChromaDB vector store used by the Retrieval-Augmented Generation (RAG) node.</p>
</div>
<button className="btn-secondary compact" onClick={onBack}>← Back</button>
</div>
<div className="config-grid" style={{ width: '100%' }}>
<div className="glass-card">
<div className="step-header" style={{ textAlign: 'left', marginBottom: '24px' }}>
<h2 style={{ fontSize: '20px', marginBottom: '8px' }}>Inject Custom Scenario (Optional)</h2>
<p>Embed new historical or hypothetical grid events into the database. The RAG agent will query these events if future anomalies match their pattern.</p>
</div>
<form className="event-form" onSubmit={submit}>
<div className="field-group" style={{ marginBottom: '16px' }}>
<label className="field-label">Event Name</label>
<input className="field-input" placeholder="e.g. Texas Deep Freeze"
value={form.event_type} onChange={e => setForm({ ...form, event_type: e.target.value })} required />
</div>
<div className="form-row" style={{ marginBottom: '16px' }}>
<div className="field-group" style={{ marginBottom: 0 }}>
<label className="field-label">Severity Level</label>
<select className="field-input" value={form.severity}
onChange={e => setForm({ ...form, severity: e.target.value })}>
<option>LOW</option><option>MEDIUM</option><option>HIGH</option><option>CRITICAL</option>
</select>
</div>
<div className="field-group" style={{ marginBottom: 0 }}>
<label className="field-label">Demand Impact (%)</label>
<input type="number" className="field-input" step="0.1"
value={form.demand_impact_pct} onChange={e => setForm({ ...form, demand_impact_pct: parseFloat(e.target.value) || 0 })} />
</div>
</div>
<div className="field-group" style={{ marginBottom: '16px' }}>
<label className="field-label">Grid Region</label>
<select className="field-input" value={form.grid_region}
onChange={e => setForm({ ...form, grid_region: e.target.value })}>
<option>PJM-West</option><option>PJM-East</option><option>PJM-South</option><option>PJM-System</option>
</select>
</div>
<div className="field-group" style={{ marginBottom: '24px' }}>
<label className="field-label">Contextual Description</label>
<textarea className="field-input" rows={3} placeholder="Describe the weather or grid conditions..."
value={form.description} onChange={e => setForm({ ...form, description: e.target.value })} required />
</div>
<div style={{ display: 'flex', gap: '12px' }}>
<button className="btn-secondary" type="button" onClick={autofill} style={{ flex: 1 }}>
Random Autofill
</button>
<button className="btn-primary" type="submit" disabled={loading} style={{ flex: 2, padding: '14px' }}>
{loading ? 'Embedding...' : 'Inject into Vector DB'}
</button>
</div>
{msg && <div className="event-msg">{msg}</div>}
</form>
</div>
<div className="glass-card" style={{ maxHeight: '800px', overflowY: 'auto' }}>
<h2 style={{ fontSize: '20px', marginBottom: '16px', color: 'var(--text-primary)' }}>Vector DB Contents</h2>
{/* Search Input */}
<div className="events-search-bar">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--text-tertiary)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="11" cy="11" r="8" /><line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
<input
className="events-search-input"
type="text"
placeholder="Search events by name, description, or region..."
value={searchText}
onChange={e => setSearchText(e.target.value)}
/>
{searchText && (
<button className="events-search-clear" onClick={() => setSearchText('')} type="button">×</button>
)}
</div>
{/* Severity Filter Pills */}
<div className="filter-bar">
{SEVERITY_LEVELS.map(level => {
const isActive = activeFilter === level;
const count = severityCounts[level] || 0;
const color = level === 'ALL' ? 'var(--accent-cyan)' : SEVERITY_COLORS[level];
return (
<button
key={level}
className={`filter-pill ${isActive ? 'active' : ''}`}
onClick={() => setActiveFilter(level)}
type="button"
style={isActive ? {
borderColor: color,
background: `${color}15`,
color: color,
} : {}}
>
{level}
<span className="filter-pill-count" style={isActive ? { background: color, color: '#000' } : {}}>
{count}
</span>
</button>
);
})}
</div>
{filteredEvents.length === 0 ? (
<div className="empty-msg">
{events.length === 0
? 'No events found in the database.'
: `No events match the current filter${searchText ? ` "${searchText}"` : ''}.`
}
{activeFilter !== 'ALL' && events.length > 0 && (
<button
className="link-btn"
onClick={() => { setActiveFilter('ALL'); setSearchText(''); }}
style={{ marginTop: '12px', display: 'block' }}
>
Clear all filters
</button>
)}
</div>
) : (
<div className="event-list">
<div className="events-result-count">
Showing {filteredEvents.length} of {events.length} events
</div>
{filteredEvents.map((ev, i) => {
const sevClass = `sev-${(ev.severity || 'medium').toLowerCase()}`;
return (
<div key={i} className="event-card">
<div className="event-header">
<strong>{ev.event_type}</strong>
<span className={`sev ${sevClass}`}>{ev.severity}</span>
<span className="impact">{ev.demand_impact_pct > 0 ? '+' : ''}{ev.demand_impact_pct}% | {ev.grid_region}</span>
</div>
<p>{ev.description}</p>
</div>
);
})}
</div>
)}
</div>
</div>
</div>
);
}