Enhance ai debug dialog

This commit is contained in:
2025-03-24 13:25:18 -04:00
parent 228ae8b2a9
commit 114018080a
3 changed files with 255 additions and 109 deletions

View File

@@ -373,8 +373,24 @@ async function generateDebugResponse(productsToUse, res) {
}); });
// Now use loadPrompt to get the actual combined prompt // Now use loadPrompt to get the actual combined prompt
const prompt = await loadPrompt(promptConnection, productsToUse, res.app.locals.pool); const promptData = await loadPrompt(promptConnection, productsToUse, res.app.locals.pool);
const fullPrompt = prompt + "\n" + JSON.stringify(productsToUse); 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 // Create the response with taxonomy stats
let categoriesCount = 0; let categoriesCount = 0;
@@ -415,8 +431,9 @@ async function generateDebugResponse(productsToUse, res) {
} }
: null, : null,
basePrompt: systemPrompt ? systemPrompt.prompt_text + "\n\n" + generalPrompt.prompt_text : generalPrompt.prompt_text, basePrompt: systemPrompt ? systemPrompt.prompt_text + "\n\n" + generalPrompt.prompt_text : generalPrompt.prompt_text,
sampleFullPrompt: fullPrompt, sampleFullPrompt: fullUserPrompt,
promptLength: fullPrompt.length, promptLength: promptLength,
apiFormat: apiMessages,
promptSources: { promptSources: {
...(systemPrompt ? { ...(systemPrompt ? {
systemPrompt: { systemPrompt: {
@@ -836,11 +853,14 @@ ${JSON.stringify(mixedTaxonomy.sizeCategories)}${
----------Here is the product data to validate----------`; ----------Here is the product data to validate----------`;
// Return the filtered prompt with system instructions first // Return both system instructions and user content separately
return systemInstructions + "\n\n" + combinedPrompt + "\n" + taxonomySection; return {
systemInstructions,
userContent: combinedPrompt + "\n" + taxonomySection
};
} }
// Generate the full unfiltered prompt // Generate the full unfiltered prompt for taxonomy section
const taxonomySection = ` const taxonomySection = `
Available Categories: Available Categories:
${JSON.stringify(taxonomy.categories)} ${JSON.stringify(taxonomy.categories)}
@@ -868,8 +888,11 @@ ${JSON.stringify(taxonomy.artists)}
Here is the product data to validate:`; Here is the product data to validate:`;
// Return the full prompt with system instructions first // Return both system instructions and user content separately
return systemInstructions + "\n\n" + combinedPrompt + "\n" + taxonomySection; return {
systemInstructions,
userContent: combinedPrompt + "\n" + taxonomySection
};
} catch (error) { } catch (error) {
console.error("Error loading prompt:", error); console.error("Error loading prompt:", error);
throw error; // Re-throw to be handled by the calling function 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 // Load the prompt with the products data to filter taxonomy
console.log("🔄 Loading prompt with filtered taxonomy..."); console.log("🔄 Loading prompt with filtered taxonomy...");
const prompt = await loadPrompt(connection, products, req.app.locals.pool); const promptData = await loadPrompt(connection, products, req.app.locals.pool);
const fullPrompt = prompt + "\n" + JSON.stringify(products); const fullUserPrompt = promptData.userContent + "\n" + JSON.stringify(products);
promptLength = fullPrompt.length; // Store prompt length for performance metrics const promptLength = promptData.systemInstructions.length + fullUserPrompt.length; // Store prompt length for performance metrics
console.log("📝 Generated prompt length:", promptLength); 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..."); console.log("🤖 Sending request to OpenAI...");
const completion = await openai.chat.completions.create({ const completion = await openai.chat.completions.create({
model: "o3-mini", model: "gpt-4o",
messages: [ messages: [
{
role: "system",
content: promptData.systemInstructions,
},
{ {
role: "user", role: "user",
content: fullPrompt, content: fullUserPrompt,
}, },
], ],
temperature: 0.2, temperature: 0.2,

View File

@@ -25,7 +25,6 @@ import {
} from "../hooks/useAiValidation"; } from "../hooks/useAiValidation";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
interface TaxonomyStats { interface TaxonomyStats {
categories: number; categories: number;
@@ -43,9 +42,14 @@ interface DebugData {
basePrompt: string; basePrompt: string;
sampleFullPrompt: string; sampleFullPrompt: string;
promptLength: number; promptLength: number;
apiFormat?: Array<{
role: string;
content: string;
}>;
promptSources?: { promptSources?: {
systemPrompt?: { id: number; prompt_text: string };
generalPrompt?: { 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?: { estimatedProcessingTime?: {
seconds: number | null; seconds: number | null;
@@ -89,7 +93,19 @@ export const AiValidationDialogs: React.FC<AiValidationDialogsProps> = ({
debugData, debugData,
}) => { }) => {
const [costPerMillionTokens, setCostPerMillionTokens] = useState(2.5); // Default cost 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 // Format time helper
const formatTime = (seconds: number): string => { const formatTime = (seconds: number): string => {
@@ -111,10 +127,6 @@ export const AiValidationDialogs: React.FC<AiValidationDialogsProps> = ({
// Use the prompt length from the current prompt // Use the prompt length from the current prompt
const promptLength = currentPrompt.prompt ? currentPrompt.prompt.length : 0; 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 ( return (
<> <>
{/* Current Prompt Dialog with Debug Info */} {/* Current Prompt Dialog with Debug Info */}
@@ -134,8 +146,8 @@ export const AiValidationDialogs: React.FC<AiValidationDialogsProps> = ({
</DialogHeader> </DialogHeader>
<div className="flex flex-col h-[calc(90vh-120px)] overflow-hidden"> <div className="flex flex-col h-[calc(90vh-120px)] overflow-hidden">
{/* Debug Information Section */} {/* Debug Information Section - Fixed at the top */}
<div className="mb-4 flex-shrink-0"> <div className="flex-shrink-0">
{currentPrompt.isLoading ? ( {currentPrompt.isLoading ? (
<div className="flex justify-center items-center h-[100px]"></div> <div className="flex justify-center items-center h-[100px]"></div>
) : ( ) : (
@@ -249,38 +261,11 @@ export const AiValidationDialogs: React.FC<AiValidationDialogsProps> = ({
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
{/* Prompt Sources Section */}
{currentPrompt.debugData?.promptSources && (
<Card className="py-2">
<CardHeader className="py-2">
<CardTitle className="text-base">
Prompt Sources
</CardTitle>
</CardHeader>
<CardContent className="py-2">
<div className="flex flex-wrap gap-2">
<Badge variant="outline" className="bg-purple-50">
System
</Badge>
<Badge variant="outline" className="bg-green-50">
General
</Badge>
{currentPrompt.debugData.promptSources.companyPrompts?.map((prompt, idx) => (
<Badge key={idx} variant="outline" className="bg-blue-50">
{prompt.companyName}
</Badge>
))}
</div>
</CardContent>
</Card>
)}
</> </>
)} )}
</div> </div>
{/* Prompt Section */} {/* Prompt Section - Scrollable content */}
<div className="flex-1 min-h-0"> <div className="flex-1 min-h-0">
{currentPrompt.isLoading ? ( {currentPrompt.isLoading ? (
<div className="flex items-center justify-center h-full"> <div className="flex items-center justify-center h-full">
@@ -288,61 +273,186 @@ export const AiValidationDialogs: React.FC<AiValidationDialogsProps> = ({
</div> </div>
) : ( ) : (
<> <>
{currentPrompt.debugData?.promptSources ? ( {currentPrompt.debugData?.apiFormat ? (
<Tabs <div className="flex flex-col h-full">
defaultValue="full" {/* Prompt Sources Card - Fixed at the top of the content area */}
value={activeTab} <Card className="py-2 mb-4 flex-shrink-0">
onValueChange={setActiveTab} <CardHeader className="py-2">
className="h-full flex flex-col" <CardTitle className="text-base">Prompt Sources</CardTitle>
</CardHeader>
<CardContent className="py-2">
<div className="flex flex-wrap gap-2">
<Badge
variant="outline"
className="bg-purple-100 hover:bg-purple-200 cursor-pointer"
onClick={() => document.getElementById('system-message')?.scrollIntoView({ behavior: 'smooth' })}
> >
<TabsList className="mb-2 w-fit"> System
<TabsTrigger value="full">Full Prompt</TabsTrigger> </Badge>
<TabsTrigger value="system">System</TabsTrigger> <Badge
<TabsTrigger value="general">General Prompt</TabsTrigger> variant="outline"
{hasCompanyPrompts && ( className="bg-green-100 hover:bg-green-200 cursor-pointer"
<TabsTrigger value="company">Company Prompts</TabsTrigger> onClick={() => document.getElementById('general-section')?.scrollIntoView({ behavior: 'smooth' })}
)} >
</TabsList> General
</Badge>
<div className="flex-1 min-h-0"> {currentPrompt.debugData.promptSources?.companyPrompts?.map((company, idx) => (
<ScrollArea className="h-full w-full"> <Badge
<TabsContent value="full" className="m-0 p-0 h-full"> key={idx}
<Code className="whitespace-pre-wrap p-4 break-normal max-w-full"> variant="outline"
{currentPrompt.prompt} className="bg-blue-100 hover:bg-blue-200 cursor-pointer"
</Code> onClick={() => document.getElementById('company-section')?.scrollIntoView({ behavior: 'smooth' })}
</TabsContent> >
{company.companyName || `Company ${company.company}`}
</Badge>
))}
<TabsContent value="system" className="m-0 p-0 h-full"> <Badge
<Code className="whitespace-pre-wrap p-4 break-normal max-w-full"> variant="outline"
{currentPrompt.debugData.promptSources.systemPrompt?.prompt_text || "No system prompt available"} className="bg-amber-100 hover:bg-amber-200 cursor-pointer"
</Code> onClick={() => document.getElementById('taxonomy-section')?.scrollIntoView({ behavior: 'smooth' })}
</TabsContent> >
Taxonomy
<TabsContent value="general" className="m-0 p-0 h-full"> </Badge>
<Code className="whitespace-pre-wrap p-4 break-normal max-w-full"> <Badge
{currentPrompt.debugData.promptSources.generalPrompt?.prompt_text || "No general prompt available"} variant="outline"
</Code> className="bg-pink-100 hover:bg-pink-200 cursor-pointer"
</TabsContent> onClick={() => document.getElementById('product-section')?.scrollIntoView({ behavior: 'smooth' })}
>
{hasCompanyPrompts && ( Products
<TabsContent value="company" className="m-0 p-0 h-full"> </Badge>
<div className="space-y-4">
{currentPrompt.debugData.promptSources.companyPrompts?.map((prompt, idx) => (
<div key={idx} className="border rounded-md p-2">
<div className="bg-muted p-2 mb-2 rounded-sm">
<strong>{prompt.companyName}</strong> <span className="text-sm text-muted-foreground">(ID: {prompt.company})</span>
</div> </div>
<Code className="whitespace-pre-wrap p-4 break-normal max-w-full"> </CardContent>
{prompt.prompt_text} </Card>
<ScrollArea className="flex-1 w-full overflow-y-auto">
{currentPrompt.debugData.apiFormat.map((message, idx: number) => (
<div key={idx} className="border rounded-md p-2 mb-4">
<div
id={message.role === 'system' ? 'system-message' : ''}
className={`p-2 mb-2 rounded-sm font-medium ${
message.role === 'system'
? 'bg-purple-50 text-purple-800'
: 'bg-green-50 text-green-800'
}`}
>
Role: {message.role}
</div>
<Code className={`whitespace-pre-wrap p-4 break-normal max-w-full ${
message.role === 'system'
? 'bg-purple-50/30'
: 'bg-green-50/30'
}`}>
{message.role === 'user' ? (
<div className="text-wrapper">
{(() => {
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(
<div id="general-section" key="general" className="border-l-4 border-green-500 pl-4 py-0 my-1">
<div className="text-xs font-semibold text-green-700 mb-2">
General Prompt
</div>
<pre className="whitespace-pre-wrap">
{content.substring(0, generalEndIndex)}
</pre>
</div>
);
}
// Company-specific section if present
if (companySpecificStartIndex >= 0 && companySpecificEndIndex >= 0) {
segments.push(
<div id="company-section" key="company" className="border-l-4 border-blue-500 pl-4 py-0 my-1">
<div className="text-xs font-semibold text-blue-700 mb-2">
Company-Specific Instructions
</div>
<pre className="whitespace-pre-wrap">
{content.substring(companySpecificStartIndex, companySpecificEndIndex + '--- END COMPANY-SPECIFIC INSTRUCTIONS ---'.length)}
</pre>
</div>
);
}
// Taxonomy section
if (actualTaxonomyStartIndex >= 0) {
const taxEnd = taxonomyEndIndex;
segments.push(
<div id="taxonomy-section" key="taxonomy" className="border-l-4 border-amber-500 pl-4 py-0 my-1">
<div className="text-xs font-semibold text-amber-700 mb-2">
Taxonomy Data
</div>
<pre className="whitespace-pre-wrap">
{content.substring(actualTaxonomyStartIndex, taxEnd)}
</pre>
</div>
);
}
// Product data section
if (productDataStartIndex >= 0) {
segments.push(
<div id="product-section" key="product" className="border-l-4 border-pink-500 pl-4 py-0 my-1">
<div className="text-xs font-semibold text-pink-700 mb-2">
Product Data
</div>
<pre className="whitespace-pre-wrap">
{content.substring(productDataStartIndex)}
</pre>
</div>
);
}
return <>{segments}</>;
})()}
</div>
) : (
<pre className="whitespace-pre-wrap">{message.content}</pre>
)}
</Code> </Code>
</div> </div>
))} ))}
</div>
</TabsContent>
)}
</ScrollArea> </ScrollArea>
</div> </div>
</Tabs>
) : ( ) : (
<ScrollArea className="h-full w-full"> <ScrollArea className="h-full w-full">
<Code className="whitespace-pre-wrap p-4 break-normal max-w-full"> <Code className="whitespace-pre-wrap p-4 break-normal max-w-full">
@@ -379,8 +489,9 @@ export const AiValidationDialogs: React.FC<AiValidationDialogsProps> = ({
className="h-full bg-primary transition-all duration-500" className="h-full bg-primary transition-all duration-500"
style={{ style={{
width: `${ width: `${
aiValidationProgress.progressPercent ?? aiValidationProgress.progressPercent !== undefined
Math.round((aiValidationProgress.step / 5) * 100) ? Math.round(aiValidationProgress.progressPercent)
: Math.round((aiValidationProgress.step / 5) * 100)
}%`, }%`,
backgroundColor: backgroundColor:
aiValidationProgress.step === -1 aiValidationProgress.step === -1
@@ -394,8 +505,9 @@ export const AiValidationDialogs: React.FC<AiValidationDialogsProps> = ({
{aiValidationProgress.step === -1 {aiValidationProgress.step === -1
? "❌" ? "❌"
: `${ : `${
aiValidationProgress.progressPercent ?? aiValidationProgress.progressPercent !== undefined
Math.round((aiValidationProgress.step / 5) * 100) ? Math.round(aiValidationProgress.progressPercent)
: Math.round((aiValidationProgress.step / 5) * 100)
}%`} }%`}
</div> </div>
</div> </div>

View File

@@ -56,6 +56,10 @@ export interface CurrentPrompt {
basePrompt: string; basePrompt: string;
sampleFullPrompt: string; sampleFullPrompt: string;
promptLength: number; promptLength: number;
apiFormat?: Array<{
role: string;
content: string;
}>;
promptSources?: { promptSources?: {
systemPrompt?: { id: number; prompt_text: string }; systemPrompt?: { id: number; prompt_text: string };
generalPrompt?: { id: number; prompt_text: string }; generalPrompt?: { id: number; prompt_text: string };
@@ -334,7 +338,8 @@ export const useAiValidation = <T extends string>(
sampleFullPrompt: result.sampleFullPrompt || '', sampleFullPrompt: result.sampleFullPrompt || '',
promptLength: result.promptLength || (promptContent ? promptContent.length : 0), promptLength: result.promptLength || (promptContent ? promptContent.length : 0),
promptSources: result.promptSources, promptSources: result.promptSources,
estimatedProcessingTime: result.estimatedProcessingTime estimatedProcessingTime: result.estimatedProcessingTime,
apiFormat: result.apiFormat
} }
})); }));
} else { } else {