jayvatliq's picture
Upload folder using huggingface_hub
16533c4 verified
Raw
History Blame Contribute Delete
9.5 kB
import type { AngleSummary, AngleLabel } from "@/types";
import { CheckCircle2, XCircle } from "lucide-react";
interface CarAngleDiagramProps {
summary: AngleSummary[];
}
type PositionKey =
| "Front View"
| "Rear View"
| "Driver Side View"
| "Passenger Side View"
| "Front Driver Side Corner View"
| "Front Passenger Side Corner View"
| "Rear Driver Side Corner View"
| "Rear Passenger Side Corner View";
const POSITIONS: Record<PositionKey, { x: number; y: number; labelX: number; labelY: number; anchor: "start" | "middle" | "end" }> = {
"Front View": { x: 180, y: 52, labelX: 180, labelY: 18, anchor: "middle" },
"Front Driver Side Corner View": { x: 72, y: 100, labelX: 20, labelY: 90, anchor: "middle" },
"Front Passenger Side Corner View": { x: 288, y: 100, labelX: 340, labelY: 90, anchor: "middle" },
"Driver Side View": { x: 42, y: 192, labelX: -18, labelY: 192, anchor: "end" },
"Passenger Side View": { x: 318, y: 192, labelX: 378, labelY: 192, anchor: "start" },
"Rear Driver Side Corner View": { x: 72, y: 284, labelX: 20, labelY: 296, anchor: "middle" },
"Rear Passenger Side Corner View": { x: 288, y: 284, labelX: 340, labelY: 296, anchor: "middle" },
"Rear View": { x: 180, y: 332, labelX: 180, labelY: 368, anchor: "middle" },
};
const SHORT: Record<PositionKey, string[]> = {
"Front View": ["Front"],
"Rear View": ["Rear"],
"Driver Side View": ["Driver", "Side"],
"Passenger Side View": ["Passenger", "Side"],
"Front Driver Side Corner View": ["Front-Left"],
"Front Passenger Side Corner View": ["Front-Right"],
"Rear Driver Side Corner View": ["Rear-Left"],
"Rear Passenger Side Corner View": ["Rear-Right"],
};
const summaryByAngle = (summary: AngleSummary[], angle: AngleLabel) =>
summary.find((s) => s.angle === angle);
export function CarAngleDiagram({ summary }: CarAngleDiagramProps) {
const detected = summary.filter((s) => s.detected).length;
const total = summary.length;
return (
<div className="flex flex-col items-center gap-6">
{/* Score ring */}
<div className="flex items-center gap-4">
<div className="relative w-20 h-20">
<svg viewBox="0 0 80 80" className="w-full h-full -rotate-90">
<circle cx="40" cy="40" r="32" fill="none" stroke="rgba(255,255,255,0.06)" strokeWidth="8" />
<circle
cx="40" cy="40" r="32"
fill="none"
stroke={detected === total ? "#10b981" : detected > 0 ? "#f59e0b" : "#ef4444"}
strokeWidth="8"
strokeDasharray={`${(detected / total) * 201.06} 201.06`}
strokeLinecap="round"
className="transition-all duration-700"
/>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center">
<span className="text-xl font-bold text-white leading-none">{detected}</span>
<span className="text-xs text-slate-400">/{total}</span>
</div>
</div>
<div>
<p className="text-white font-semibold text-lg">
{detected === total
? "All angles captured!"
: detected === 0
? "No angles detected"
: `${detected} of ${total} angles found`}
</p>
<p className="text-slate-400 text-sm">
{total - detected === 0
? "Perfect coverage"
: `${total - detected} angle${total - detected !== 1 ? "s" : ""} missing`}
</p>
</div>
</div>
{/* SVG Car Diagram */}
<div className="w-full max-w-[420px]">
<svg
viewBox="-55 0 470 390"
className="w-full h-auto"
xmlns="http://www.w3.org/2000/svg"
>
{/* Dashed guide lines from car center to angle points */}
{(Object.keys(POSITIONS) as PositionKey[]).map((angle) => {
const pos = POSITIONS[angle];
const s = summaryByAngle(summary, angle as AngleLabel);
const color = s?.detected ? "#10b981" : "#ef4444";
return (
<line
key={angle}
x1="180" y1="192"
x2={pos.x} y2={pos.y}
stroke={color}
strokeWidth="1"
strokeDasharray="4 4"
opacity="0.35"
/>
);
})}
{/* Car top-down SVG silhouette */}
<g transform="translate(100, 90)">
{/* Car body */}
<rect x="0" y="8" width="160" height="200" rx="20" ry="20" fill="rgba(30,37,48,0.9)" stroke="rgba(255,255,255,0.12)" strokeWidth="1.5" />
{/* Hood */}
<rect x="16" y="0" width="128" height="30" rx="12" ry="12" fill="rgba(37,45,58,0.95)" stroke="rgba(255,255,255,0.1)" strokeWidth="1" />
{/* Trunk */}
<rect x="16" y="186" width="128" height="30" rx="12" ry="12" fill="rgba(37,45,58,0.95)" stroke="rgba(255,255,255,0.1)" strokeWidth="1" />
{/* Windshield */}
<rect x="22" y="30" width="116" height="40" rx="4" fill="rgba(26,107,255,0.12)" stroke="rgba(26,107,255,0.3)" strokeWidth="1" />
{/* Rear windshield */}
<rect x="22" y="146" width="116" height="40" rx="4" fill="rgba(26,107,255,0.08)" stroke="rgba(26,107,255,0.2)" strokeWidth="1" />
{/* Doors center line */}
<line x1="80" y1="70" x2="80" y2="146" stroke="rgba(255,255,255,0.06)" strokeWidth="1" />
{/* Side mirrors */}
<rect x="-14" y="38" width="14" height="20" rx="3" fill="rgba(37,45,58,0.95)" stroke="rgba(255,255,255,0.1)" strokeWidth="1" />
<rect x="160" y="38" width="14" height="20" rx="3" fill="rgba(37,45,58,0.95)" stroke="rgba(255,255,255,0.1)" strokeWidth="1" />
{/* Wheels */}
<ellipse cx="8" cy="58" rx="12" ry="16" fill="rgba(15,18,24,0.9)" stroke="rgba(255,255,255,0.15)" strokeWidth="1.5" />
<ellipse cx="152" cy="58" rx="12" ry="16" fill="rgba(15,18,24,0.9)" stroke="rgba(255,255,255,0.15)" strokeWidth="1.5" />
<ellipse cx="8" cy="158" rx="12" ry="16" fill="rgba(15,18,24,0.9)" stroke="rgba(255,255,255,0.15)" strokeWidth="1.5" />
<ellipse cx="152" cy="158" rx="12" ry="16" fill="rgba(15,18,24,0.9)" stroke="rgba(255,255,255,0.15)" strokeWidth="1.5" />
{/* Headlights */}
<rect x="18" y="4" width="34" height="10" rx="3" fill="rgba(255,240,150,0.25)" stroke="rgba(255,220,50,0.5)" strokeWidth="1" />
<rect x="108" y="4" width="34" height="10" rx="3" fill="rgba(255,240,150,0.25)" stroke="rgba(255,220,50,0.5)" strokeWidth="1" />
{/* Tail lights */}
<rect x="18" y="202" width="34" height="10" rx="3" fill="rgba(255,80,80,0.2)" stroke="rgba(255,80,80,0.5)" strokeWidth="1" />
<rect x="108" y="202" width="34" height="10" rx="3" fill="rgba(255,80,80,0.2)" stroke="rgba(255,80,80,0.5)" strokeWidth="1" />
{/* Direction arrow (front indicator) */}
<polygon points="75,0 80,-6 85,0" fill="rgba(26,107,255,0.6)" />
</g>
{/* Angle indicator dots + labels */}
{(Object.keys(POSITIONS) as PositionKey[]).map((angle) => {
const pos = POSITIONS[angle];
const s = summaryByAngle(summary, angle as AngleLabel);
const detected = s?.detected ?? false;
const color = detected ? "#10b981" : "#ef4444";
const bgColor = detected ? "rgba(16,185,129,0.15)" : "rgba(239,68,68,0.15)";
const lines = SHORT[angle];
return (
<g key={angle}>
{/* Dot circle */}
<circle cx={pos.x} cy={pos.y} r="16" fill={bgColor} stroke={color} strokeWidth="1.5" />
{detected ? (
<CheckCircle2
x={pos.x - 9} y={pos.y - 9}
width="18" height="18"
color="#10b981"
strokeWidth={2}
/>
) : (
<XCircle
x={pos.x - 9} y={pos.y - 9}
width="18" height="18"
color="#ef4444"
strokeWidth={2}
/>
)}
{/* Label */}
{lines.map((line, i) => (
<text
key={i}
x={pos.labelX}
y={pos.labelY + i * 13}
textAnchor={pos.anchor}
fill={color}
fontSize="10"
fontWeight="600"
fontFamily="Inter, sans-serif"
>
{line}
</text>
))}
</g>
);
})}
</svg>
</div>
{/* Legend */}
<div className="flex items-center gap-6 text-sm">
<div className="flex items-center gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400" />
<span className="text-slate-300">Detected</span>
</div>
<div className="flex items-center gap-2">
<XCircle className="w-4 h-4 text-red-400" />
<span className="text-slate-300">Missing</span>
</div>
</div>
</div>
);
}