Enhance AI validation with progress tracking and prompt debugging

This commit is contained in:
2025-02-22 20:53:13 -05:00
parent 694014934c
commit 959a64aebc
4 changed files with 660 additions and 67 deletions

View File

@@ -65,6 +65,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Code } from "@/components/ui/code"
type Props<T extends string> = {
initialData: (Data<T> & Meta)[]
@@ -724,6 +725,26 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
isOpen: false,
});
const [aiValidationProgress, setAiValidationProgress] = useState<{
isOpen: boolean;
status: string;
step: number;
}>({
isOpen: false,
status: "",
step: 0,
});
const [currentPrompt, setCurrentPrompt] = useState<{
isOpen: boolean;
prompt: string | null;
isLoading: boolean;
}>({
isOpen: false,
prompt: null,
isLoading: false,
});
// Memoize filtered data to prevent recalculation on every render
const filteredData = useMemo(() => {
if (!filterByErrors) return data
@@ -967,7 +988,18 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
const handleAiValidation = async () => {
try {
setIsAiValidating(true);
console.log('Sending data for AI validation:', data);
setAiValidationProgress({
isOpen: true,
status: "Preparing data for validation...",
step: 1
});
console.log('Sending data for validation:', data);
setAiValidationProgress(prev => ({
...prev,
status: "Sending data to AI service and awaiting response...",
step: 2
}));
const response = await fetch(`${config.apiUrl}/ai-validation/validate`, {
method: 'POST',
@@ -981,6 +1013,12 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
throw new Error('AI validation failed');
}
setAiValidationProgress(prev => ({
...prev,
status: "Processing AI response...",
step: 3
}));
const result = await response.json();
console.log('AI validation response:', result);
@@ -988,6 +1026,12 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
throw new Error(result.error || 'AI validation failed');
}
setAiValidationProgress(prev => ({
...prev,
status: "Applying corrections...",
step: 4
}));
// Update the data with AI suggestions
if (result.correctedData && Array.isArray(result.correctedData)) {
// Log the differences
@@ -1027,6 +1071,16 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
isOpen: true,
});
setAiValidationProgress(prev => ({
...prev,
status: "Validation complete!",
step: 5
}));
setTimeout(() => {
setAiValidationProgress(prev => ({ ...prev, isOpen: false }));
}, 1000);
} catch (error) {
console.error('AI Validation Error:', error);
toast({
@@ -1034,11 +1088,82 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
description: error instanceof Error ? error.message : "An error occurred during AI validation",
variant: "destructive",
});
setAiValidationProgress(prev => ({
...prev,
status: "Validation failed",
step: -1
}));
} finally {
setIsAiValidating(false);
}
};
// Add function to fetch current prompt
const showCurrentPrompt = async () => {
try {
setCurrentPrompt(prev => ({ ...prev, isLoading: true, isOpen: true }));
// Debug log the data being sent
console.log('Sending products data:', {
dataLength: data.length,
firstProduct: data[0],
lastProduct: data[data.length - 1]
});
// Clean the data to ensure we only send what's needed
const cleanedData = data.map(item => {
const { __errors, __index, ...rest } = item;
return rest;
});
console.log('Cleaned data sample:', {
length: cleanedData.length,
firstProduct: cleanedData[0],
lastProduct: cleanedData[cleanedData.length - 1]
});
// Use POST to send products in request body
const response = await fetch(`${config.apiUrl}/ai-validation/debug`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ products: cleanedData })
});
if (!response.ok) {
const errorText = await response.text();
console.error('Debug endpoint error:', {
status: response.status,
statusText: response.statusText,
body: errorText
});
throw new Error(`Failed to fetch prompt: ${response.status} ${response.statusText}`);
}
const debugData = await response.json();
// Log the response stats
console.log('Debug response stats:', {
promptLength: debugData.promptLength,
taxonomyStats: debugData.taxonomyStats
});
setCurrentPrompt(prev => ({
...prev,
prompt: debugData.sampleFullPrompt,
isLoading: false
}));
} catch (error) {
console.error('Error fetching prompt:', error);
toast({
title: "Error",
description: error instanceof Error ? error.message : "Failed to fetch current prompt",
variant: "destructive",
});
setCurrentPrompt(prev => ({ ...prev, isLoading: false }));
}
};
return (
<div className="flex h-[calc(100vh-9.5rem)] flex-col">
<CopyDownDialog
@@ -1047,6 +1172,25 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
onConfirm={executeCopyDown}
fieldLabel={copyDownField?.label || ""}
/>
<Dialog open={currentPrompt.isOpen} onOpenChange={(open) => setCurrentPrompt(prev => ({ ...prev, isOpen: open }))}>
<DialogContent className="max-w-4xl h-[80vh]">
<DialogHeader>
<DialogTitle>Current AI Prompt</DialogTitle>
<DialogDescription>
This is the exact prompt that would be sent to the AI for validation
</DialogDescription>
</DialogHeader>
<ScrollArea className="flex-1">
{currentPrompt.isLoading ? (
<div className="flex items-center justify-center h-full">
<Loader2 className="h-8 w-8 animate-spin" />
</div>
) : (
<Code className="whitespace-pre-wrap p-4">{currentPrompt.prompt}</Code>
)}
</ScrollArea>
</DialogContent>
</Dialog>
<AlertDialog open={showSubmitAlert} onOpenChange={setShowSubmitAlert}>
<AlertDialogPortal>
<AlertDialogOverlay className="z-[1400]" />
@@ -1074,6 +1218,34 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialog>
<Dialog open={aiValidationProgress.isOpen} onOpenChange={() => {}}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>AI Validation Progress</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="flex items-center gap-4">
<div className="flex-1">
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
<div
className="h-full bg-primary transition-all duration-500"
style={{
width: `${(aiValidationProgress.step / 5) * 100}%`,
backgroundColor: aiValidationProgress.step === -1 ? 'var(--destructive)' : undefined
}}
/>
</div>
</div>
<div className="text-sm text-muted-foreground w-12 text-right">
{aiValidationProgress.step === -1 ? '❌' : `${Math.round((aiValidationProgress.step / 5) * 100)}%`}
</div>
</div>
<p className="text-center text-sm text-muted-foreground">
{aiValidationProgress.status}
</p>
</div>
</DialogContent>
</Dialog>
<Dialog
open={aiValidationDetails.isOpen}
onOpenChange={(open) => setAiValidationDetails(prev => ({ ...prev, isOpen: open }))}
@@ -1141,6 +1313,14 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
)}
AI Validate
</Button>
<Button
variant="outline"
size="sm"
onClick={showCurrentPrompt}
disabled={data.length === 0}
>
Show Prompt
</Button>
<div className="flex items-center gap-2">
<Switch
checked={filterByErrors}

