Netra / frontend /src /components /RawOutput.jsx
chikentikka's picture
Add violation rules, NETRA UI overhaul, MongoDB challan store, and model weights
f184c04
Raw
History Blame Contribute Delete
2.87 kB
import { useEffect, useRef, useState } from "react";
import { ChevronDown, Copy, Check, TerminalSquare } from "lucide-react";
// A terminal-style panel that "streams" the raw backend JSON line by line —
// surfaces real confidence scores and coordinates for technical credibility.
export default function RawOutput({ result }) {
const [open, setOpen] = useState(true);
const [shown, setShown] = useState(0);
const [copied, setCopied] = useState(false);
const bodyRef = useRef(null);
const json = result ? JSON.stringify(result, null, 2) : "";
const lines = json ? json.split("\n") : [];
// Reveal lines progressively whenever a new result arrives.
useEffect(() => {
if (!result) return;
let intervalId;
const resetId = setTimeout(() => {
setShown(0);
intervalId = setInterval(() => {
setShown((n) => {
if (n >= lines.length) {
clearInterval(intervalId);
return n;
}
return n + 1;
});
}, 28);
}, 0);
return () => {
clearTimeout(resetId);
clearInterval(intervalId);
};
}, [json]); // eslint-disable-line react-hooks/exhaustive-deps
// Keep the newest line in view while streaming.
useEffect(() => {
if (open && bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
}, [shown, open]);
if (!result) return null;
const copy = () => {
navigator.clipboard?.writeText(json);
setCopied(true);
setTimeout(() => setCopied(false), 1400);
};
const streaming = shown < lines.length;
return (
<div className={`raw${open ? " open" : ""}`}>
<div className="raw-bar">
<button className="raw-toggle" onClick={() => setOpen((o) => !o)}>
<span className="raw-dots" aria-hidden>
<i /><i /><i />
</span>
<TerminalSquare size={14} strokeWidth={1.9} />
<span>API response</span>
<span className="raw-meta">
{streaming ? "streaming…" : `${lines.length} lines · ${json.length} bytes`}
</span>
<ChevronDown size={15} strokeWidth={2} className="raw-chevron" />
</button>
<button className="raw-copy" onClick={copy} title="Copy JSON">
{copied ? <Check size={13} strokeWidth={2.2} /> : <Copy size={13} strokeWidth={1.9} />}
{copied ? "Copied" : "Copy"}
</button>
</div>
{open && (
<pre className="raw-body" ref={bodyRef}>
<code>
{lines.slice(0, shown).map((ln, i) => (
<span key={i} className="raw-line">
<span className="raw-ln">{i + 1}</span>
<span className="raw-code">{ln || " "}</span>
</span>
))}
{streaming && <span className="raw-cursor" />}
</code>
</pre>
)}
</div>
);
}