AI tweaks and make column name matching case insensitive

This commit is contained in:
2025-02-20 15:49:48 -05:00
parent 45a52cbc33
commit 7f7e6fdd1f
4 changed files with 142 additions and 46 deletions

View File

@@ -11,11 +11,12 @@ export const findMatch = <T extends string>(
fields: Fields<T>,
autoMapDistance: number,
): T | undefined => {
const headerLower = header.toLowerCase()
const smallestValue = fields.reduce<AutoMatchAccumulator<T>>((acc, field) => {
const distance = Math.min(
...[
lavenstein(field.key, header),
...(field.alternateMatches?.map((alternate) => lavenstein(alternate, header)) || []),
lavenstein(field.key.toLowerCase(), headerLower),
...(field.alternateMatches?.map((alternate) => lavenstein(alternate.toLowerCase(), headerLower)) || []),
],
)
return distance < acc.distance || acc.distance === undefined

View File

@@ -57,6 +57,14 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip"
import config from "@/config"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { ScrollArea } from "@/components/ui/scroll-area"
type Props<T extends string> = {
initialData: (Data<T> & Meta)[]
@@ -706,6 +714,15 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
const [isSubmitting, setSubmitting] = useState(false)
const [copyDownField, setCopyDownField] = useState<{key: T, label: string} | null>(null)
const [isAiValidating, setIsAiValidating] = useState(false)
const [aiValidationDetails, setAiValidationDetails] = useState<{
changes: string[];
warnings: string[];
isOpen: boolean;
}>({
changes: [],
warnings: [],
isOpen: false,
});
// Memoize filtered data to prevent recalculation on every render
const filteredData = useMemo(() => {
@@ -949,76 +966,78 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
// Add AI validation function
const handleAiValidation = async () => {
try {
setIsAiValidating(true)
setIsAiValidating(true);
console.log('Sending data for AI validation:', data);
const response = await fetch(`${config.apiUrl}/ai-validation/validate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ products: data }),
})
});
if (!response.ok) {
throw new Error('AI validation failed')
throw new Error('AI validation failed');
}
const result = await response.json()
const result = await response.json();
console.log('AI validation response:', result);
if (!result.success) {
throw new Error(result.error || 'AI validation failed')
throw new Error(result.error || 'AI validation failed');
}
// Update the data with AI suggestions
if (result.correctedData && Array.isArray(result.correctedData)) {
// Log the differences
data.forEach((original, index) => {
const corrected = result.correctedData[index];
if (corrected) {
const changes = Object.keys(corrected).filter(key => {
const originalValue = original[key as keyof typeof original];
const correctedValue = corrected[key as keyof typeof corrected];
return JSON.stringify(originalValue) !== JSON.stringify(correctedValue);
});
if (changes.length > 0) {
console.log(`Changes for row ${index + 1}:`, changes.map(key => ({
field: key,
original: original[key as keyof typeof original],
corrected: corrected[key as keyof typeof corrected]
})));
}
}
});
// Preserve the __index and __errors from the original data
const newData = result.correctedData.map((item: any, idx: number) => ({
...item,
__index: data[idx]?.__index,
__errors: data[idx]?.__errors,
}))
}));
// Update the data and run validations
await updateData(newData)
await updateData(newData);
}
// Show changes and warnings
if (result.changes?.length) {
toast({
title: "AI Validation Changes",
description: (
<div className="mt-2 space-y-2">
{result.changes.map((change: string, i: number) => (
<div key={i} className="text-sm">• {change}</div>
))}
</div>
),
})
}
// Show changes and warnings in dialog
setAiValidationDetails({
changes: result.changes || [],
warnings: result.warnings || [],
isOpen: true,
});
if (result.warnings?.length) {
toast({
title: "AI Validation Warnings",
description: (
<div className="mt-2 space-y-2">
{result.warnings.map((warning: string, i: number) => (
<div key={i} className="text-sm">• {warning}</div>
))}
</div>
),
variant: "destructive",
})
}
} catch (error) {
console.error('AI Validation Error:', error)
console.error('AI Validation Error:', error);
toast({
title: "AI Validation Error",
description: error instanceof Error ? error.message : "An error occurred during AI validation",
variant: "destructive",
})
});
} finally {
setIsAiValidating(false)
setIsAiValidating(false);
}
}
};
return (
<div className="flex h-[calc(100vh-9.5rem)] flex-col">
@@ -1055,6 +1074,47 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialog>
<Dialog
open={aiValidationDetails.isOpen}
onOpenChange={(open) => setAiValidationDetails(prev => ({ ...prev, isOpen: open }))}
>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>AI Validation Results</DialogTitle>
<DialogDescription>
Review the changes and warnings suggested by the AI
</DialogDescription>
</DialogHeader>
<ScrollArea className="max-h-[60vh]">
{aiValidationDetails.changes.length > 0 && (
<div className="mb-4">
<h3 className="font-semibold mb-2">Changes Made:</h3>
<ul className="space-y-2">
{aiValidationDetails.changes.map((change, i) => (
<li key={i} className="flex gap-2">
<span className="text-green-500"></span>
<span>{change}</span>
</li>
))}
</ul>
</div>
)}
{aiValidationDetails.warnings.length > 0 && (
<div>
<h3 className="font-semibold mb-2">Warnings:</h3>
<ul className="space-y-2">
{aiValidationDetails.warnings.map((warning, i) => (
<li key={i} className="flex gap-2">
<span className="text-yellow-500"></span>
<span>{warning}</span>
</li>
))}
</ul>
</div>
)}
</ScrollArea>
</DialogContent>
</Dialog>
<div className="flex-1 overflow-hidden">
<div className="h-full flex flex-col">
<div className="px-8 pt-6">

View File

@@ -27,7 +27,7 @@ const BASE_IMPORT_FIELDS = [
label: "UPC",
key: "upc",
description: "Universal Product Code/Barcode",
alternateMatches: ["barcode", "bar code", "JAN", "EAN"],
alternateMatches: ["upc","UPC","barcode", "bar code", "JAN", "EAN"],
fieldType: { type: "input" },
width: 140,
validations: [
@@ -94,7 +94,7 @@ const BASE_IMPORT_FIELDS = [
label: "MSRP",
key: "msrp",
description: "Manufacturer's Suggested Retail Price",
alternateMatches: ["retail", "retail price", "sugg retail", "price", "sugg. Retail"],
alternateMatches: ["retail", "retail price", "sugg retail", "price", "sugg. Retail","msrp","MSRP"],
fieldType: {
type: "input",
price: true
@@ -136,7 +136,7 @@ const BASE_IMPORT_FIELDS = [
label: "Case Pack",
key: "case_qty",
description: "Number of units per case",
alternateMatches: ["mc qty"],
alternateMatches: ["mc qty","MC Qty","case qty","Case Qty"],
fieldType: { type: "input" },
width: 50,
validations: [