View File

@@ -40,7 +40,7 @@ const BASE_IMPORT_FIELDS = [
label: "Supplier #",
key: "supplier_no",
description: "Supplier's product identifier",
alternateMatches: ["sku", "item#", "mfg item #", "item"],
alternateMatches: ["sku", "item#", "mfg item #", "item", "supplier #"],
fieldType: { type: "input" },
width: 180,
validations: [
@@ -52,6 +52,7 @@ const BASE_IMPORT_FIELDS = [
label: "Notions #",
key: "notions_no",
description: "Internal notions number",
alternateMatches: ["notions #"],
fieldType: { type: "input" },
width: 110,
validations: [
@@ -109,7 +110,7 @@ const BASE_IMPORT_FIELDS = [
label: "Qty Per Unit",
key: "qty_per_unit",
description: "Quantity of items per individual unit",
alternateMatches: ["inner pack", "inner", "min qty", "unit qty", "min. order qty"],
alternateMatches: ["inner pack", "inner", "min qty", "unit qty", "min. order qty", "supplier qty/unit"],
fieldType: { type: "input" },
width: 90,
validations: [
@@ -121,7 +122,7 @@ const BASE_IMPORT_FIELDS = [
label: "Cost Each",
key: "cost_each",
description: "Wholesale cost per unit",
alternateMatches: ["wholesale", "wholesale price"],
alternateMatches: ["wholesale", "wholesale price", "supplier cost each"],
fieldType: {
type: "input",
price: true
@@ -264,7 +265,7 @@ const BASE_IMPORT_FIELDS = [
label: "Country Of Origin",
key: "coo",
description: "2-letter country code (ISO)",
alternateMatches: ["coo"],
alternateMatches: ["coo", "country of origin"],
fieldType: { type: "input" },
width: 100,
validations: [
@@ -275,7 +276,7 @@ const BASE_IMPORT_FIELDS = [
label: "HTS Code",
key: "hts_code",
description: "Harmonized Tariff Schedule code",
alternateMatches: ["taric"],
alternateMatches: ["taric","hts"],
fieldType: { type: "input" },
width: 130,
validations: [