From 7f7e6fdd1f42b76fc595bab3ae71164c199756dd Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 20 Feb 2025 15:49:48 -0500 Subject: [PATCH] AI tweaks and make column name matching case insensitive --- inventory-server/src/routes/ai-validation.js | 43 +++++- .../steps/MatchColumnsStep/utils/findMatch.ts | 5 +- .../steps/ValidationStep/ValidationStep.tsx | 134 +++++++++++++----- inventory/src/pages/Import.tsx | 6 +- 4 files changed, 142 insertions(+), 46 deletions(-) diff --git a/inventory-server/src/routes/ai-validation.js b/inventory-server/src/routes/ai-validation.js index d54afbd..4f2a266 100644 --- a/inventory-server/src/routes/ai-validation.js +++ b/inventory-server/src/routes/ai-validation.js @@ -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' diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/utils/findMatch.ts b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/utils/findMatch.ts index 346f3c4..06be615 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/utils/findMatch.ts +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/utils/findMatch.ts @@ -11,11 +11,12 @@ export const findMatch = ( fields: Fields, autoMapDistance: number, ): T | undefined => { + const headerLower = header.toLowerCase() const smallestValue = fields.reduce>((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 diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStep/ValidationStep.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStep/ValidationStep.tsx index a0a9850..3ffead2 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStep/ValidationStep.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStep/ValidationStep.tsx @@ -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 = { initialData: (Data & Meta)[] @@ -706,6 +714,15 @@ export const ValidationStep = ({ 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 = ({ 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: ( -
- {result.changes.map((change: string, i: number) => ( -
• {change}
- ))} -
- ), - }) - } + // 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: ( -
- {result.warnings.map((warning: string, i: number) => ( -
• {warning}
- ))} -
- ), - 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 (
@@ -1055,6 +1074,47 @@ export const ValidationStep = ({ initialData, file, onBack }: + setAiValidationDetails(prev => ({ ...prev, isOpen: open }))} + > + + + AI Validation Results + + Review the changes and warnings suggested by the AI + + + + {aiValidationDetails.changes.length > 0 && ( +
+

Changes Made:

+
    + {aiValidationDetails.changes.map((change, i) => ( +
  • + + {change} +
  • + ))} +
+
+ )} + {aiValidationDetails.warnings.length > 0 && ( +
+

Warnings:

+
    + {aiValidationDetails.warnings.map((warning, i) => ( +
  • + + {warning} +
  • + ))} +
+
+ )} +
+
+
diff --git a/inventory/src/pages/Import.tsx b/inventory/src/pages/Import.tsx index f535fcd..ed951ea 100644 --- a/inventory/src/pages/Import.tsx +++ b/inventory/src/pages/Import.tsx @@ -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: [