"use client";
import { useState, useRef, useCallback, useEffect } from "react";
export default function SplitView({ paragraphs, classifications, enriched }) {
const leftRef = useRef(null);
const rightRef = useRef(null);
const [syncing, setSyncing] = useState(false);
const [hoveredIndex, setHoveredIndex] = useState(null);
// Build a lookup: paragraph index -> classification
const classMap = new Map();
if (classifications && classifications.length > 0) {
for (const c of classifications) {
classMap.set(c.index, c);
}
}
// Sync scroll between panels
const handleScroll = useCallback(
(source) => {
if (syncing) return;
setSyncing(true);
const sourceEl = source === "left" ? leftRef.current : rightRef.current;
const targetEl = source === "left" ? rightRef.current : leftRef.current;
if (sourceEl && targetEl) {
const ratio =
sourceEl.scrollTop /
(sourceEl.scrollHeight - sourceEl.clientHeight || 1);
targetEl.scrollTop =
ratio * (targetEl.scrollHeight - targetEl.clientHeight);
}
requestAnimationFrame(() => setSyncing(false));
},
[syncing]
);
// Scroll to a paragraph on click
const scrollToIndex = useCallback((index, panel) => {
const ref = panel === "left" ? leftRef : rightRef;
const el = ref.current?.querySelector(`[data-para-index="${index}"]`);
if (el) {
el.scrollIntoView({ behavior: "smooth", block: "center" });
}
}, []);
return (
{/* Left panel: Original document view */}
handleScroll("left")}
style={{ background: "var(--bg-secondary)" }}
>
{paragraphs.map((p) => (
setHoveredIndex(p.index)}
onMouseLeave={() => setHoveredIndex(null)}
>
{p.text ? (
{p.index}
{p.text}
) : (
{p.index}
(empty)
)}
{p.fontSizePt && (
{p.fontSizePt}pt
{p.isBold ? " • bold" : ""}
)}
))}
{/* Right panel: Enriched view with toggle controls */}
Enriched Document
{enriched && (
Click checkboxes to toggle headings
)}
handleScroll("right")}
style={{ background: "var(--bg-secondary)" }}
>
{!enriched ? (
Click "Enrich with AI" to start
The AI will detect which paragraphs are headings
) : (
)}
);
}
function EnrichedParagraphs({
paragraphs,
classifications,
classMap,
hoveredIndex,
setHoveredIndex,
}) {
// We maintain local state for checkbox toggling
// This lets users toggle headings before clicking "Apply Changes"
const [localClassifications, setLocalClassifications] = useState(classifications);
// Sync when parent classifications change (after enrich or apply)
useEffect(() => {
setLocalClassifications(classifications);
}, [classifications]);
const localClassMap = new Map();
for (const c of localClassifications) {
localClassMap.set(c.index, c);
}
const toggleHeading = (index) => {
setLocalClassifications((prev) =>
prev.map((c) =>
c.index === index ? { ...c, isHeading: !c.isHeading } : c
)
);
// Also update the parent's classifications directly so "Apply Changes" picks them up
const existing = classMap.get(index);
if (existing) {
existing.isHeading = !existing.isHeading;
}
};
return (
<>
{paragraphs.map((p) => {
const cls = localClassMap.get(p.index);
const isHeading = cls?.isHeading || false;
return (
setHoveredIndex(p.index)}
onMouseLeave={() => setHoveredIndex(null)}
>
toggleHeading(p.index)}
title={isHeading ? "Unmark as heading" : "Mark as heading"}
/>
{p.text ? (
{p.index}
{p.text}
) : (
{p.index}
(empty)
)}
{isHeading && (
✦ Heading — font size will be increased
)}
);
})}
>
);
}