/** * components/CorrectionPanel.jsx * ─────────────────────────────── * Right panel. Features: * - Skeleton loader during processing * - Word-level diff highlighting * - 3D flip card: front = correction, back = refinement * - Metric chips, confidence badge, copy, "Use as Input" */ import { useMemo } from 'react' import { motion, AnimatePresence } from 'framer-motion' import toast from 'react-hot-toast' // ── Word diff renderer ── function renderDiff(original, diffs) { if (!diffs || diffs.length === 0) return {original} const changeMap = {} for (const d of diffs) { if (d.original || d.corrected) changeMap[d.position] = d } const tokens = original.split(/(\s+)/) let idx = 0 const nodes = [] tokens.forEach((tok, i) => { if (/^\s+$/.test(tok)) { nodes.push(tok); return } const ch = changeMap[idx] if (ch) { const cls = ch.type === 'homophone' ? 'diff-hom' : 'diff-del' nodes.push({ch.original || tok}) if (ch.corrected) { nodes.push(' ') nodes.push({ch.corrected}) } } else { nodes.push(tok) } idx++ }) return <>{nodes} } // ── Confidence badge ── function ConfBadge({ value }) { const pct = Math.round(value * 100) const color = pct >= 85 ? 'var(--green)' : pct >= 65 ? 'var(--amber)' : 'var(--red)' const bg = pct >= 85 ? 'var(--green-soft)' : pct >= 65 ? 'var(--amber-soft)' : 'var(--red-soft)' return ( {pct}% confidence ) } // ── Skeleton ── function Skeleton({ lines = [88, 72, 80, 55, 68] }) { return ( {lines.map((w, i) => ( ))} ) } // ── Copy util ── function copyText(text) { navigator.clipboard.writeText(text).then(() => { toast.success('Copied to clipboard', { style: { background: 'var(--green-soft)', color: 'var(--green)', border: '1px solid var(--green)', fontFamily: "'DM Sans',sans-serif", fontSize: '13px', }, }) }) } // ── Card header ── function PanelHeader({ title, tagLabel, tagClass, copyValue, extra }) { return (
{title} {tagLabel}
{extra} {copyValue && ( copyText(copyValue)} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} > ⎘ Copy )}
) } // ── Empty state ── function EmptyState() { return (

Awaiting Text Pipeline

Insert text on the left and run diagnostics to see engine evaluations.

) } // ── Correction face (front) ── function CorrectionFace({ result, loading, onFlipHint }) { const diffNode = useMemo(() => result ? renderDiff(result.original, result.diffs) : null, [result]) if (loading) return <> if (!result) return return ( <>
{result.spell_fixed > 0 && ( {result.spell_fixed} spelling )} {result.grammar_fixed > 0 && ( {result.grammar_fixed} grammar )} {result.homophone_fixed > 0 && ( {result.homophone_fixed} homophone )}
{diffNode}
{result.corrected}
{onFlipHint && (
Click Refine Text to synthesize a stylistic rewrite →
)} ) } // ── Refinement face (back) ── function RefinementFace({ result, loading }) { if (loading) return <> if (!result) return null return ( <> { window.dispatchEvent(new CustomEvent('wr:loadText', { detail: result.refined })) toast.success('Loaded into editor', { style: { background: 'var(--purple-soft)', color: 'var(--purple)', border: '1px solid var(--purple)', fontFamily: "'DM Sans',sans-serif", fontSize: '13px' } }) }} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} > ↑ Use as Input } /> {result.improvements?.length > 0 && (
{result.improvements.map((imp, i) => ( {imp} ))}
)} {result.refined}
T5 Transformer Output ✦ AI-enhanced
) } // ── Main export ── export default function CorrectionPanel({ correctionResult, refineResult, correctionLoading, refineLoading }) { const isFlipped = Boolean(refineResult || refineLoading) return ( {isFlipped && ( Viewing refined output · card flipped 180° )}
{isFlipped && correctionResult && ( window.dispatchEvent(new CustomEvent('wr:backToCorrection'))} whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.95 }} > ← Back to diagnostics )}
) }