"use client"; import { useState } from "react"; import { CircleNotch as Loader2, Flask as Beaker, Warning as AlertTriangle, Check } from "@phosphor-icons/react"; import { computeProTox, type ProToxResult } from "@/lib/api"; const GREEN = "#4ADE80"; const AMBER = "#FBBF24"; const ORANGE = "#FB923C"; const RED = "#F87171"; const VIOLET = "#A78BFA"; const SLATE = "#848CA4"; const MODEL_LABELS: Record = { dili: "Hepatotoxicity", neuro: "Neurotoxicity", nephro: "Nephrotoxicity", respi: "Respiratory", cardio: "Cardiotoxicity", carcino: "Carcinogenicity", immuno: "Immunotoxicity", mutagen: "Mutagenicity", cyto: "Cytotoxicity", bbb: "BBB barrier", eco: "Ecotoxicity", clinical: "Clinical", nutri: "Nutritional", nr_ahr: "AhR", nr_ar: "AR", nr_ar_lbd: "AR-LBD", nr_aromatase: "Aromatase", nr_er: "ER-α", nr_er_lbd: "ER-LBD", nr_ppar_gamma: "PPAR-γ", sr_are: "Nrf2/ARE", sr_hse: "HSE", sr_mmp: "MMP", sr_p53: "p53", sr_atad5: "ATAD5", mie_thr_alpha: "THR-α", mie_thr_beta: "THR-β", mie_ttr: "TTR", mie_ryr: "RYR", mie_gabar: "GABA-A R", mie_nmdar: "NMDA R", mie_ampar: "AMPA R", mie_kar: "KA R", mie_ache: "AChE", mie_car: "CAR", mie_pxr: "PXR", mie_nadhox: "NADH ox", mie_vgsc: "VGSC", mie_nis: "NIS", }; function cellKey(name: string): string { return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, ""); } function findValue(row: Record, keys: string[]): string { const entry = Object.entries(row).find(([k, v]) => v && keys.includes(cellKey(k))); return entry ? entry[1] : ""; } function ToxLabel({ active }: { active: string }) { const a = active.toLowerCase(); if (["1", "active", "toxic", "true"].includes(a)) { return Active; } if (["0", "inactive", "false", "nontoxic"].includes(a)) { return Inactive; } return {active}; } function ClassBadge({ cls }: { cls: string }) { const num = parseInt(cls, 10); const cfg = [ { label: "Class VI (non-toxic)", color: GREEN }, { label: "Class V", color: "#86EFAC" }, { label: "Class IV", color: AMBER }, { label: "Class III", color: ORANGE }, { label: "Class II", color: "#F87171" }, { label: "Class I (highly toxic)", color: RED }, ][(num && num >= 1 && num <= 6 ? num : 0) - 1]; if (!cfg) return {cls}; return {cfg.label}; } function ProbBar({ value }: { value: string }) { const num = parseFloat(value); if (isNaN(num)) return {value}; const v = Math.min(100, Math.max(0, num)); const pct = v > 1 && num <= 100 ? v : v * 100; const color = num <= 100 && v > 1 ? (v >= 65 ? RED : v >= 45 ? AMBER : GREEN) : (pct >= 65 ? RED : pct >= 45 ? AMBER : GREEN); return (
{value}
); } function GenericTable({ rows, kind }: { rows: Record[]; kind: "model" | "target" }) { if (!rows || rows.length === 0) return

No rows returned.

; const allKeys = Array.from(new Set(rows.flatMap((r) => Object.keys(r)))); const labelKey = allKeys.find((k) => cellKey(k).includes("target") || cellKey(k).includes("name")) || allKeys[0]; const predKey = allKeys.find((k) => cellKey(k).includes("prediction")) || ""; const probKey = allKeys.find((k) => cellKey(k).includes("prob") || cellKey(k).includes("conf") || cellKey(k).includes("similar")) || ""; return ( {probKey && } {rows.map((row, i) => { const label = row[labelKey] ?? ""; const labelDisplay = kind === "model" && MODEL_LABELS[cellKey(label)] ? MODEL_LABELS[cellKey(label)] : label; const pred = predKey ? (row[predKey] ?? "") : ""; const prob = probKey ? (row[probKey] ?? "") : ""; return ( {probKey && } ); })}
Target PredictionConfidence
{labelDisplay} {kind === "model" ? : {pred || "—"}}{prob ? : "—"}
); } function AcuteCard({ acute, name }: { acute: Record; name: string }) { const entries = Object.entries(acute).filter(([, v]) => v); if (entries.length === 0) return null; const ld50 = findValue(acute, ["ld50", "predicted_ld50"]); const cls = findValue(acute, ["tox_class", "class"]); const sim = findValue(acute, ["similarity", "avg_similarity"]); return (

Acute toxicity

Predicted LD50
{ld50 || "—"}
mg/kg (rat, oral)
Toxicity class
Globally Harmonized System (1–6)
Avg. similarity
{sim || "—"}
to training compounds
{name &&

Compound: {name}

}
); } export default function ProToxView({ smiles, name }: { smiles: string; name?: string }) { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const run = async () => { setError(""); setData(null); setLoading(true); try { const res = await computeProTox({ smiles: smiles || undefined, name: name || undefined }); setData(res.result); } catch (e: unknown) { const msg = e instanceof Error ? e.message : "Unknown error"; const detail = msg.includes("502") ? "ProTox server is currently unreachable or the daily quota is exhausted. Try again later." : msg; setError(detail); } finally { setLoading(false); } }; const modelCount = data ? data.requested_models.split(/\s+/).length : 0; return (

ProTox 3.0 toxicity prediction

Real ML prediction from Charité (61 endpoints): acute toxicity (LD50), organ toxicity, Tox21 pathways, targets & CYP inhibition. Queued upstream — a run can take up to ~5 min.

{error && (

{error}

)} {loading && (
Submitting to ProTox 3.0…
The Charité server queues requests; this usually takes 30 s – 5 min.
)}
{data && ( <>
task {data.task_id} {modelCount} model groups · {data.chemical_name || data.input}
{(data.model_results?.length ?? 0) > 0 && (

Organ toxicity · Endpoints · CYPs

)} {(data.toxicity_targets?.length ?? 0) > 0 && (

Toxicity targets

)} {data.methodology && (
METHODOLOGY

{data.methodology.method}

{data.methodology.note}

)} )}
); }