Build [v1.7.0] - GitGuard - Infrastructure Hardening
This commit is contained in:
@@ -16,6 +16,7 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
|
||||
const [image, setImage] = useState<string | null>(null);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [extractedData, setExtractedData] = useState<any>(null);
|
||||
const [mode, setMode] = useState<'item' | 'box'>('item');
|
||||
|
||||
// Extract unique item types for suggestions
|
||||
const existingTypes = Array.from(new Set(inventory.map(i => i.type).filter(Boolean))).sort() as string[];
|
||||
@@ -42,7 +43,7 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
|
||||
const formData = new FormData();
|
||||
formData.append('file', blob, 'label.jpg');
|
||||
|
||||
const data = await inventoryApi.analyzeLabel(formData);
|
||||
const data = await inventoryApi.analyzeLabel(formData, mode);
|
||||
|
||||
if (data.error) {
|
||||
toast.error(`AI Error: ${data.error}`);
|
||||
@@ -51,7 +52,11 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
|
||||
}
|
||||
|
||||
setExtractedData(data);
|
||||
toast.success("AI extraction complete!");
|
||||
if (mode === 'box') {
|
||||
toast.success(`Box identified: ${data.box_label || data.name}`);
|
||||
} else {
|
||||
toast.success("AI extraction complete!");
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("Failed to process image with AI");
|
||||
console.error(error);
|
||||
@@ -97,13 +102,34 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
|
||||
|
||||
{!image ? (
|
||||
<div className="flex-1 flex flex-col gap-6 min-h-0">
|
||||
<div className="flex bg-slate-900/50 p-1.5 rounded-2xl border border-slate-800/50 shrink-0">
|
||||
<button
|
||||
onClick={() => setMode('item')}
|
||||
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl font-bold transition-all ${mode === 'item' ? 'bg-primary text-white shadow-lg' : 'text-slate-500 hover:text-slate-300'}`}
|
||||
>
|
||||
<Package size={18} />
|
||||
<span className="text-xs">Item Label</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMode('box')}
|
||||
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl font-bold transition-all ${mode === 'box' ? 'bg-primary text-white shadow-lg' : 'text-slate-500 hover:text-slate-300'}`}
|
||||
>
|
||||
<Layers size={18} />
|
||||
<span className="text-xs">Box / Container</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col items-center justify-center border-2 border-dashed border-slate-800 rounded-[2.5rem] bg-slate-900/30 overflow-hidden px-4">
|
||||
<div className="w-20 h-20 bg-slate-900 rounded-3xl flex items-center justify-center mb-6 shadow-inner">
|
||||
<Camera size={32} className="text-slate-500" />
|
||||
<Camera size={32} className={mode === 'box' ? 'text-primary' : 'text-slate-500'} />
|
||||
</div>
|
||||
<p className="text-slate-300 mb-2 text-center font-bold">Label Insight Mode</p>
|
||||
<p className="text-slate-300 mb-2 text-center font-bold">
|
||||
{mode === 'box' ? 'Container Discovery Mode' : 'Label Insight Mode'}
|
||||
</p>
|
||||
<p className="text-xs text-slate-500 px-8 text-center leading-relaxed font-bold">
|
||||
Scan or upload a sharp photo of the item specifications
|
||||
{mode === 'box'
|
||||
? 'Scan the large, prominent label or hand-written name on the container'
|
||||
: 'Scan or upload a sharp photo of the item specifications'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ export default function Scanner({ onScanSuccess, onOCRMatch, paused }: ScannerPr
|
||||
const [isSelecting, setIsSelecting] = useState(false);
|
||||
const [capturedImage, setCapturedImage] = useState<string | null>(null);
|
||||
const isBusy = useRef(false);
|
||||
const isTransitioning = useRef(false); // Flag to track start/stop transitions
|
||||
const scannerId = "reader-container-unique";
|
||||
|
||||
useEffect(() => {
|
||||
@@ -33,11 +34,20 @@ export default function Scanner({ onScanSuccess, onOCRMatch, paused }: ScannerPr
|
||||
}
|
||||
|
||||
const startScanner = async () => {
|
||||
if (isBusy.current) return;
|
||||
if (isBusy.current || isTransitioning.current) return;
|
||||
isBusy.current = true;
|
||||
isTransitioning.current = true;
|
||||
|
||||
try {
|
||||
// If already scanning, we MUST stop it first
|
||||
if (html5QrCodeRef.current?.isScanning) {
|
||||
await html5QrCodeRef.current.stop();
|
||||
try {
|
||||
await html5QrCodeRef.current.stop();
|
||||
} catch (e) {
|
||||
console.warn("Graceful stop failed, might be in transition:", e);
|
||||
// If it's already in transition, we just return and wait for the next effect trigger
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const config = {
|
||||
@@ -85,6 +95,7 @@ export default function Scanner({ onScanSuccess, onOCRMatch, paused }: ScannerPr
|
||||
}
|
||||
} finally {
|
||||
isBusy.current = false;
|
||||
isTransitioning.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -94,11 +105,15 @@ export default function Scanner({ onScanSuccess, onOCRMatch, paused }: ScannerPr
|
||||
|
||||
return () => {
|
||||
if (html5QrCodeRef.current?.isScanning) {
|
||||
isTransitioning.current = true;
|
||||
html5QrCodeRef.current.stop()
|
||||
.then(() => {
|
||||
setIsStarted(false);
|
||||
})
|
||||
.catch(e => console.error("Stop failed", e));
|
||||
.catch(e => console.error("Unmount stop failed", e))
|
||||
.finally(() => {
|
||||
isTransitioning.current = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [paused, onScanSuccess]);
|
||||
|
||||
Reference in New Issue
Block a user