import { useState, useRef, useCallback } from "react"; // ───────────────────────────────────────────────────────── // THEME DEFINITIONS // ───────────────────────────────────────────────────────── const THEMES = { dark: { name: "dark", bg: "#070d14", surface: "#0f1923", surfaceAlt: "#0a1520", border: "#1e2d3d", borderFocus: "#3b82f6", text: "#e2e8f0", textMuted: "#4a6080", textSub: "#94a3b8", textBody: "#cbd5e1", inputBg: "#0a1520", uploadBg: "#0a1520", uploadHover: "#1e2d3d", scrollTrack: "#0f1923", scrollThumb: "#1e3a5f", infoBg: "#0a1520", emptyColor: "#1e3a5f", cardBg: "#0f1923", barTrack: "#1e2d3d", btnDisabled: "#1e2d3d", btnDisabledTxt: "#4a6080", headerShadow: "none", cardShadow: "none", }, light: { name: "light", bg: "#f0f4f8", surface: "#ffffff", surfaceAlt: "#f8fafc", border: "#d1dde9", borderFocus: "#2563eb", text: "#0f172a", textMuted: "#64748b", textSub: "#475569", textBody: "#334155", inputBg: "#ffffff", uploadBg: "#f8fafc", uploadHover: "#e2eaf4", scrollTrack: "#e2e8f0", scrollThumb: "#94a3b8", infoBg: "#f1f5f9", emptyColor: "#94a3b8", cardBg: "#ffffff", barTrack: "#e2e8f0", btnDisabled: "#e2e8f0", btnDisabledTxt: "#94a3b8", headerShadow: "0 1px 6px rgba(0,0,0,0.07)", cardShadow: "0 1px 4px rgba(0,0,0,0.06)", }, }; // ───────────────────────────────────────────────────────── // THEME TOGGLE // ───────────────────────────────────────────────────────── const ThemeToggle = ({ theme, onToggle }) => { const isDark = theme.name === "dark"; return ( ); }; // ───────────────────────────────────────────────────────── // HF JUDGE SCORE BADGE (replaces ROUGE-L badge) // ───────────────────────────────────────────────────────── const HFJudgeBadge = ({ score }) => { const pct = parseFloat(score) * 100; const color = pct >= 60 ? "#22c55e" : pct >= 35 ? "#f59e0b" : "#ef4444"; return ( HF Judge: {pct.toFixed(1)}% ); }; // ───────────────────────────────────────────────────────── // OUTPUT CARD // ───────────────────────────────────────────────────────── const OutputCard = ({ title, icon, content, badge, accent, loading, theme }) => (
{icon} {title} {badge &&
{badge}
}
{loading ? (
{[0, 0.2, 0.4].map((d, i) => ( ))} Generating…
) : content ? (

{content}

) : (

Awaiting input…

)}
); // ───────────────────────────────────────────────────────── // REWARD BREAKDOWN CARD — mirrors inference.py output exactly // ───────────────────────────────────────────────────────── const RewardBar = ({ label, score, color, desc, theme }) => { const pct = Math.round(parseFloat(score) * 100); return (
{label} {parseFloat(score).toFixed(4)}
{desc}
); }; const RewardBreakdownCard = ({ breakdown, label, accent, theme }) => { if (!breakdown) return null; const totalPct = Math.round(parseFloat(breakdown.total) * 100); const totalColor = totalPct >= 60 ? "#22c55e" : totalPct >= 40 ? "#f59e0b" : "#ef4444"; return (
{/* Header */}
📊 {label} Reward Breakdown
{/* Total score pill */} TOTAL: {parseFloat(breakdown.total).toFixed(4)}
{/* Component bars */} {/* Total bar */}
TOTAL {parseFloat(breakdown.total).toFixed(4)} / 1.0
Weighted average (0.25 × each component)
); }; // ───────────────────────────────────────────────────────── // MAIN APP // ───────────────────────────────────────────────────────── export default function App() { const [themeName, setThemeName] = useState("dark"); const theme = THEMES[themeName]; const toggleTheme = () => setThemeName(t => t === "dark" ? "light" : "dark"); const [image, setImage] = useState(null); const [imageFile, setImageFile] = useState(null); const [groundTruth, setGroundTruth] = useState(""); const [sftOutput, setSftOutput] = useState(""); const [rewardOutput, setRewardOutput] = useState(""); const [grpoOutput, setGrpoOutput] = useState(""); const [rewardScore, setRewardScore] = useState(null); // ── NEW: HF Judge scores from server (replaces client-side ROUGE-L) ── const [sftHFJudge, setSftHFJudge] = useState(null); const [grpoHFJudge, setGrpoHFJudge] = useState(null); const [loading, setLoading] = useState(false); const [dragging, setDragging] = useState(false); const fileRef = useRef(); // ── Pipeline ───────────────────────────────────────── const runInference = async () => { if (!image || !imageFile) return; setLoading(true); setSftOutput(""); setRewardOutput(""); setGrpoOutput(""); setRewardScore(null); // ── Reset HF Judge scores ── setSftHFJudge(null); setGrpoHFJudge(null); const BASE = ""; try { // 1. SFT const sftForm = new FormData(); sftForm.append("file", imageFile); const sftRes = await fetch(`${BASE}/sft`, { method: "POST", body: sftForm }); const sftData = await sftRes.json(); setSftOutput(sftData.report); // 2. Reward — send ground truth for full breakdown + hf_judge const rmForm = new FormData(); rmForm.append("file", imageFile); rmForm.append("ground_truth", groundTruth); const rmRes = await fetch(`${BASE}/reward`, { method: "POST", body: rmForm }); const rmData = await rmRes.json(); setRewardOutput(rmData.feedback); setRewardScore(parseFloat(rmData.score).toFixed(2)); // ── Capture SFT HF Judge score from server ── if (rmData.hf_judge !== null && rmData.hf_judge !== undefined) { setSftHFJudge(rmData.hf_judge); } // 3. GRPO + its reward breakdown + hf_judge const grpoForm = new FormData(); grpoForm.append("file", imageFile); grpoForm.append("ground_truth", groundTruth); const grpoRes = await fetch(`${BASE}/grpo_reward`, { method: "POST", body: grpoForm }); const grpoData = await grpoRes.json(); setGrpoOutput(grpoData.report); // ── Capture GRPO HF Judge score from server ── if (grpoData.hf_judge !== null && grpoData.hf_judge !== undefined) { setGrpoHFJudge(grpoData.hf_judge); } } catch (err) { console.error("Inference error:", err); setSftOutput("⚠️ Could not connect to server. Please check your connection."); } setLoading(false); }; // ── File handling ───────────────────────────────────── const handleFile = (file) => { if (!file || !file.type.startsWith("image/")) return; setImageFile(file); const reader = new FileReader(); reader.onload = e => setImage(e.target.result); reader.readAsDataURL(file); }; const onDrop = useCallback((e) => { e.preventDefault(); setDragging(false); handleFile(e.dataTransfer.files[0]); }, []); // eslint-disable-line const clearAll = () => { setImage(null); setImageFile(null); setSftOutput(""); setRewardOutput(""); setGrpoOutput(""); setRewardScore(null); setSftHFJudge(null); setGrpoHFJudge(null); if (fileRef.current) fileRef.current.value = ""; }; return (
{/* ═══ HEADER ═══ */}
🫁
BioStack
NeMo Gym Based Medical Report Generation
{/* ═══ MAIN GRID ═══ */}
{/* ══ LEFT PANEL ══ */}