import { useEffect, useRef, useState } from 'react'; import type { ModelCatalog, ModelRequest, ModelResult } from '@her2/shared'; import { describeFailure } from './boundaries'; import { fetchModelCatalog, submitModelRun } from './modelBoundaries'; import { LiveInference } from './LiveInference'; const display = (value: number | null | undefined, digits = 2) => value === null || value === undefined ? 'Not measured' : value.toFixed(digits); function MoleculeImage({ svg, name }: { svg: string | null; name: string }) { const [url, setUrl] = useState(); useEffect(() => { if (!svg) { setUrl(undefined); return; } const next = URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' })); setUrl(next); return () => URL.revokeObjectURL(next); }, [svg]); return url ? {`Molecular :
No molecular graph: output is not a plain, valid SMILES.
; } export function ModelLab() { const [catalog, setCatalog] = useState(null); const [failure, setFailure] = useState(null); const [product, setProduct] = useState('DRG0ERKBH'); const [molecule, setMolecule] = useState('control-01'); const [policy, setPolicy] = useState('all'); const [confirmed, setConfirmed] = useState(false); const [busy, setBusy] = useState(false); const [result, setResult] = useState(null); const active = useRef(null); useEffect(() => { const controller = new AbortController(); void fetchModelCatalog(controller.signal).then(data => { if (!controller.signal.aborted) setCatalog(data); }).catch(cause => { if (!controller.signal.aborted) setFailure(describeFailure(cause).message); }); return () => { controller.abort(); active.current?.abort(); }; }, []); function reset() { active.current?.abort(); active.current = null; setBusy(false); setResult(null); setFailure(null); } async function run(id = molecule) { if (!confirmed || !catalog) return; reset(); setMolecule(id); setBusy(true); const controller = new AbortController(); active.current = controller; try { const next = await submitModelRun({ product_id: product, molecule_id: id, observation_policy: policy, synthetic_confirmed: true }, catalog, AbortSignal.any([controller.signal, AbortSignal.timeout(15000)])); if (!controller.signal.aborted && active.current === controller) setResult(next); } catch (cause) { if (!controller.signal.aborted && active.current === controller) setFailure(describeFailure(cause).message); } finally { if (active.current === controller) { active.current = null; setBusy(false); } } } function download() { if (!result) return; const url = URL.createObjectURL(new Blob([JSON.stringify(result, null, 2)], { type: 'application/json' })); const a = document.createElement('a'); a.href = url; a.download = 'conjugate-component-check.json'; a.click(); window.setTimeout(() => URL.revokeObjectURL(url), 1000); } if (!catalog) return
{failure ?

{failure}

:

Loading model observations…

}
; const selected = catalog.snapshot.molecules.find(m => m.id === molecule); const trials = catalog.snapshot.molecules.filter(m => m.adapter_trial); const descriptor = result?.molecule.descriptors; return

Components, not a treatment ranking

Linker lab

A molecule can score well and still fail a basic check. Compare chemistry, antibody sequences and target structure without turning them into an efficacy score.

AntibodyESM-2 650MImported sequence comparisons
MoleculeRDKit + checkpoint probesCalculated properties and graph checks
TargetAlphaFold DBHER2 monomer confidence

Try the shortcut

The proxy says 1.00. The handles are missing.

A clipped QED/SA/ring proxy gives our no-handle control the same score as a two-handle fragment. A separate graph check catches the difference.

{ event.preventDefault(); void run(); }}> {busy && }

Checks use frozen observations from {new Date(catalog.snapshot.generated_at).toLocaleDateString()}. Clicking does not call a model or spend inference credits. Synthetic fragments are not the marketed Kadcyla or Enhertu linkers.

{selected &&

{selected.adapter_trial ? 'Raw adapter output' : 'Synthetic SMILES'}{selected.smiles || '(empty output)'}

} {failure &&

{failure}

} {result &&

{result.checks.filter(c => c.passed).length} of 3 representation checks passed

{result.molecule.name}

Molecule properties

Clipped proxy reward
{display(result.molecule.proxy_reward)}
QED
{display(descriptor?.qed)}
SA score · 1–10
{display(descriptor?.sa_score)}
cLogP
{display(descriptor?.logp)}
MW · g/mol
{display(descriptor?.molecular_weight)}
TPSA · Ų
{display(descriptor?.tpsa)}

Properties use hydrogen-capped fragments. QED and SA are proxies, not measured cleavage or synthesis success.

Antibody sequence

{display(result.sequence?.cosine, 5)}

ESM cosine to the Kadcyla antibody reference

Alignment identity baseline
{result.sequence ? `${(result.sequence.identity * 100).toFixed(1)}%` : 'Withheld'}
Antibody
{result.sequence?.antibody ?? 'Withheld'}

Kadcyla and Enhertu use the same matched variable-domain sequences. This signal cannot distinguish their payloads or rank ADC performance.

HER2 target structure

{display(result.structure?.mean_plddt)}

Mean pLDDT · 0–100 · target monomer only

{result.structure && <> `${i * 400 / 1254},${100 - v}`).join(' ')} />

{(result.structure.below_50_fraction * 100).toFixed(1)}% of residues below pLDDT 50. {result.structure.residue_count} residues, model v{result.structure.version}.

}

Local fold confidence is not antibody–HER2 affinity. No antibody complex or conjugated linker was folded.

{result.checks.map(check =>
{check.passed ? 'Pass' : 'Fail'}
{check.id.replaceAll('_', ' ')}

{check.detail}

)}

What a whole-ADC score would still need

    {result.missing.map(item =>
  • {item}
  • )}

Overall ADC score: not assessed. Passing these graph checks does not make a fragment a usable ADC linker.

Methods and source fingerprints

Snapshot SHA-256: {result.snapshot_sha256}

Sequence artifact SHA-256: {result.sequence_artifact_sha256}

Controller SHA-256: {result.code_sha256}

Hashes are server-reported. Offline replay compares this export with the pinned artifacts, not experimental truth.

{result.sequence &&

ESM: {result.sequence.model_id}, revision {result.sequence.revision}, imported {result.sequence.generated_at}. Mean pooling over VH/VL variable domains; no new ESM inference in this check.

}{result.structure &&

AlphaFold DB confidence file · {result.structure.entry_id} v6 · CC-BY-4.0 · {result.structure.method}. {result.structure.confidence_sha256}

}
}

The HF checkpoint is an experiment, not the ADC paper’s model

jarod0411/linkerGPT has no declared license or documented conditioning format. The published ADC Linker-GPT paper uses a different architecture.

We retained {trials.length} unconditioned format probes at seed {catalog.snapshot.linker_model.seed}, capped at {catalog.snapshot.linker_model.max_new_tokens} new tokens each. They contain fragment markers or incomplete strings; they are not a molecular-validity benchmark or reproduced paper results. No outputs are repaired into candidates.

{trials.filter(m => m.completed_with_eos).length}/{trials.length} reached EOS; {trials.filter(m => m.valid).length}/{trials.length} parsed as plain SMILES. These describe this adapter trial only. NLL measures the generated serialization’s token fit, not molecule quality.

Checkpoint settings
{JSON.stringify(catalog.snapshot.linker_model, null, 2)}

Not run

{catalog.unavailable.map(item =>

{item.name} — {item.reason}

)}
; }