Enhance AI validation with progress tracking and prompt debugging
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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: [
|
||||
|
||||
Reference in New Issue
Block a user