Spaces:
Running
Running
"use client" | |
import { useState } from "react" | |
import { Progress } from "@/components/ui/progress" | |
import { Badge } from "@/components/ui/badge" | |
import { Button } from "@/components/ui/button" | |
import { ArrowLeft } from "lucide-react" | |
import { SystemInfoForm } from "./system-info-form" | |
import { CategorySelection } from "./category-selection" | |
import { CategoryEvaluation } from "./category-evaluation" | |
import { EvaluationForm } from "./evaluation-form" | |
import { ResultsDashboard } from "./results-dashboard" | |
import { getAllCategories, getCategoryById } from "@/lib/schema" | |
export type SystemInfo = { | |
name: string | |
url: string | |
provider: string | |
version: string | |
deploymentContexts: string[] | |
modelTag?: string | |
knowledgeCutoff?: string | |
modelType?: "foundational" | "fine-tuned" | "na" | |
inputModalities?: string[] | |
outputModalities?: string[] | |
id?: string | |
systemName?: string | |
systemTypes?: string[] | |
deploymentContext?: string | |
evaluationDate?: string | |
evaluator?: string | |
} | |
export type CategoryScore = { | |
benchmarkScore: number | |
processScore: number | |
totalScore: number | |
status: "strong" | "adequate" | "weak" | "insufficient" | "not-evaluated" | |
// optional metadata | |
totalQuestions?: number | |
totalApplicable?: number | |
naCount?: number | |
} | |
export type EvaluationData = { | |
systemInfo: SystemInfo | null | |
selectedCategories: string[] | |
excludedCategoryReasons?: Record<string, string> | |
categoryScores: Record<string, CategoryScore> | |
categoryEvaluationsDetailed?: Record<string, any> | |
currentCategory: string | null | |
} | |
interface AIEvaluationDashboardProps { | |
onBack?: () => void | |
onSaveEvaluation?: (evaluation: any) => void | |
} | |
export function AIEvaluationDashboard({ onBack, onSaveEvaluation }: AIEvaluationDashboardProps) { | |
const [currentStep, setCurrentStep] = useState<"system-info" | "categories" | "evaluation" | "results">("system-info") | |
const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0) | |
const [evaluationData, setEvaluationData] = useState<EvaluationData>({ | |
systemInfo: null, | |
selectedCategories: [], | |
categoryScores: {}, | |
currentCategory: null, | |
}) | |
const steps = [ | |
{ id: "system-info", label: "System Info", number: 1 }, | |
{ id: "categories", label: "Categories", number: 2 }, | |
{ id: "evaluation", label: "Evaluation", number: 3 }, | |
{ id: "results", label: "Results", number: 4 }, | |
] | |
const getOverallProgress = () => { | |
if (currentStep === "system-info") return 10 | |
if (currentStep === "categories") return 25 | |
if (currentStep === "evaluation") { | |
const completed = Object.keys(evaluationData.categoryScores).length | |
const total = evaluationData.selectedCategories.length | |
return total > 0 ? 25 + (completed / total) * 65 : 25 | |
} | |
return 100 | |
} | |
const handleSystemInfoComplete = (systemInfo: SystemInfo) => { | |
setEvaluationData((prev) => ({ ...prev, systemInfo })) | |
setCurrentStep("categories") | |
} | |
const handleCategoriesSelected = (categories: string[]) => { | |
setEvaluationData((prev) => ({ ...prev, selectedCategories: categories })) | |
setCurrentCategoryIndex(0) | |
setCurrentStep("evaluation") | |
} | |
const handleCategoriesSelectedWithReasons = (categories: string[], excludedReasons: Record<string, string>) => { | |
setEvaluationData((prev) => ({ ...prev, selectedCategories: categories, excludedCategoryReasons: excludedReasons })) | |
setCurrentCategoryIndex(0) | |
setCurrentStep("evaluation") | |
} | |
const handleCategoryComplete = (categoryId: string, score: CategoryScore) => { | |
console.log("[v0] handleCategoryComplete called with:", { categoryId, score }) | |
setEvaluationData((prev) => { | |
const newCategoryScores = { ...prev.categoryScores, [categoryId]: score } | |
console.log("[v0] Updated categoryScores:", newCategoryScores) | |
return { | |
...prev, | |
categoryScores: newCategoryScores, | |
} | |
}) | |
const nextIndex = currentCategoryIndex + 1 | |
console.log( | |
"[v0] Current index:", | |
currentCategoryIndex, | |
"Next index:", | |
nextIndex, | |
"Total categories:", | |
evaluationData.selectedCategories.length, | |
) | |
if (nextIndex >= evaluationData.selectedCategories.length) { | |
console.log("[v0] All categories complete, moving to results") | |
setCurrentStep("results") | |
} else { | |
console.log("[v0] Moving to next category at index:", nextIndex) | |
setCurrentCategoryIndex(nextIndex) | |
} | |
} | |
const handleSaveDetailed = (categoryId: string, data: any) => { | |
console.log('[v0] handleSaveDetailed called for', categoryId, data) | |
setEvaluationData((prev) => ({ | |
...prev, | |
categoryEvaluationsDetailed: { | |
...(prev.categoryEvaluationsDetailed || {}), | |
[categoryId]: data, | |
}, | |
})) | |
} | |
const handleSaveEvaluation = async () => { | |
console.log("[v0] handleSaveEvaluation called") | |
console.log("[v0] evaluationData:", evaluationData) | |
if (!evaluationData.systemInfo || evaluationData.selectedCategories.length === 0) { | |
alert("Please complete system information and select categories before saving.") | |
return | |
} | |
const timestamp = Date.now() | |
const evaluationId = `eval-${timestamp}` | |
console.log("[v0] Generated evaluationId:", evaluationId) | |
console.log("[v0] Processing category scores:", evaluationData.categoryScores) | |
const capabilityCategories = evaluationData.selectedCategories.filter((cat) => { | |
const category = getCategoryById(cat) | |
console.log("[v0] Category check:", cat, "type:", category?.type) | |
return category?.type === "capability" | |
}) | |
console.log("[v0] Capability categories:", capabilityCategories) | |
const riskCategories = evaluationData.selectedCategories.filter((cat) => { | |
const category = getCategoryById(cat) | |
return category?.type === "risk" | |
}) | |
console.log("[v0] Risk categories:", riskCategories) | |
const strongCategories = Object.entries(evaluationData.categoryScores) | |
.filter(([_, score]) => { | |
console.log("[v0] Checking score for strong:", score) | |
return score.status === "strong" | |
}) | |
.map(([catId]) => catId) | |
console.log("[v0] Strong categories:", strongCategories) | |
const adequateCategories = Object.entries(evaluationData.categoryScores) | |
.filter(([_, score]) => score.status === "adequate") | |
.map(([catId]) => catId) | |
console.log("[v0] Adequate categories:", adequateCategories) | |
const weakCategories = Object.entries(evaluationData.categoryScores) | |
.filter(([_, score]) => score.status === "weak") | |
.map(([catId]) => catId) | |
console.log("[v0] Weak categories:", weakCategories) | |
const insufficientCategories = Object.entries(evaluationData.categoryScores) | |
.filter(([_, score]) => score.status === "insufficient") | |
.map(([catId]) => catId) | |
console.log("[v0] Insufficient categories:", insufficientCategories) | |
const evaluationJson = { | |
id: evaluationId, | |
systemName: evaluationData.systemInfo.name, | |
provider: evaluationData.systemInfo.provider, | |
version: evaluationData.systemInfo.url || "1.0", | |
deploymentContext: evaluationData.systemInfo.deploymentContexts.join(", ") || "Production", | |
evaluator: "Current User", | |
inputModalities: evaluationData.systemInfo.inputModalities || ["Text"], | |
outputModalities: evaluationData.systemInfo.outputModalities || ["Text"], | |
evaluationDate: new Date().toISOString().split("T")[0], | |
selectedCategories: evaluationData.selectedCategories, | |
excludedCategoryReasons: evaluationData.excludedCategoryReasons || {}, | |
categoryEvaluations: evaluationData.categoryEvaluationsDetailed || evaluationData.categoryScores, | |
overallStats: { | |
completenessScore: 85, // Safe default value | |
totalApplicable: evaluationData.selectedCategories.length, | |
capabilityApplicable: capabilityCategories.length, | |
riskApplicable: riskCategories.length, | |
strongCategories, | |
adequateCategories, | |
weakCategories, | |
insufficientCategories, | |
}, | |
} | |
console.log("[v0] Final evaluationJson:", evaluationJson) | |
try { | |
console.log("[v0] Creating blob and download") | |
const blob = new Blob([JSON.stringify(evaluationJson, null, 2)], { type: "application/json" }) | |
const url = URL.createObjectURL(blob) | |
const a = document.createElement("a") | |
a.href = url | |
a.download = `${evaluationId}.json` | |
document.body.appendChild(a) | |
a.click() | |
document.body.removeChild(a) | |
URL.revokeObjectURL(url) | |
console.log("[v0] Download completed successfully") | |
alert( | |
`Evaluation saved as ${evaluationId}.json. Please upload this file to the public/evaluations/ directory to see it on the homepage.`, | |
) | |
onBack?.() | |
} catch (error) { | |
console.error("[v0] Error saving evaluation:", error) | |
alert("Error saving evaluation. Please try again.") | |
} | |
} | |
const renderCurrentStep = () => { | |
switch (currentStep) { | |
case "system-info": | |
return <SystemInfoForm onSubmit={handleSystemInfoComplete} initialData={evaluationData.systemInfo} /> | |
case "categories": | |
return ( | |
<CategorySelection | |
categories={getAllCategories()} | |
selectedCategories={evaluationData.selectedCategories} | |
onSelectionChange={handleCategoriesSelectedWithReasons} | |
/> | |
) | |
case "evaluation": | |
return ( | |
<EvaluationForm | |
categories={getAllCategories()} | |
selectedCategories={evaluationData.selectedCategories} | |
categoryScores={evaluationData.categoryScores} | |
onScoreUpdate={(categoryId, score) => handleCategoryComplete(categoryId, score)} | |
onSaveDetailed={(catId, data) => handleSaveDetailed(catId, data)} | |
onComplete={() => setCurrentStep("results")} | |
/> | |
) | |
case "results": | |
return ( | |
<ResultsDashboard | |
systemInfo={evaluationData.systemInfo} | |
categories={getAllCategories()} | |
selectedCategories={evaluationData.selectedCategories} | |
categoryScores={evaluationData.categoryScores} | |
excludedCategoryReasons={evaluationData.excludedCategoryReasons || {}} | |
/> | |
) | |
default: | |
return null | |
} | |
} | |
return ( | |
<div className="min-h-screen bg-background flex flex-col"> | |
{/* Header */} | |
<header className="border-b bg-card"> | |
<div className="container mx-auto px-4 sm:px-6 py-4"> | |
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3"> | |
<div className="flex items-center gap-4"> | |
{onBack && ( | |
<Button variant="ghost" size="sm" onClick={onBack} className="gap-2"> | |
<ArrowLeft className="h-4 w-4" /> | |
Back | |
</Button> | |
)} | |
<div> | |
<h1 className="text-xl sm:text-2xl font-bold font-heading text-foreground">New Eval Card</h1> | |
<p className="text-sm sm:text-base text-muted-foreground">Create comprehensive AI system evaluation card</p> | |
</div> | |
</div> | |
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-3 sm:gap-4 w-full sm:w-auto"> | |
<div className="text-left sm:text-right"> | |
<p className="text-sm text-muted-foreground">Overall Progress</p> | |
<div className="flex items-center gap-2"> | |
<Progress value={getOverallProgress()} className="w-24" /> | |
<span className="text-sm font-medium">{Math.round(getOverallProgress())}%</span> | |
</div> | |
</div> | |
<div className="flex items-center gap-2 w-full sm:w-auto"> | |
{evaluationData.systemInfo && ( | |
<Badge variant="secondary" className="font-medium"> | |
{evaluationData.systemInfo.name} | |
</Badge> | |
)} | |
<Button onClick={handleSaveEvaluation} className="gap-2 w-full sm:w-auto"> | |
<span className="hidden sm:inline">Save Eval Card</span> | |
<span className="sm:hidden">Save</span> | |
</Button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</header> | |
{/* Step tabs navigation */} | |
<div className="border-b bg-card"> | |
<div className="container mx-auto px-4 sm:px-6 py-4"> | |
<div className="flex items-center space-x-4 sm:space-x-8 overflow-x-auto"> | |
{steps.map((step) => { | |
const isActive = currentStep === step.id | |
const isCompleted = | |
(step.id === "system-info" && evaluationData.systemInfo) || | |
(step.id === "categories" && evaluationData.selectedCategories.length > 0) || | |
(step.id === "evaluation" && Object.keys(evaluationData.categoryScores).length > 0) || | |
(step.id === "results" && currentStep === "results") | |
return ( | |
<div | |
key={step.id} | |
className={`flex items-center gap-2 sm:gap-3 py-4 border-b-2 transition-colors whitespace-nowrap ${ | |
isActive | |
? "border-primary text-primary" | |
: isCompleted | |
? "border-green-500 text-green-600" | |
: "border-transparent text-muted-foreground" | |
}`} | |
> | |
<div | |
className={`flex items-center justify-center w-6 h-6 sm:w-8 sm:h-8 rounded-full text-xs sm:text-sm font-medium ${ | |
isActive | |
? "bg-primary text-primary-foreground" | |
: isCompleted | |
? "bg-green-500 text-white" | |
: "bg-muted text-muted-foreground" | |
}`} | |
> | |
{step.number} | |
</div> | |
<span className="font-medium text-sm sm:text-base">{step.label}</span> | |
</div> | |
) | |
})} | |
</div> | |
</div> | |
</div> | |
<div className="container mx-auto px-4 sm:px-6 py-4 sm:py-6 flex-1 min-h-0"> | |
{renderCurrentStep()} | |
</div> | |
</div> | |
) | |
} | |