jayvatliq's picture
Upload folder using huggingface_hub
16533c4 verified
Raw
History Blame Contribute Delete
5.55 kB
import { useState } from "react";
import { ChevronDown, ChevronUp, Eye } from "lucide-react";
import type { ImageResult } from "@/types";
import { getImageUrl } from "@/services/api";
interface ImageResultCardProps {
result: ImageResult;
index: number;
}
export function ImageResultCard({ result, index }: ImageResultCardProps) {
const [expanded, setExpanded] = useState(false);
const hasError = !!result.error;
return (
<div
className={[
"rounded-xl border transition-all duration-200",
hasError
? "border-red-500/20 bg-red-500/5"
: "border-surface-500 bg-surface-700 hover:border-surface-400",
].join(" ")}
>
<div
className="flex items-center gap-4 p-4 cursor-pointer"
onClick={() => setExpanded((e) => !e)}
>
{/* Index badge */}
<div className="w-8 h-8 rounded-lg bg-surface-600 flex items-center justify-center flex-shrink-0">
<span className="text-xs font-mono text-slate-400">{index + 1}</span>
</div>
{/* Preview thumbnail */}
{result.previewUrl && (
<div className="w-12 h-12 rounded-lg overflow-hidden flex-shrink-0 border border-surface-500">
<img
src={result.previewUrl}
alt={result.filename}
className="w-full h-full object-cover"
/>
</div>
)}
{/* Name + status */}
<div className="flex-1 min-w-0">
<p className="font-medium text-white text-sm truncate">{result.filename}</p>
{hasError ? (
<p className="text-red-400 text-xs mt-0.5">
{typeof result.error === "string" ? result.error : JSON.stringify(result.error)}
</p>
) : (
<div className="flex items-center gap-2 mt-1 flex-wrap">
{result.angles.map((angle, i) => (
<span
key={i}
className="text-xs px-2 py-0.5 rounded-full bg-brand-500/15 text-brand-400 font-medium border border-brand-500/20"
>
{angle}
</span>
))}
{result.review && (
<span className="text-xs px-2 py-0.5 rounded-full bg-amber-500/15 text-amber-400 font-medium border border-amber-500/20">
Needs review
</span>
)}
</div>
)}
</div>
{/* Expand */}
<div className="flex-shrink-0 text-slate-500">
{expanded ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
</div>
</div>
{/* Expanded view */}
{expanded && !hasError && (
<div className="px-4 pb-4 border-t border-surface-600 pt-4 animate-fade-in space-y-4">
{/* Local preview always shown */}
{result.previewUrl && (
<div>
<p className="text-xs text-slate-400 mb-2 font-medium uppercase tracking-wider">
Uploaded Image
</p>
<div className="rounded-lg overflow-hidden border border-surface-500 max-w-sm">
<img
src={result.previewUrl}
alt={result.filename}
className="w-full object-contain max-h-64 bg-surface-900"
/>
</div>
</div>
)}
{/* Backend detection overlays (only when available) */}
{result.fileDetails && (
<div>
<p className="text-xs text-slate-400 mb-2 font-medium uppercase tracking-wider">
Detection Outputs
</p>
<div className="grid grid-cols-3 gap-3">
{[
{ label: "Original", path: result.fileDetails.main_img_name },
{ label: "Car Detection", path: result.fileDetails.car_detection },
{ label: "Part Detection", path: result.fileDetails.part_detection },
].map(({ label, path }) => (
<a
key={label}
href={getImageUrl(path)}
target="_blank"
rel="noopener noreferrer"
className="group relative rounded-lg overflow-hidden border border-surface-500 hover:border-brand-500/50 transition-colors block"
>
<div className="aspect-square bg-surface-800">
<img
src={getImageUrl(path)}
alt={label}
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
onError={(e) => {
e.currentTarget.style.display = "none";
}}
/>
</div>
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex items-end p-2">
<span className="text-white text-xs font-medium flex items-center gap-1">
<Eye className="w-3 h-3" /> {label}
</span>
</div>
<p className="text-center text-xs text-slate-400 py-1.5 bg-surface-800">
{label}
</p>
</a>
))}
</div>
</div>
)}
</div>
)}
</div>
);
}