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:
2026-06-25 08:21:46 +02:00
parent c95093a81f
commit eec9bd5dce
5 changed files with 184 additions and 57 deletions
+41 -13
View File
@@ -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"