diff --git a/inventory-server/src/routes/ai-validation.js b/inventory-server/src/routes/ai-validation.js index bf36ea9..c43b347 100644 --- a/inventory-server/src/routes/ai-validation.js +++ b/inventory-server/src/routes/ai-validation.js @@ -373,8 +373,24 @@ async function generateDebugResponse(productsToUse, res) { }); // Now use loadPrompt to get the actual combined prompt - const prompt = await loadPrompt(promptConnection, productsToUse, res.app.locals.pool); - const fullPrompt = prompt + "\n" + JSON.stringify(productsToUse); + const promptData = await loadPrompt(promptConnection, productsToUse, res.app.locals.pool); + const fullUserPrompt = promptData.userContent + "\n" + JSON.stringify(productsToUse); + const promptLength = promptData.systemInstructions.length + fullUserPrompt.length; // Store prompt length for performance metrics + console.log("📝 Generated prompt length:", promptLength); + console.log("📝 System instructions length:", promptData.systemInstructions.length); + console.log("📝 User content length:", fullUserPrompt.length); + + // Format the messages as they would be sent to the API + const apiMessages = [ + { + role: "system", + content: promptData.systemInstructions + }, + { + role: "user", + content: fullUserPrompt + } + ]; // Create the response with taxonomy stats let categoriesCount = 0; @@ -415,8 +431,9 @@ async function generateDebugResponse(productsToUse, res) { } : null, basePrompt: systemPrompt ? systemPrompt.prompt_text + "\n\n" + generalPrompt.prompt_text : generalPrompt.prompt_text, - sampleFullPrompt: fullPrompt, - promptLength: fullPrompt.length, + sampleFullPrompt: fullUserPrompt, + promptLength: promptLength, + apiFormat: apiMessages, promptSources: { ...(systemPrompt ? { systemPrompt: { @@ -836,11 +853,14 @@ ${JSON.stringify(mixedTaxonomy.sizeCategories)}${ ----------Here is the product data to validate----------`; - // Return the filtered prompt with system instructions first - return systemInstructions + "\n\n" + combinedPrompt + "\n" + taxonomySection; + // Return both system instructions and user content separately + return { + systemInstructions, + userContent: combinedPrompt + "\n" + taxonomySection + }; } - // Generate the full unfiltered prompt + // Generate the full unfiltered prompt for taxonomy section const taxonomySection = ` Available Categories: ${JSON.stringify(taxonomy.categories)} @@ -868,8 +888,11 @@ ${JSON.stringify(taxonomy.artists)} Here is the product data to validate:`; - // Return the full prompt with system instructions first - return systemInstructions + "\n\n" + combinedPrompt + "\n" + taxonomySection; + // Return both system instructions and user content separately + return { + systemInstructions, + userContent: combinedPrompt + "\n" + taxonomySection + }; } catch (error) { console.error("Error loading prompt:", error); throw error; // Re-throw to be handled by the calling function @@ -917,18 +940,24 @@ router.post("/validate", async (req, res) => { // Load the prompt with the products data to filter taxonomy console.log("🔄 Loading prompt with filtered taxonomy..."); - const prompt = await loadPrompt(connection, products, req.app.locals.pool); - const fullPrompt = prompt + "\n" + JSON.stringify(products); - promptLength = fullPrompt.length; // Store prompt length for performance metrics + const promptData = await loadPrompt(connection, products, req.app.locals.pool); + const fullUserPrompt = promptData.userContent + "\n" + JSON.stringify(products); + const promptLength = promptData.systemInstructions.length + fullUserPrompt.length; // Store prompt length for performance metrics console.log("📝 Generated prompt length:", promptLength); + console.log("📝 System instructions length:", promptData.systemInstructions.length); + console.log("📝 User content length:", fullUserPrompt.length); console.log("🤖 Sending request to OpenAI..."); const completion = await openai.chat.completions.create({ - model: "o3-mini", + model: "gpt-4o", messages: [ + { + role: "system", + content: promptData.systemInstructions, + }, { role: "user", - content: fullPrompt, + content: fullUserPrompt, }, ], temperature: 0.2, 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 3ea14e6..683f152 100644 --- a/inventory/src/components/product-import/steps/ValidationStepNew/components/AiValidationDialogs.tsx +++ b/inventory/src/components/product-import/steps/ValidationStepNew/components/AiValidationDialogs.tsx @@ -25,7 +25,6 @@ import { } from "../hooks/useAiValidation"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; interface TaxonomyStats { categories: number; @@ -43,9 +42,14 @@ interface DebugData { basePrompt: string; sampleFullPrompt: string; promptLength: number; + apiFormat?: Array<{ + role: string; + content: string; + }>; promptSources?: { + systemPrompt?: { id: number; prompt_text: string }; generalPrompt?: { id: number; prompt_text: string }; - companyPrompts?: Array<{ id: number; company: string; prompt_text: string }>; + companyPrompts?: Array<{ id: number; company: string; companyName?: string; prompt_text: string }>; }; estimatedProcessingTime?: { seconds: number | null; @@ -89,8 +93,20 @@ export const AiValidationDialogs: React.FC = ({ debugData, }) => { const [costPerMillionTokens, setCostPerMillionTokens] = useState(2.5); // Default cost - const [activeTab, setActiveTab] = useState("full"); + const hasCompanyPrompts = currentPrompt.debugData?.promptSources?.companyPrompts && + currentPrompt.debugData.promptSources.companyPrompts.length > 0; + + // 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) { + setActiveTab("full"); + } + }, [currentPrompt.isOpen]); + // Format time helper const formatTime = (seconds: number): string => { if (seconds < 60) { @@ -110,10 +126,6 @@ export const AiValidationDialogs: React.FC = ({ // Use the prompt length from the current prompt const promptLength = currentPrompt.prompt ? currentPrompt.prompt.length : 0; - - // Check if we have company-specific prompts - const hasCompanyPrompts = currentPrompt.debugData?.promptSources?.companyPrompts && - currentPrompt.debugData.promptSources.companyPrompts.length > 0; return ( <> @@ -134,8 +146,8 @@ export const AiValidationDialogs: React.FC = ({
- {/* Debug Information Section */} -
+ {/* Debug Information Section - Fixed at the top */} +
{currentPrompt.isLoading ? (
) : ( @@ -249,38 +261,11 @@ export const AiValidationDialogs: React.FC = ({
- - {/* Prompt Sources Section */} - {currentPrompt.debugData?.promptSources && ( - - - - Prompt Sources - - - -
- - System - - - General - - - {currentPrompt.debugData.promptSources.companyPrompts?.map((prompt, idx) => ( - - {prompt.companyName} - - ))} -
-
-
- )} )}
- {/* Prompt Section */} + {/* Prompt Section - Scrollable content */}
{currentPrompt.isLoading ? (
@@ -288,61 +273,186 @@ export const AiValidationDialogs: React.FC = ({
) : ( <> - {currentPrompt.debugData?.promptSources ? ( - - - Full Prompt - System - General Prompt - {hasCompanyPrompts && ( - Company Prompts - )} - + {currentPrompt.debugData?.apiFormat ? ( +
+ {/* Prompt Sources Card - Fixed at the top of the content area */} + + + Prompt Sources + + +
+ document.getElementById('system-message')?.scrollIntoView({ behavior: 'smooth' })} + > + System + + 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('taxonomy-section')?.scrollIntoView({ behavior: 'smooth' })} + > + Taxonomy + + document.getElementById('product-section')?.scrollIntoView({ behavior: 'smooth' })} + > + Products + +
+
+
-
- - - - {currentPrompt.prompt} + + {currentPrompt.debugData.apiFormat.map((message, idx: number) => ( +
+
+ 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}
+ )}
- - - - - {currentPrompt.debugData.promptSources.systemPrompt?.prompt_text || "No system prompt available"} - - - - - - {currentPrompt.debugData.promptSources.generalPrompt?.prompt_text || "No general prompt available"} - - - - {hasCompanyPrompts && ( - -
- {currentPrompt.debugData.promptSources.companyPrompts?.map((prompt, idx) => ( -
-
- {prompt.companyName} (ID: {prompt.company}) -
- - {prompt.prompt_text} - -
- ))} -
-
- )} - -
- +
+ ))} + +
) : ( @@ -379,8 +489,9 @@ export const AiValidationDialogs: React.FC = ({ className="h-full bg-primary transition-all duration-500" style={{ width: `${ - aiValidationProgress.progressPercent ?? - Math.round((aiValidationProgress.step / 5) * 100) + aiValidationProgress.progressPercent !== undefined + ? Math.round(aiValidationProgress.progressPercent) + : Math.round((aiValidationProgress.step / 5) * 100) }%`, backgroundColor: aiValidationProgress.step === -1 @@ -394,8 +505,9 @@ export const AiValidationDialogs: React.FC = ({ {aiValidationProgress.step === -1 ? "❌" : `${ - aiValidationProgress.progressPercent ?? - Math.round((aiValidationProgress.step / 5) * 100) + aiValidationProgress.progressPercent !== undefined + ? Math.round(aiValidationProgress.progressPercent) + : Math.round((aiValidationProgress.step / 5) * 100) }%`}
diff --git a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useAiValidation.tsx b/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useAiValidation.tsx index e797436..9e40231 100644 --- a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useAiValidation.tsx +++ b/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useAiValidation.tsx @@ -56,6 +56,10 @@ export interface CurrentPrompt { basePrompt: string; sampleFullPrompt: string; promptLength: number; + apiFormat?: Array<{ + role: string; + content: string; + }>; promptSources?: { systemPrompt?: { id: number; prompt_text: string }; generalPrompt?: { id: number; prompt_text: string }; @@ -334,7 +338,8 @@ export const useAiValidation = ( sampleFullPrompt: result.sampleFullPrompt || '', promptLength: result.promptLength || (promptContent ? promptContent.length : 0), promptSources: result.promptSources, - estimatedProcessingTime: result.estimatedProcessingTime + estimatedProcessingTime: result.estimatedProcessingTime, + apiFormat: result.apiFormat } })); } else {