'use client'; import React, { useState } from 'react'; import { toast } from 'react-hot-toast'; import { Camera, Check, RefreshCw, X, Image as ImageIcon, Sparkles, Hash, Layout, Layers, Package, ChevronDown } from 'lucide-react'; import { useAIExtraction } from '@/hooks/useAIExtraction'; import { ImageAdjustmentModal } from './ImageAdjustmentModal'; interface AIOnboardingProps { onCancel: () => void; onComplete: (itemData: any) => void; categories: any[]; inventory: any[]; } export default function AIOnboarding({ onCancel, onComplete, categories, inventory }: AIOnboardingProps) { const [showImageAdjustment, setShowImageAdjustment] = useState(false); const [adjustingItemIndex, setAdjustingItemIndex] = useState(null); const [pendingItemData, setPendingItemData] = useState(null); const { image, setImage, uploading, extractedItems, setExtractedItems, editingIndex, setEditingIndex, mode, setMode, isLive, videoRef, canvasRef, fileInputRef, existingTypes, existingBoxes, extractedImageBlob, setExtractedImageBlob, startLiveCamera, stopLiveCamera, captureSnapshot, processImage, confirmSingleItem: hookConfirmSingleItem, confirmAllItems: hookConfirmAllItems, updateEditingItem, handleFileChange } = useAIExtraction(inventory, onComplete); const confirmSingleItem = (index: number) => { if (!extractedItems[index]) return; const data = extractedItems[index]; const skipPhoto = data._skipPhoto === true; if (skipPhoto || !image) { hookConfirmSingleItem(index); return; } setPendingItemData({ index, data, skipPhoto }); setAdjustingItemIndex(index); setShowImageAdjustment(true); }; const handleImageAdjustmentConfirm = async (adjustments: any) => { if (!pendingItemData) return; const { index } = pendingItemData; console.log('=== AIONBOARDING ADJUSTMENT CONFIRM ==='); console.log('[Input] Adjustments from modal:', adjustments); console.log('[Input] Current item index:', index); console.log('[Input] Current extractedItems[index]:', extractedItems[index]); console.log('[Current] extractedImageBlob before:', extractedImageBlob?.size || 'null', 'bytes'); if (adjustments && extractedItems[index]) { // UPDATE IMMEDIATELY - don't use setState, update the array directly // because hookConfirmSingleItem needs to see the updated values synchronously const updatedItems = [...extractedItems]; updatedItems[index] = { ...updatedItems[index], image_processing: { ...updatedItems[index].image_processing, rotation_degrees: adjustments.rotation, crop_bounds: { x: Math.round(adjustments.cropBounds.x), y: Math.round(adjustments.cropBounds.y), width: Math.round(adjustments.cropBounds.width), height: Math.round(adjustments.cropBounds.height) }, user_adjusted: true } }; console.log('[Update] Updated item_processing:', updatedItems[index].image_processing); // CRITICAL: Update state AND processed blob BEFORE calling hook setExtractedItems(updatedItems); if (adjustments.imageBlob) { console.log('[Update] Setting extractedImageBlob to processed blob:', adjustments.imageBlob.size, 'bytes'); setExtractedImageBlob(adjustments.imageBlob); } // CRITICAL: Call hook with UPDATED item, not old one console.log('[Action] Calling hookConfirmSingleItem with UPDATED item'); console.log('[Action] Updated item:', updatedItems[index]); // We need to manually call the hook logic here since we can't wait for state // Build the item exactly as the hook does, but with updated values const data = updatedItems[index]; const skipPhoto = data._skipPhoto === true; const newItem = { name: String(data.Item || data.name || "New AI Item"), category: String(data.Category || data.category || "Uncategorized"), type: data.Type || data.type ? String(data.Type || data.type) : null, part_number: data.PartNr || data.part_number ? String(data.PartNr || data.part_number) : null, color: data.Color || data.color ? String(data.Color || data.color) : null, description: String(data.Description || data.description || ""), connector: data.Connector || data.connector ? String(data.Connector || data.connector) : null, size: data.Size || data.size ? String(data.Size || data.size) : null, ocr_text: data.OCR || data.ocr_text ? String(data.OCR || data.ocr_text) : null, specs: String(data.specs || ""), barcode: String(data.barcode || data.PartNr || data.part_number || `AI-${Date.now()}-${index}`), quantity: parseFloat(String(data.quantity || 1)), min_quantity: 1.0, box_label: data.box_label ? String(data.box_label) : null, labels_data: JSON.stringify(data), ...(skipPhoto ? {} : { extractedImageBlob: adjustments.imageBlob || extractedImageBlob, imageProcessing: data.image_processing }) }; console.log('[Final] newItem with ADJUSTED values:', newItem); console.log('[Final] imageProcessing in final item:', newItem.imageProcessing); onComplete(newItem); // Clean up extracted items const remaining = [...updatedItems]; remaining.splice(index, 1); setExtractedItems(remaining.length > 0 ? remaining : []); } else { hookConfirmSingleItem(index); } console.log('=== END CONFIRM DEBUG ==='); setShowImageAdjustment(false); setAdjustingItemIndex(null); setPendingItemData(null); }; const handleImageAdjustmentCancel = () => { setShowImageAdjustment(false); setAdjustingItemIndex(null); setPendingItemData(null); }; const confirmAllItems = async () => { await hookConfirmAllItems(); onCancel(); // Close modal after bulk completion }; return (

AI Discovery

Powered by Gemini 2.0 Flash

{!image && !isLive ? (

{mode === 'box' ? 'Deep Box Analysis' : 'Multi-Item Extraction'}

{mode === 'box' ? 'Scan container labels to identify storage locations' : 'Identify multiple technical items from a single photo or label'}

) : isLive ? ( // LIVE VIEWFINDER MODE