diff --git a/inventory/src/components/product-import/steps/ValidationStepNew/components/AiValidationDialogs.tsx b/inventory/src/components/product-import/steps/ValidationStepNew/components/AiValidationDialogs.tsx index 683f152..0478258 100644 --- a/inventory/src/components/product-import/steps/ValidationStepNew/components/AiValidationDialogs.tsx +++ b/inventory/src/components/product-import/steps/ValidationStepNew/components/AiValidationDialogs.tsx @@ -8,7 +8,7 @@ import { } from "@/components/ui/dialog"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Button } from "@/components/ui/button"; -import { Loader2, CheckIcon } from "lucide-react"; +import { Loader2, CheckIcon, XIcon } from "lucide-react"; import { Code } from "@/components/ui/code"; import { Table, @@ -49,7 +49,12 @@ interface DebugData { promptSources?: { systemPrompt?: { id: number; prompt_text: string }; generalPrompt?: { id: number; prompt_text: string }; - companyPrompts?: Array<{ id: number; company: string; companyName?: string; prompt_text: string }>; + companyPrompts?: Array<{ + id: number; + company: string; + companyName?: string; + prompt_text: string; + }>; }; estimatedProcessingTime?: { seconds: number | null; @@ -93,13 +98,69 @@ export const AiValidationDialogs: React.FC = ({ debugData, }) => { const [costPerMillionTokens, setCostPerMillionTokens] = useState(2.5); // Default cost - const hasCompanyPrompts = currentPrompt.debugData?.promptSources?.companyPrompts && - currentPrompt.debugData.promptSources.companyPrompts.length > 0; + const hasCompanyPrompts = + currentPrompt.debugData?.promptSources?.companyPrompts && + currentPrompt.debugData.promptSources.companyPrompts.length > 0; + + // Create our own state to track changes + const [localReversionState, setLocalReversionState] = useState< + Record + >({}); + + // Initialize local state from the isChangeReverted function when component mounts + // or when aiValidationDetails changes + React.useEffect(() => { + if ( + aiValidationDetails.changeDetails && + aiValidationDetails.changeDetails.length > 0 + ) { + const initialState: Record = {}; + + aiValidationDetails.changeDetails.forEach((product) => { + product.changes.forEach((change) => { + const key = `${product.productIndex}-${change.field}`; + initialState[key] = isChangeReverted( + product.productIndex, + change.field + ); + }); + }); + + setLocalReversionState(initialState); + } + }, [aiValidationDetails.changeDetails, isChangeReverted]); + + // This function will toggle the local state for a given change + const toggleChangeAcceptance = (productIndex: number, fieldKey: string) => { + const key = `${productIndex}-${fieldKey}`; + const currentlyRejected = !!localReversionState[key]; + + // Toggle the local state + setLocalReversionState((prev) => ({ + ...prev, + [key]: !prev[key], + })); + + // Only call revertAiChange when toggling to rejected state + // Since revertAiChange is specifically for rejecting changes + if (!currentlyRejected) { + revertAiChange(productIndex, fieldKey); + } + }; + + // Function to check local reversion state + const isChangeLocallyReverted = ( + productIndex: number, + fieldKey: string + ): boolean => { + const key = `${productIndex}-${fieldKey}`; + return !!localReversionState[key]; + }; // Use "full" as the default tab const defaultTab = "full"; const [activeTab, setActiveTab] = useState(defaultTab); - + // Update activeTab when the dialog is opened with new data React.useEffect(() => { if (currentPrompt.isOpen) { @@ -155,7 +216,9 @@ export const AiValidationDialogs: React.FC = ({
- Prompt Length + + Prompt Length +
@@ -163,10 +226,14 @@ export const AiValidationDialogs: React.FC = ({ Characters: {" "} - {promptLength} + + {promptLength} +
- Tokens:{" "} + + Tokens: + {" "} ~{Math.round(promptLength / 4)} @@ -177,7 +244,9 @@ export const AiValidationDialogs: React.FC = ({ - Cost Estimate + + Cost Estimate +
@@ -225,7 +294,8 @@ export const AiValidationDialogs: React.FC = ({
{currentPrompt.debugData?.estimatedProcessingTime ? ( - currentPrompt.debugData.estimatedProcessingTime.seconds ? ( + currentPrompt.debugData.estimatedProcessingTime + .seconds ? ( <>
@@ -233,23 +303,28 @@ export const AiValidationDialogs: React.FC = ({ {" "} {formatTime( - currentPrompt.debugData.estimatedProcessingTime.seconds + currentPrompt.debugData + .estimatedProcessingTime.seconds )}
Based on{" "} - {currentPrompt.debugData.estimatedProcessingTime.sampleCount}{" "} + { + currentPrompt.debugData + .estimatedProcessingTime.sampleCount + }{" "} similar validation - {currentPrompt.debugData.estimatedProcessingTime - .sampleCount !== 1 + {currentPrompt.debugData + .estimatedProcessingTime.sampleCount !== 1 ? "s" : ""}
) : (
- No historical data available for this prompt size + No historical data available for this prompt + size
) ) : ( @@ -278,179 +353,276 @@ export const AiValidationDialogs: React.FC = ({ {/* Prompt Sources Card - Fixed at the top of the content area */} - Prompt Sources + + Prompt Sources +
- document.getElementById('system-message')?.scrollIntoView({ behavior: 'smooth' })} + onClick={() => + document + .getElementById("system-message") + ?.scrollIntoView({ behavior: "smooth" }) + } > System - document.getElementById('general-section')?.scrollIntoView({ behavior: 'smooth' })} + onClick={() => + document + .getElementById("general-section") + ?.scrollIntoView({ behavior: "smooth" }) + } > General - - {currentPrompt.debugData.promptSources?.companyPrompts?.map((company, idx) => ( - document.getElementById('company-section')?.scrollIntoView({ behavior: 'smooth' })} - > - {company.companyName || `Company ${company.company}`} - - ))} - - ( + + document + .getElementById("company-section") + ?.scrollIntoView({ behavior: "smooth" }) + } + > + {company.companyName || + `Company ${company.company}`} + + ) + )} + + document.getElementById('taxonomy-section')?.scrollIntoView({ behavior: 'smooth' })} + onClick={() => + document + .getElementById("taxonomy-section") + ?.scrollIntoView({ behavior: "smooth" }) + } > Taxonomy - document.getElementById('product-section')?.scrollIntoView({ behavior: 'smooth' })} + onClick={() => + document + .getElementById("product-section") + ?.scrollIntoView({ behavior: "smooth" }) + } > Products
- + - {currentPrompt.debugData.apiFormat.map((message, idx: number) => ( -
-
( +
- Role: {message.role} +
+ Role: {message.role} +
+ + + {message.role === "user" ? ( +
+ {(() => { + const content = message.content; + + // Find section boundaries by looking for specific markers + const companySpecificStartIndex = + content.indexOf( + "--- COMPANY-SPECIFIC INSTRUCTIONS ---" + ); + const companySpecificEndIndex = + content.indexOf( + "--- END COMPANY-SPECIFIC INSTRUCTIONS ---" + ); + + const taxonomyStartIndex = + content.indexOf( + "All Available Categories:" + ); + const taxonomyFallbackStartIndex = + content.indexOf( + "Available Categories:" + ); + const actualTaxonomyStartIndex = + taxonomyStartIndex >= 0 + ? taxonomyStartIndex + : taxonomyFallbackStartIndex; + + const productDataStartIndex = + content.indexOf( + "----------Here is the product data to validate----------" + ); + + // If we can't find any markers, just return the content as-is + if ( + actualTaxonomyStartIndex < 0 && + productDataStartIndex < 0 && + companySpecificStartIndex < 0 + ) { + return content; + } + + // Determine section indices + let generalEndIndex = content.length; + + if (companySpecificStartIndex >= 0) { + generalEndIndex = + companySpecificStartIndex; + } else if ( + actualTaxonomyStartIndex >= 0 + ) { + generalEndIndex = + actualTaxonomyStartIndex; + } else if (productDataStartIndex >= 0) { + generalEndIndex = productDataStartIndex; + } + + // Determine where taxonomy starts + let taxonomyEndIndex = content.length; + if (productDataStartIndex >= 0) { + taxonomyEndIndex = + productDataStartIndex; + } + + // Segments to render with appropriate styling + const segments = []; + + // General section (beginning to company/taxonomy/product) + if (generalEndIndex > 0) { + segments.push( +
+
+ General Prompt +
+
+                                              {content.substring(
+                                                0,
+                                                generalEndIndex
+                                              )}
+                                            
+
+ ); + } + + // Company-specific section if present + if ( + companySpecificStartIndex >= 0 && + companySpecificEndIndex >= 0 + ) { + segments.push( +
+
+ Company-Specific Instructions +
+
+                                              {content.substring(
+                                                companySpecificStartIndex,
+                                                companySpecificEndIndex +
+                                                  "--- END COMPANY-SPECIFIC INSTRUCTIONS ---"
+                                                    .length
+                                              )}
+                                            
+
+ ); + } + + // Taxonomy section + if (actualTaxonomyStartIndex >= 0) { + const taxEnd = taxonomyEndIndex; + segments.push( +
+
+ Taxonomy Data +
+
+                                              {content.substring(
+                                                actualTaxonomyStartIndex,
+                                                taxEnd
+                                              )}
+                                            
+
+ ); + } + + // Product data section + if (productDataStartIndex >= 0) { + segments.push( +
+
+ Product Data +
+
+                                              {content.substring(
+                                                productDataStartIndex
+                                              )}
+                                            
+
+ ); + } + + return <>{segments}; + })()} +
+ ) : ( +
+                                    {message.content}
+                                  
+ )} +
- - - {message.role === 'user' ? ( -
- {(() => { - const content = message.content; - - // Find section boundaries by looking for specific markers - const companySpecificStartIndex = content.indexOf('--- COMPANY-SPECIFIC INSTRUCTIONS ---'); - const companySpecificEndIndex = content.indexOf('--- END COMPANY-SPECIFIC INSTRUCTIONS ---'); - - const taxonomyStartIndex = content.indexOf('All Available Categories:'); - const taxonomyFallbackStartIndex = content.indexOf('Available Categories:'); - const actualTaxonomyStartIndex = taxonomyStartIndex >= 0 ? taxonomyStartIndex : taxonomyFallbackStartIndex; - - const productDataStartIndex = content.indexOf('----------Here is the product data to validate----------'); - - // If we can't find any markers, just return the content as-is - if (actualTaxonomyStartIndex < 0 && productDataStartIndex < 0 && companySpecificStartIndex < 0) { - return content; - } - - // Determine section indices - let generalEndIndex = content.length; - - if (companySpecificStartIndex >= 0) { - generalEndIndex = companySpecificStartIndex; - } else if (actualTaxonomyStartIndex >= 0) { - generalEndIndex = actualTaxonomyStartIndex; - } else if (productDataStartIndex >= 0) { - generalEndIndex = productDataStartIndex; - } - - // Determine where taxonomy starts - let taxonomyEndIndex = content.length; - if (productDataStartIndex >= 0) { - taxonomyEndIndex = productDataStartIndex; - } - - // Segments to render with appropriate styling - const segments = []; - - // General section (beginning to company/taxonomy/product) - if (generalEndIndex > 0) { - segments.push( -
-
- General Prompt -
-
-                                            {content.substring(0, generalEndIndex)}
-                                          
-
- ); - } - - // Company-specific section if present - if (companySpecificStartIndex >= 0 && companySpecificEndIndex >= 0) { - segments.push( -
-
- Company-Specific Instructions -
-
-                                            {content.substring(companySpecificStartIndex, companySpecificEndIndex + '--- END COMPANY-SPECIFIC INSTRUCTIONS ---'.length)}
-                                          
-
- ); - } - - // Taxonomy section - if (actualTaxonomyStartIndex >= 0) { - const taxEnd = taxonomyEndIndex; - segments.push( -
-
- Taxonomy Data -
-
-                                            {content.substring(actualTaxonomyStartIndex, taxEnd)}
-                                          
-
- ); - } - - // Product data section - if (productDataStartIndex >= 0) { - segments.push( -
-
- Product Data -
-
-                                            {content.substring(productDataStartIndex)}
-                                          
-
- ); - } - - return <>{segments}; - })()} -
- ) : ( -
{message.content}
- )} -
-
- ))} + ) + )}
) : ( @@ -566,14 +738,14 @@ export const AiValidationDialogs: React.FC = ({ setAiValidationDetails((prev) => ({ ...prev, isOpen: open })) } > - + AI Validation Results Review the changes and warnings suggested by the AI - + {aiValidationDetails.changeDetails && aiValidationDetails.changeDetails.length > 0 ? (
@@ -595,10 +767,16 @@ export const AiValidationDialogs: React.FC = ({ - Field - Original Value - Corrected Value - Action + Field + + Original Value + + + Corrected Value + + + Accept Changes? + @@ -609,7 +787,7 @@ export const AiValidationDialogs: React.FC = ({ const fieldLabel = field ? field.label : change.field; - const isReverted = isChangeReverted( + const isReverted = isChangeLocallyReverted( product.productIndex, change.field ); @@ -632,7 +810,6 @@ export const AiValidationDialogs: React.FC = ({ dangerouslySetInnerHTML={{ __html: originalHtml, }} - className={isReverted ? "font-medium" : ""} /> @@ -640,36 +817,46 @@ export const AiValidationDialogs: React.FC = ({ dangerouslySetInnerHTML={{ __html: correctedHtml, }} - className={!isReverted ? "font-medium" : ""} /> - -
- {isReverted ? ( - - ) : ( - - )} + +
+ +