Split off AI prompt into separate file, auto include taxonomy in prompt, create prompt debug page

This commit is contained in:
2025-02-21 11:50:46 -05:00
parent 7f7e6fdd1f
commit cff176e7a3
5 changed files with 475 additions and 28 deletions

View File

@@ -17,6 +17,7 @@ import { Vendors } from '@/pages/Vendors';
import { Categories } from '@/pages/Categories';
import { Import } from '@/pages/Import';
import { ChakraProvider } from '@chakra-ui/react';
import { AiValidationDebug } from "@/pages/AiValidationDebug"
const queryClient = new QueryClient();
@@ -71,6 +72,7 @@ function App() {
<Route path="/analytics" element={<Analytics />} />
<Route path="/settings" element={<Settings />} />
<Route path="/forecasting" element={<Forecasting />} />
<Route path="/ai-validation/debug" element={<AiValidationDebug />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</Routes>

View File

@@ -0,0 +1,206 @@
import { useEffect, useState } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Code } from "@/components/ui/code"
import { useToast } from "@/hooks/use-toast"
import { Loader2 } from "lucide-react"
import config from "@/config"
interface CacheStatus {
isCacheValid: boolean
lastUpdated: string | null
timeUntilExpiry: string
}
interface TaxonomyStats {
categories: number
themes: number
colors: number
taxCodes: number
sizeCategories: number
}
interface DebugData {
cacheStatus: CacheStatus
taxonomyStats: TaxonomyStats | null
basePrompt: string
sampleFullPrompt: string
promptLength: number
}
export function AiValidationDebug() {
const [isLoading, setIsLoading] = useState(false)
const [debugData, setDebugData] = useState<DebugData | null>(null)
const { toast } = useToast()
const fetchDebugData = async () => {
setIsLoading(true)
try {
const response = await fetch(`${config.apiUrl}/ai-validation/debug`)
if (!response.ok) {
throw new Error('Failed to fetch debug data')
}
const data = await response.json()
setDebugData(data)
} catch (error) {
console.error('Error fetching debug data:', error)
toast({
variant: "destructive",
title: "Error",
description: error instanceof Error ? error.message : "Failed to fetch debug data"
})
} finally {
setIsLoading(false)
}
}
const refreshCache = async () => {
if (!confirm('Are you sure you want to refresh the cache?')) return
setIsLoading(true)
try {
const response = await fetch(`${config.apiUrl}/ai-validation/refresh-cache`, {
method: 'POST'
})
if (!response.ok) {
throw new Error('Failed to refresh cache')
}
const data = await response.json()
if (data.success) {
toast({
title: "Success",
description: "Cache refreshed successfully"
})
fetchDebugData()
} else {
throw new Error(data.error || 'Failed to refresh cache')
}
} catch (error) {
console.error('Error refreshing cache:', error)
toast({
variant: "destructive",
title: "Error",
description: error instanceof Error ? error.message : "Failed to refresh cache"
})
} finally {
setIsLoading(false)
}
}
useEffect(() => {
fetchDebugData()
}, [])
return (
<div className="container mx-auto py-6 space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold tracking-tight">AI Validation Debug</h1>
<div className="space-x-4">
<Button
variant="outline"
onClick={fetchDebugData}
disabled={isLoading}
>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Refresh Data
</Button>
<Button
variant="outline"
onClick={refreshCache}
disabled={isLoading}
>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Force Cache Refresh
</Button>
</div>
</div>
{debugData && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card>
<CardHeader>
<CardTitle>Cache Status</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
<div>Valid: {debugData.cacheStatus.isCacheValid ? "Yes" : "No"}</div>
<div>Last Updated: {debugData.cacheStatus.lastUpdated || "never"}</div>
<div>Expires in: {debugData.cacheStatus.timeUntilExpiry}</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Taxonomy Stats</CardTitle>
</CardHeader>
<CardContent>
{debugData.taxonomyStats ? (
<div className="space-y-2">
<div>Categories: {debugData.taxonomyStats.categories}</div>
<div>Themes: {debugData.taxonomyStats.themes}</div>
<div>Colors: {debugData.taxonomyStats.colors}</div>
<div>Tax Codes: {debugData.taxonomyStats.taxCodes}</div>
<div>Size Categories: {debugData.taxonomyStats.sizeCategories}</div>
</div>
) : (
<div>No taxonomy data available</div>
)}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Prompt Length</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="space-y-2">
<div>Characters: {debugData.promptLength}</div>
<div>Tokens (est.): ~{Math.round(debugData.promptLength / 4)}</div>
</div>
<div className="space-y-2">
<label htmlFor="costPerMillion" className="text-sm text-muted-foreground">
Cost per million tokens ($)
</label>
<input
id="costPerMillion"
type="number"
className="w-full px-3 py-2 border rounded-md"
defaultValue="3"
onChange={(e) => {
const costPerMillion = parseFloat(e.target.value)
if (!isNaN(costPerMillion)) {
const tokens = Math.round(debugData.promptLength / 4)
const cost = (tokens / 1_000_000) * costPerMillion * 100 // Convert to cents
const costElement = document.getElementById('tokenCost')
if (costElement) {
costElement.textContent = cost.toFixed(1)
}
}
}}
/>
<div className="text-sm">
Cost: <span id="tokenCost">{((Math.round(debugData.promptLength / 4) / 1_000_000) * 3 * 100).toFixed(1)}</span>¢
</div>
</div>
</div>
</CardContent>
</Card>
<Card className="col-span-full">
<CardHeader>
<CardTitle>Full Sample Prompt</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-[500px] w-full rounded-md border p-4">
<Code className="whitespace-pre-wrap">{debugData.sampleFullPrompt}</Code>
</ScrollArea>
</CardContent>
</Card>
</div>
)}
</div>
)
}

View File

@@ -27,7 +27,7 @@ const BASE_IMPORT_FIELDS = [
label: "UPC",
key: "upc",
description: "Universal Product Code/Barcode",
alternateMatches: ["upc","UPC","barcode", "bar code", "JAN", "EAN"],
alternateMatches: ["barcode", "bar code", "jan", "ean"],
fieldType: { type: "input" },
width: 140,
validations: [
@@ -94,7 +94,7 @@ const BASE_IMPORT_FIELDS = [
label: "MSRP",
key: "msrp",
description: "Manufacturer's Suggested Retail Price",
alternateMatches: ["retail", "retail price", "sugg retail", "price", "sugg. Retail","msrp","MSRP"],
alternateMatches: ["retail", "retail price", "sugg retail", "price", "sugg. retail","default price"],
fieldType: {
type: "input",
price: true
@@ -136,7 +136,7 @@ const BASE_IMPORT_FIELDS = [
label: "Case Pack",
key: "case_qty",
description: "Number of units per case",
alternateMatches: ["mc qty","MC Qty","case qty","Case Qty"],
alternateMatches: ["mc qty","case qty","case pack"],
fieldType: { type: "input" },
width: 50,
validations: [
@@ -208,6 +208,7 @@ const BASE_IMPORT_FIELDS = [
label: "Weight",
key: "weight",
description: "Product weight (in lbs)",
alternateMatches: ["weight (lbs.)"],
fieldType: { type: "input" },
width: 100,
validations: [
@@ -295,6 +296,7 @@ const BASE_IMPORT_FIELDS = [
label: "Description",
key: "description",
description: "Detailed product description",
alternateMatches: ["details/description"],
fieldType: {
type: "input",
multiline: true