"use client"; import { useEffect, useState, useRef } from "react"; import { downloadTsv, exportSvgPng } from "@/lib/export-utils"; import { useAuditTrail } from "@/hooks/useAuditTrail"; type StructureMatch = { pdb_id: string; chain: string; description: string; tm_score: number; rmsd: number; seq_identity: number; aligned_length: number; }; export function StructureComparison({ pdbId, chain = "A" }: { pdbId: string; chain?: string }) { const audit = useAuditTrail(); const [matches, setMatches] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const svgRef = useRef(null); const auditedRef = useRef(false); useEffect(() => { if (!pdbId) return; auditedRef.current = false; setLoading(true); setError(null); setMatches([]); const controller = new AbortController(); fetch(`/api/backend/api/structure_analysis/compare/${pdbId}?chain=${chain}`, { signal: controller.signal }) .then(r => r.ok ? r.json() : r.json().then(d => { throw new Error(d.detail); })) .then(d => { if (!controller.signal.aborted) { setMatches(d.matches); if (!auditedRef.current) { auditedRef.current = true; audit.emitSuccess('struct_compare', 'PDBeFold', pdbId, `${d.matches?.length || 0} matches`); } } }) .catch(e => { if (!controller.signal.aborted) { setError(e.message); audit.emitFailed('struct_compare', 'PDBeFold', pdbId, e.message); } }) .finally(() => { if (!controller.signal.aborted) setLoading(false); }); return () => controller.abort(); }, [pdbId, chain]); if (loading) return
Searching structural homologs (PDBeFold)…
; if (error) return
{error}
; return (

Structural Homologs

{matches.length} hits · PDBeFold
TM-score (0→1) TM > 0.5 = same fold
{matches.map((m, i) => (
{m.pdb_id}:{m.chain}
0.5 ? "#4ADE80" : m.tm_score > 0.3 ? "#FBBF24" : "#EF4444", opacity: 0.7, }} />
{m.tm_score.toFixed(3)}
))}
{["PDB", "Chain", "RMSD", "Seq ID%", "Aligned", "Description"].map(h => ( ))} {matches.map((m, i) => ( ))}
{h}
{m.pdb_id} {m.chain} {m.rmsd.toFixed(2)}Å {(m.seq_identity * 100).toFixed(1)}% {m.aligned_length} aa {m.description}
); }