feat(frontend): wire Vite app to FastAPI parser with NDJSON streaming + Upload page
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
interface SparklineProps {
|
||||
values: number[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal SVG sparkline. Normalises to its own range, draws a stroked line
|
||||
* with a soft fill, and marks the last point. Stretch-friendly via
|
||||
* preserveAspectRatio="none" + non-scaling-stroke.
|
||||
*/
|
||||
export function Sparkline({ values, className }: SparklineProps) {
|
||||
if (!values || values.length < 2) return null;
|
||||
const w = 100;
|
||||
const h = 28;
|
||||
const min = Math.min(...values);
|
||||
const max = Math.max(...values);
|
||||
const range = max - min || 1;
|
||||
|
||||
const pts = values.map((v, i) => {
|
||||
const x = (i / (values.length - 1)) * w;
|
||||
const y = h - ((v - min) / range) * (h - 4) - 2;
|
||||
return [x, y] as const;
|
||||
});
|
||||
|
||||
const line = pts
|
||||
.map((p, i) => `${i === 0 ? "M" : "L"}${p[0].toFixed(2)} ${p[1].toFixed(2)}`)
|
||||
.join(" ");
|
||||
const area = `${line} L ${w} ${h} L 0 ${h} Z`;
|
||||
const last = pts[pts.length - 1]!;
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
viewBox={`0 0 ${w} ${h}`}
|
||||
preserveAspectRatio="none"
|
||||
width="100%"
|
||||
height="36"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d={area} fill="hsl(var(--accent) / 0.13)" />
|
||||
<path
|
||||
d={line}
|
||||
fill="none"
|
||||
stroke="hsl(var(--accent))"
|
||||
strokeWidth="1.4"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
vectorEffect="non-scaling-stroke"
|
||||
/>
|
||||
<circle
|
||||
cx={last[0]}
|
||||
cy={last[1]}
|
||||
r="2"
|
||||
fill="hsl(var(--accent))"
|
||||
vectorEffect="non-scaling-stroke"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user