import React, { useState, useEffect, useRef } from 'react'; const WS_BASE = (import.meta.env.VITE_API_URL || 'http://localhost:8000').replace('http', 'ws'); const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000'; const DECISION_COLOR = { APPROVED: 'var(--accent)', REVIEW: 'var(--warning)', BLOCKED: 'var(--danger)', }; const DECISION_BG = { APPROVED: 'rgba(0,228,117,0.1)', REVIEW: 'rgba(255,179,0,0.1)', BLOCKED: 'rgba(255,51,102,0.1)', }; export default function MLGuard() { const [events, setEvents] = useState([]); const [connected, setConnected] = useState(false); const [stats, setStats] = useState({ approved: 0, review: 0, blocked: 0, total: 0, avgScore: 0 }); const [refundResult, setRefundResult] = useState(null); const [refundOrderId, setRefundOrderId] = useState(''); const wsRef = useRef(null); const feedEndRef = useRef(null); useEffect(() => { connect(); return () => wsRef.current?.close(); }, []); useEffect(() => { feedEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [events]); const connect = () => { try { const ws = new WebSocket(`${WS_BASE}/ws/fraud-feed`); wsRef.current = ws; ws.onopen = () => setConnected(true); ws.onclose = () => { setConnected(false); setTimeout(connect, 3000); }; ws.onmessage = (e) => { const ev = JSON.parse(e.data); setEvents(prev => [...prev.slice(-79), ev]); setStats(prev => { const total = prev.total + 1; const approved = prev.approved + (ev.decision === 'APPROVED' ? 1 : 0); const review = prev.review + (ev.decision === 'REVIEW' ? 1 : 0); const blocked = prev.blocked + (ev.decision === 'BLOCKED' ? 1 : 0); const avgScore = (prev.avgScore * prev.total + ev.fraud_score) / total; return { total, approved, review, blocked, avgScore }; }); }; } catch { setTimeout(connect, 3000); } }; const runRefundTriage = async () => { const oid = refundOrderId.trim() || 'HF-00001'; try { const res = await fetch(`${API_BASE}/api/ml/refund-triage?order_id=${encodeURIComponent(oid)}`); setRefundResult(await res.json()); } catch { setRefundResult({ error: 'Backend not reachable. Make sure FastAPI is running.' }); } }; return (
{/* Header */}
ML Guard
FraudGuard v2 · COD Gatekeeper · Rider Theft Sentinel · Semantic Refund Checker
{connected ? 'Feed Live' : 'Connecting...'}
{/* KPI row */}
Scored Today
{stats.total}
orders processed
Approved
{stats.approved}
{stats.total ? ((stats.approved / stats.total) * 100).toFixed(0) : 0}% pass rate
Flagged / Review
{stats.review}
manual queue
Blocked
{stats.blocked}
avg score {stats.avgScore.toFixed(3)}
{/* Live fraud feed */}
Live Fraud Scoring Feed FraudGuard v2 · {connected ? 'streaming' : 'offline'}
{events.length === 0 ? (
Connecting to fraud detection WebSocket...
) : events.map((ev, i) => ( ))}
{/* Refund triage panel */}
Refund Triage

Semantic plausibility checker + SLA penalty engine. Auto-approves, flags for manual review, or escalates.

setRefundOrderId(e.target.value)} onKeyDown={e => e.key === 'Enter' && runRefundTriage()} />
{refundResult && !refundResult.error && (
{refundResult.order_id} {refundResult.decision}
Confidence: {refundResult.confidence}
Escrow: {refundResult.escrow_action}
{refundResult.detected_reasons?.map(r => ( {r} ))}
{refundResult.model}
)} {refundResult?.error && (
{refundResult.error}
)}
); } function FraudRow({ ev }) { return (
{ev.timestamp?.slice(11, 19)} {ev.order_id} {ev.restaurant} Rs {ev.order_value?.toFixed(0)} 0.5 ? 'var(--danger)' : ev.fraud_score > 0.25 ? 'var(--warning)' : 'var(--accent)' }}> {ev.fraud_score?.toFixed(3)} {ev.decision}
); }