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

@@ -33,15 +33,19 @@ Respond in the following JSON format:
router.post('/validate', async (req, res) => {
try {
const { products } = req.body;
console.log('🔍 Received products for validation:', JSON.stringify(products, null, 2));
if (!Array.isArray(products)) {
console.error('❌ Invalid input: products is not an array');
return res.status(400).json({ error: 'Products must be an array' });
}
const prompt = createValidationPrompt(products);
console.log('📝 Generated prompt:', prompt);
console.log('🤖 Sending request to OpenAI...');
const completion = await openai.chat.completions.create({
model: "gpt-4-turbo-preview",
model: "gpt-4o-mini",
messages: [
{
role: "system",
@@ -52,19 +56,50 @@ router.post('/validate', async (req, res) => {
content: prompt
}
],
temperature: 0.3, // Lower temperature for more consistent results
temperature: 0.3,
max_tokens: 4000,
response_format: { type: "json_object" }
});
const aiResponse = JSON.parse(completion.choices[0].message.content);
console.log('✅ Received response from OpenAI');
const rawResponse = completion.choices[0].message.content;
console.log('📄 Raw AI response:', rawResponse);
const aiResponse = JSON.parse(rawResponse);
console.log('🔄 Parsed AI response:', JSON.stringify(aiResponse, null, 2));
// Compare original and corrected data
if (aiResponse.correctedData) {
console.log('📊 Changes summary:');
products.forEach((original, index) => {
const corrected = aiResponse.correctedData[index];
if (corrected) {
const changes = Object.keys(corrected).filter(key =>
JSON.stringify(original[key]) !== JSON.stringify(corrected[key])
);
if (changes.length > 0) {
console.log(`\nProduct ${index + 1} changes:`);
changes.forEach(key => {
console.log(` ${key}:`);
console.log(` - Original: ${JSON.stringify(original[key])}`);
console.log(` - Corrected: ${JSON.stringify(corrected[key])}`);
});
}
}
});
}
res.json({
success: true,
...aiResponse
});
} catch (error) {
console.error('AI Validation Error:', error);
console.error('AI Validation Error:', error);
console.error('Error details:', {
name: error.name,
message: error.message,
stack: error.stack
});
res.status(500).json({
success: false,
error: error.message || 'Error during AI validation'

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: [