feat: make AI background removal optional with resize-only upload and batch detour selection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,8 @@ import type { ImageItem } from '@/types/images'
|
||||
|
||||
interface ImageCardProps {
|
||||
item: ImageItem
|
||||
selected?: boolean
|
||||
onSelect?: (id: string, selected: boolean) => void
|
||||
onFeatherChange: (id: string, value: number) => void
|
||||
onAlphaThresholdChange: (id: string, value: number) => void
|
||||
onRotate: (id: string, direction: 'cw' | 'ccw') => void
|
||||
@@ -12,19 +14,21 @@ interface ImageCardProps {
|
||||
onOpenLightbox: (item: ImageItem) => void
|
||||
onAssignProduct: (id: string, shopifyProductId: string, shopifyProductTitle: string) => void
|
||||
onReprocess: (id: string) => void
|
||||
onDetour: (id: string) => void
|
||||
}
|
||||
|
||||
const STATUS_LABELS: Record<string, { label: string; color: string }> = {
|
||||
pending: { label: '⏳ En attente', color: 'text-slate-400' },
|
||||
converting: { label: '🔄 Conversion HEIC', color: 'text-yellow-400' },
|
||||
processing: { label: '🤖 Détourage IA', color: 'text-blue-400' },
|
||||
searching: { label: '🔍 Recherche…', color: 'text-purple-400' },
|
||||
ready: { label: '✓ Prêt', color: 'text-green-400' },
|
||||
uploading: { label: '⬆️ Upload…', color: 'text-blue-400' },
|
||||
uploaded: { label: '✅ Uploadé', color: 'text-green-500' },
|
||||
skipped: { label: '⏭️ Déjà sur Shopify', color: 'text-slate-400' },
|
||||
not_found: { label: '❌ Réf. introuvable', color: 'text-red-400' },
|
||||
error: { label: '❌ Erreur', color: 'text-red-400' },
|
||||
pending: { label: '⏳ En attente', color: 'text-slate-400' },
|
||||
converting: { label: '🔄 Conversion HEIC', color: 'text-yellow-400' },
|
||||
searching: { label: '🔍 Recherche…', color: 'text-purple-400' },
|
||||
converted: { label: '✓ Prêt (sans détourage)', color: 'text-amber-400' },
|
||||
processing: { label: '🤖 Détourage IA', color: 'text-blue-400' },
|
||||
ready: { label: '✓ Détouré', color: 'text-green-400' },
|
||||
uploading: { label: '⬆️ Upload…', color: 'text-blue-400' },
|
||||
uploaded: { label: '✅ Uploadé', color: 'text-green-500' },
|
||||
skipped: { label: '⏭️ Déjà sur Shopify', color: 'text-slate-400' },
|
||||
not_found: { label: '❌ Réf. introuvable', color: 'text-red-400' },
|
||||
error: { label: '❌ Erreur', color: 'text-red-400' },
|
||||
}
|
||||
|
||||
function BlobThumbnail({ blob, alt }: { blob: Blob; alt: string }) {
|
||||
@@ -95,6 +99,8 @@ function ProductSearch({ itemId, onAssign }: { itemId: string; onAssign: (id: st
|
||||
|
||||
export function ImageCard({
|
||||
item,
|
||||
selected = false,
|
||||
onSelect,
|
||||
onFeatherChange,
|
||||
onAlphaThresholdChange,
|
||||
onRotate,
|
||||
@@ -103,18 +109,31 @@ export function ImageCard({
|
||||
onOpenLightbox,
|
||||
onAssignProduct,
|
||||
onReprocess,
|
||||
onDetour,
|
||||
}: ImageCardProps) {
|
||||
const statusInfo = STATUS_LABELS[item.status] ?? { label: item.status, color: 'text-slate-400' }
|
||||
const isProcessing = ['pending', 'converting', 'processing', 'searching', 'uploading'].includes(item.status)
|
||||
const canAdjust = !!item.rawPngBlob && !isProcessing
|
||||
const canUpload = (item.status === 'ready' || item.status === 'converted') && !!item.shopifyProductId
|
||||
const canDetour = (item.status === 'converted' || item.status === 'error' || item.status === 'not_found') && !isProcessing
|
||||
|
||||
return (
|
||||
<div className="bg-slate-900 border border-slate-700 rounded-xl overflow-hidden flex flex-col">
|
||||
<div className={`bg-slate-900 border rounded-xl overflow-hidden flex flex-col ${selected ? 'border-indigo-500 ring-1 ring-indigo-500' : 'border-slate-700'}`}>
|
||||
{/* Miniatures avant / après */}
|
||||
<div
|
||||
className="relative h-40 bg-slate-800 cursor-pointer"
|
||||
onClick={() => onOpenLightbox(item)}
|
||||
>
|
||||
{onSelect && (
|
||||
<div className="absolute top-2 left-2 z-10" onClick={e => e.stopPropagation()}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected}
|
||||
onChange={e => onSelect(item.id, e.target.checked)}
|
||||
className="w-4 h-4 accent-indigo-500 cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={`absolute top-0 bottom-0 left-0 overflow-hidden ${item.processedBlob ? 'w-1/2' : 'w-full'}`}>
|
||||
<BlobThumbnail blob={item.originalBlob} alt="Original" />
|
||||
</div>
|
||||
@@ -195,7 +214,7 @@ export function ImageCard({
|
||||
|
||||
{/* Boutons d'action */}
|
||||
<div className="flex gap-2 mt-auto pt-1">
|
||||
{item.status === 'ready' && (
|
||||
{canUpload && (
|
||||
<button
|
||||
onClick={() => onUpload(item.id)}
|
||||
className="flex-1 py-1.5 text-xs bg-indigo-600 hover:bg-indigo-500 text-white font-semibold rounded"
|
||||
@@ -203,7 +222,16 @@ export function ImageCard({
|
||||
⬆️ Uploader
|
||||
</button>
|
||||
)}
|
||||
{(item.status === 'ready' || item.status === 'uploaded' || item.status === 'skipped' || item.status === 'error' || item.status === 'not_found') && item.rawPngBlob && (
|
||||
{canDetour && (
|
||||
<button
|
||||
onClick={() => onDetour(item.id)}
|
||||
title="Lancer le détourage IA"
|
||||
className="flex-1 py-1.5 text-xs bg-purple-700 hover:bg-purple-600 text-white font-semibold rounded"
|
||||
>
|
||||
✂️ Détourer
|
||||
</button>
|
||||
)}
|
||||
{(item.status === 'ready' || item.status === 'uploaded' || item.status === 'skipped' || item.status === 'error') && item.rawPngBlob && (
|
||||
<button
|
||||
onClick={() => onReprocess(item.id)}
|
||||
title="Relancer le détourage IA"
|
||||
|
||||
Reference in New Issue
Block a user