feat: add image confirmation step to AI extraction flow
Users now see the extracted photo during item editing and can choose to: - 'Use Photo': Auto-save the image with the item - 'Skip Photo': Create item without saving the photo Changes: 1. frontend/components/AIOnboarding.tsx: Added image preview panel in edit form - Shows extracted image to user - Buttons to accept/reject photo - Visual feedback when photo is skipped 2. frontend/hooks/useAIExtraction.ts: Updated confirmSingleItem and confirmAllItems - Respect user's photo decision (_skipPhoto flag) - Only pass extractedImageBlob if user approved it - Prevents unwanted auto-photo-save This gives users full control over which extracted photos are auto-saved.
This commit is contained in:
@@ -224,7 +224,40 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
|
||||
Back to List
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Image Preview Section */}
|
||||
{image && (
|
||||
<div className="bg-surface/70 border border-slate-800/50 rounded-2xl overflow-hidden">
|
||||
<div className="relative h-40 md:h-48 bg-black/20 flex items-center justify-center">
|
||||
<img
|
||||
src={image}
|
||||
alt="Extracted label"
|
||||
className="max-w-full max-h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-3 space-y-2">
|
||||
<p className="text-xs text-muted font-normal">Extracted Photo</p>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => updateEditingItem({ _skipPhoto: false })}
|
||||
className="flex-1 px-3 py-2 bg-green-500/20 border border-green-500/50 text-green-400 rounded-lg hover:bg-green-500/30 transition-colors font-normal text-xs"
|
||||
>
|
||||
Use Photo
|
||||
</button>
|
||||
<button
|
||||
onClick={() => updateEditingItem({ _skipPhoto: true })}
|
||||
className="flex-1 px-3 py-2 bg-rose-500/20 border border-rose-500/50 text-rose-400 rounded-lg hover:bg-rose-500/30 transition-colors font-normal text-xs"
|
||||
>
|
||||
Skip Photo
|
||||
</button>
|
||||
</div>
|
||||
{extractedItems[editingIndex]._skipPhoto && (
|
||||
<p className="text-xs text-rose-400/80">Photo will not be saved</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1 overflow-y-auto space-y-2 pr-1 scrollbar-hide">
|
||||
<div data-testid="extracted-name" className="bg-surface py-2.5 px-4 rounded-[1.2rem] border border-slate-800 focus-within:border-primary/50 transition-colors group">
|
||||
<label className="text-xs text-muted font-normal mb-0.5 block group-focus-within:text-primary transition-colors tracking-tight">Item Name</label>
|
||||
|
||||
@@ -133,6 +133,8 @@ export function useAIExtraction(inventory: Item[], onComplete: (itemData: any) =
|
||||
|
||||
const confirmSingleItem = (index: number) => {
|
||||
const data = extractedItems[index];
|
||||
const skipPhoto = data._skipPhoto === true; // User explicitly rejected the photo
|
||||
|
||||
const newItem = {
|
||||
name: String(data.Item || data.name || "New AI Item"),
|
||||
category: String(data.Category || data.category || "Uncategorized"),
|
||||
@@ -149,9 +151,11 @@ export function useAIExtraction(inventory: Item[], onComplete: (itemData: any) =
|
||||
min_quantity: 1.0,
|
||||
box_label: data.box_label ? String(data.box_label) : null,
|
||||
labels_data: JSON.stringify(data),
|
||||
// Pass extracted image blob and image_processing metadata for auto-photo-save
|
||||
extractedImageBlob,
|
||||
imageProcessing: data.image_processing
|
||||
// Only pass image blob if user approved it
|
||||
...(skipPhoto ? {} : {
|
||||
extractedImageBlob,
|
||||
imageProcessing: data.image_processing
|
||||
})
|
||||
};
|
||||
onComplete(newItem);
|
||||
|
||||
@@ -174,6 +178,8 @@ export function useAIExtraction(inventory: Item[], onComplete: (itemData: any) =
|
||||
try {
|
||||
for (let i = 0; i < itemsToProcess.length; i++) {
|
||||
const data = itemsToProcess[i];
|
||||
const skipPhoto = data._skipPhoto === true;
|
||||
|
||||
const newItem = {
|
||||
name: String(data.Item || data.name || "New AI Item"),
|
||||
category: String(data.Category || data.category || "Uncategorized"),
|
||||
@@ -190,9 +196,11 @@ export function useAIExtraction(inventory: Item[], onComplete: (itemData: any) =
|
||||
min_quantity: 1.0,
|
||||
box_label: data.box_label ? String(data.box_label) : null,
|
||||
labels_data: JSON.stringify(data),
|
||||
// Pass extracted image blob and image_processing metadata for auto-photo-save
|
||||
extractedImageBlob,
|
||||
imageProcessing: data.image_processing
|
||||
// Only pass image blob if user approved it
|
||||
...(skipPhoto ? {} : {
|
||||
extractedImageBlob,
|
||||
imageProcessing: data.image_processing
|
||||
})
|
||||
};
|
||||
await onComplete(newItem);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user