import { useEffect, useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; import { api } from '../api/client'; const EXAMPLE_QUERIES = [ 'Top 5 drugs at risk from Hebei shutdown', 'India rare earth exposure vs Vietnam', 'Worst case 30-day scenario for paracetamol', 'Which EV components are most vulnerable to Jiangsu export freeze?', 'Buffer stock recommendations for critical APIs', ]; function modeMeta(responseMode, shockFeedMode) { if (responseMode === 'hybrid_live') { return { label: 'Gemini Flash | hybrid context', color: 'var(--primary)', bg: 'rgba(79,156,249,0.12)', border: '1px solid rgba(79,156,249,0.25)', }; } if (responseMode === 'fallback') { return { label: 'Local grounded fallback', color: 'var(--amber)', bg: 'rgba(245,158,11,0.12)', border: '1px solid rgba(245,158,11,0.25)', }; } if (responseMode === 'demo' || shockFeedMode === 'demo') { return { label: 'Curated scenario analyst', color: 'var(--primary)', bg: 'rgba(79,156,249,0.12)', border: '1px solid rgba(79,156,249,0.25)', }; } return { label: 'Gemini Flash | grounded RAG', color: 'var(--green)', bg: 'rgba(16,185,129,0.12)', border: '1px solid rgba(16,185,129,0.25)', }; } export default function Query() { const [question, setQuestion] = useState(''); const [answer, setAnswer] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [history, setHistory] = useState([]); const [health, setHealth] = useState(null); useEffect(() => { api.health().then(setHealth).catch(() => setHealth(null)); }, []); const responseMode = answer?.response_mode; const badge = useMemo( () => modeMeta(responseMode, health?.shock_feed_mode), [responseMode, health?.shock_feed_mode], ); async function submit(q) { const text = (q || question).trim(); if (!text || loading) return; setLoading(true); setError(null); setAnswer(null); try { const res = await api.query(text); setAnswer(res); setHistory((prev) => [{ question: text, answer: res, ts: new Date() }, ...prev.slice(0, 5)]); } catch (e) { setError(e.message || 'Query failed. Check backend connectivity.'); } finally { setLoading(false); } } function handleKey(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submit(); } } const metrics = [ { label: 'Feed', value: health?.shock_feed_mode === 'hybrid_demo_live' ? 'Hybrid' : health?.shock_feed_mode || 'Live' }, { label: 'Live shocks', value: health?.live_shocks ?? '-' }, { label: 'Scenario bank', value: health?.demo_scenarios ?? '-' }, ]; return (
Ask for exposed inputs, buffer pressure, supplier options, escalation triggers, or scenario comparisons. The response path will use live context when it is available and fall back to curated scenario evidence when it is not.
Operational query composer
Grounded answers with citations, affected inputs, and action framing.
{item.label}
{item.value}
{error}
If Gemini or Qdrant is unavailable, ShockMap falls back to local scenario and seed-document context.
Scenario matches
Inspect next
Source pack
{c.snippet}
Good questions to ask
Operator notes
Best for
Exposure ranking, stockout pressure, escalation triggers, and next 72-hour actions.
Current model
Gemini Flash with local retrieval, plus scenario-backed fallback when live context is thin.
Recent history