Spaces:
Running
Running
Avijit Ghosh
commited on
Commit
·
94700ed
1
Parent(s):
56d40a2
fixed some bugs
Browse files- app/about/page.tsx +0 -25
- app/evaluation/[id]/page.client.tsx +351 -60
- app/page.tsx +49 -13
- components/evaluation-card.tsx +183 -241
app/about/page.tsx
CHANGED
|
@@ -176,31 +176,6 @@ export default function AboutPage() {
|
|
| 176 |
</Link>
|
| 177 |
</CardContent>
|
| 178 |
</Card>
|
| 179 |
-
|
| 180 |
-
{/* <Card>
|
| 181 |
-
<CardHeader>
|
| 182 |
-
<CardTitle>Technical Implementation</CardTitle>
|
| 183 |
-
<CardDescription>
|
| 184 |
-
Built with modern web technologies for performance and accessibility
|
| 185 |
-
</CardDescription>
|
| 186 |
-
</CardHeader>
|
| 187 |
-
<CardContent>
|
| 188 |
-
<div className="grid gap-3 md:grid-cols-3">
|
| 189 |
-
<div className="text-center p-3 border rounded-lg">
|
| 190 |
-
<h4 className="font-semibold">Next.js 14</h4>
|
| 191 |
-
<p className="text-xs text-muted-foreground">React framework with SSR</p>
|
| 192 |
-
</div>
|
| 193 |
-
<div className="text-center p-3 border rounded-lg">
|
| 194 |
-
<h4 className="font-semibold">TypeScript</h4>
|
| 195 |
-
<p className="text-xs text-muted-foreground">Type-safe development</p>
|
| 196 |
-
</div>
|
| 197 |
-
<div className="text-center p-3 border rounded-lg">
|
| 198 |
-
<h4 className="font-semibold">Tailwind CSS</h4>
|
| 199 |
-
<p className="text-xs text-muted-foreground">Utility-first styling</p>
|
| 200 |
-
</div>
|
| 201 |
-
</div>
|
| 202 |
-
</CardContent>
|
| 203 |
-
</Card> */}
|
| 204 |
</div>
|
| 205 |
|
| 206 |
<Separator className="my-8" />
|
|
|
|
| 176 |
</Link>
|
| 177 |
</CardContent>
|
| 178 |
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
</div>
|
| 180 |
|
| 181 |
<Separator className="my-8" />
|
app/evaluation/[id]/page.client.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
| 8 |
import { Badge } from "@/components/ui/badge"
|
| 9 |
import { ArrowLeft, Download, Eye, EyeOff, Info, Database, Globe, Calendar, User, Building, Cpu, MonitorSpeaker, Hash, Tags, Clock, Activity, Settings, Sun, Moon,
|
| 10 |
// Section icons
|
| 11 |
-
Target, BarChart3, Shield, AlertTriangle,
|
| 12 |
// Capability icons
|
| 13 |
MessageCircle, Heart, Brain, Lightbulb, BookOpen, Camera, Hand, Search, Bot,
|
| 14 |
// Risk icons
|
|
@@ -102,6 +102,39 @@ export default function EvaluationDetailsPage() {
|
|
| 102 |
const [expandedNegatives, setExpandedNegatives] = useState<Record<string, boolean>>({})
|
| 103 |
const toggleNegatives = (key: string) => setExpandedNegatives((p) => ({ ...p, [key]: !p[key] }))
|
| 104 |
const [visibleCategories, setVisibleCategories] = useState<Record<string, boolean>>({})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
const toggleCategoryVisibility = (id: string) => setVisibleCategories((p) => ({ ...p, [id]: !p[id] }))
|
| 106 |
const selectAll = () => {
|
| 107 |
const map: Record<string, boolean> = {}
|
|
@@ -285,7 +318,7 @@ export default function EvaluationDetailsPage() {
|
|
| 285 |
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
| 286 |
<span className="sr-only">Toggle theme</span>
|
| 287 |
</Button>
|
| 288 |
-
<Button variant="outline" size="sm">
|
| 289 |
<Download className="h-4 w-4 mr-2" />
|
| 290 |
Export Report
|
| 291 |
</Button>
|
|
@@ -472,7 +505,39 @@ export default function EvaluationDetailsPage() {
|
|
| 472 |
</div>
|
| 473 |
<div className="flex-1">
|
| 474 |
<p className="text-sm font-medium text-muted-foreground">Completeness Score</p>
|
| 475 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
</div>
|
| 477 |
</div>
|
| 478 |
</div>
|
|
@@ -658,74 +723,300 @@ export default function EvaluationDetailsPage() {
|
|
| 658 |
Overall Statistics
|
| 659 |
</CardTitle>
|
| 660 |
</CardHeader>
|
| 661 |
-
<CardContent>
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 668 |
</div>
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 672 |
</div>
|
| 673 |
-
<div className="text-sm text-blue-600 dark:text-blue-400">Adequate</div>
|
| 674 |
</div>
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 678 |
</div>
|
| 679 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 680 |
</div>
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 684 |
</div>
|
| 685 |
-
<div className="text-sm text-red-600 dark:text-red-400">Insufficient</div>
|
| 686 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 687 |
</div>
|
| 688 |
</CardContent>
|
| 689 |
</Card>
|
| 690 |
|
| 691 |
-
{/* Priority Areas (show only weak/insufficient like results) */}
|
| 692 |
-
{((evaluation.overallStats?.weakCategories || []).length > 0 || (evaluation.overallStats?.insufficientCategories || []).length > 0) && (
|
| 693 |
-
<Card className="mb-6">
|
| 694 |
-
<CardHeader>
|
| 695 |
-
<CardTitle className="flex items-center gap-2 text-xl font-bold">
|
| 696 |
-
<AlertTriangle className="h-5 w-5 text-amber-600" />
|
| 697 |
-
Priority Areas
|
| 698 |
-
</CardTitle>
|
| 699 |
-
</CardHeader>
|
| 700 |
-
<CardContent>
|
| 701 |
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
| 702 |
-
{[...(evaluation.overallStats?.insufficientCategories || []), ...(evaluation.overallStats?.weakCategories || [])]
|
| 703 |
-
.filter(Boolean)
|
| 704 |
-
.map((catId: string) => {
|
| 705 |
-
const category = getCategoryById(catId)
|
| 706 |
-
return (
|
| 707 |
-
<div key={catId} className="p-3 border rounded-md flex items-center justify-between">
|
| 708 |
-
<div>
|
| 709 |
-
<div className="font-medium flex items-center gap-2">
|
| 710 |
-
{(() => {
|
| 711 |
-
const IconComponent = getCategoryIcon(catId)
|
| 712 |
-
return <IconComponent className="h-4 w-4 text-muted-foreground" />
|
| 713 |
-
})()}
|
| 714 |
-
{category?.name || catId}
|
| 715 |
-
</div>
|
| 716 |
-
<div className="text-xs text-muted-foreground ml-6">{category?.description}</div>
|
| 717 |
-
</div>
|
| 718 |
-
<Badge variant={evaluation.overallStats?.insufficientCategories?.includes(catId) ? "destructive" : "outline"}>
|
| 719 |
-
{evaluation.overallStats?.insufficientCategories?.includes(catId) ? "insufficient" : "weak"}
|
| 720 |
-
</Badge>
|
| 721 |
-
</div>
|
| 722 |
-
)
|
| 723 |
-
})}
|
| 724 |
-
</div>
|
| 725 |
-
</CardContent>
|
| 726 |
-
</Card>
|
| 727 |
-
)}
|
| 728 |
-
|
| 729 |
{/* Evaluation Details */}
|
| 730 |
{evaluation.categoryEvaluations &&
|
| 731 |
Object.entries(evaluation.categoryEvaluations)
|
|
|
|
| 8 |
import { Badge } from "@/components/ui/badge"
|
| 9 |
import { ArrowLeft, Download, Eye, EyeOff, Info, Database, Globe, Calendar, User, Building, Cpu, MonitorSpeaker, Hash, Tags, Clock, Activity, Settings, Sun, Moon,
|
| 10 |
// Section icons
|
| 11 |
+
Target, BarChart3, Shield, AlertTriangle, ShieldAlert,
|
| 12 |
// Capability icons
|
| 13 |
MessageCircle, Heart, Brain, Lightbulb, BookOpen, Camera, Hand, Search, Bot,
|
| 14 |
// Risk icons
|
|
|
|
| 102 |
const [expandedNegatives, setExpandedNegatives] = useState<Record<string, boolean>>({})
|
| 103 |
const toggleNegatives = (key: string) => setExpandedNegatives((p) => ({ ...p, [key]: !p[key] }))
|
| 104 |
const [visibleCategories, setVisibleCategories] = useState<Record<string, boolean>>({})
|
| 105 |
+
const [capabilitiesStatsExpanded, setCapabilitiesStatsExpanded] = useState(false)
|
| 106 |
+
const [risksStatsExpanded, setRisksStatsExpanded] = useState(false)
|
| 107 |
+
|
| 108 |
+
const exportReport = () => {
|
| 109 |
+
const reportData = {
|
| 110 |
+
systemName: evaluation.systemName,
|
| 111 |
+
provider: evaluation.provider,
|
| 112 |
+
version: evaluation.version,
|
| 113 |
+
evaluationDate: evaluation.evaluationDate,
|
| 114 |
+
exportDate: new Date().toISOString(),
|
| 115 |
+
url: evaluation.url,
|
| 116 |
+
inputModalities: evaluation.inputModalities,
|
| 117 |
+
outputModalities: evaluation.outputModalities,
|
| 118 |
+
deploymentContexts: evaluation.deploymentContexts,
|
| 119 |
+
knowledgeCutoff: evaluation.knowledgeCutoff,
|
| 120 |
+
modelType: evaluation.modelType,
|
| 121 |
+
completenessScore: Math.round((Object.keys(evaluation.categoryEvaluations || {}).length / getAllCategories().length) * 100),
|
| 122 |
+
overallStats: evaluation.overallStats,
|
| 123 |
+
categoryEvaluations: evaluation.categoryEvaluations,
|
| 124 |
+
processResponses: evaluation.processResponses,
|
| 125 |
+
evaluator: evaluation.evaluator
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
const blob = new Blob([JSON.stringify(reportData, null, 2)], { type: "application/json" })
|
| 129 |
+
const url = URL.createObjectURL(blob)
|
| 130 |
+
const a = document.createElement("a")
|
| 131 |
+
a.href = url
|
| 132 |
+
a.download = `evaluation-report-${evaluation.systemName.replace(/[^a-zA-Z0-9]/g, '-')}-${new Date().toISOString().split("T")[0]}.json`
|
| 133 |
+
a.click()
|
| 134 |
+
URL.revokeObjectURL(url)
|
| 135 |
+
}
|
| 136 |
+
const toggleCapabilitiesStats = () => setCapabilitiesStatsExpanded(prev => !prev)
|
| 137 |
+
const toggleRisksStats = () => setRisksStatsExpanded(prev => !prev)
|
| 138 |
const toggleCategoryVisibility = (id: string) => setVisibleCategories((p) => ({ ...p, [id]: !p[id] }))
|
| 139 |
const selectAll = () => {
|
| 140 |
const map: Record<string, boolean> = {}
|
|
|
|
| 318 |
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
| 319 |
<span className="sr-only">Toggle theme</span>
|
| 320 |
</Button>
|
| 321 |
+
<Button variant="outline" size="sm" onClick={exportReport}>
|
| 322 |
<Download className="h-4 w-4 mr-2" />
|
| 323 |
Export Report
|
| 324 |
</Button>
|
|
|
|
| 505 |
</div>
|
| 506 |
<div className="flex-1">
|
| 507 |
<p className="text-sm font-medium text-muted-foreground">Completeness Score</p>
|
| 508 |
+
<Tooltip>
|
| 509 |
+
<TooltipTrigger asChild>
|
| 510 |
+
<p className="text-lg font-semibold text-foreground mt-1 cursor-help">
|
| 511 |
+
{evaluation.overallStats?.completenessScore ? `${evaluation.overallStats.completenessScore}%` : "N/A"}
|
| 512 |
+
</p>
|
| 513 |
+
</TooltipTrigger>
|
| 514 |
+
<TooltipContent className="max-w-sm">
|
| 515 |
+
<div className="space-y-2 text-xs">
|
| 516 |
+
<div className="font-semibold text-foreground">Weighted Average Formula:</div>
|
| 517 |
+
<div className="bg-muted p-2 rounded border text-foreground">
|
| 518 |
+
(Strong×4 + Adequate×3 + Weak×2 + Insufficient×1) ÷ (Total×4) × 100
|
| 519 |
+
</div>
|
| 520 |
+
<div className="space-y-1">
|
| 521 |
+
<div className="flex items-center gap-2">
|
| 522 |
+
<div className="w-2 h-2 bg-emerald-500 rounded-full"></div>
|
| 523 |
+
<span className="text-foreground"><strong>Strong (4 pts):</strong> Most evals reported</span>
|
| 524 |
+
</div>
|
| 525 |
+
<div className="flex items-center gap-2">
|
| 526 |
+
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
| 527 |
+
<span className="text-foreground"><strong>Adequate (3 pts):</strong> Many evals reported</span>
|
| 528 |
+
</div>
|
| 529 |
+
<div className="flex items-center gap-2">
|
| 530 |
+
<div className="w-2 h-2 bg-yellow-500 rounded-full"></div>
|
| 531 |
+
<span className="text-foreground"><strong>Weak (2 pts):</strong> Some evals reported</span>
|
| 532 |
+
</div>
|
| 533 |
+
<div className="flex items-center gap-2">
|
| 534 |
+
<div className="w-2 h-2 bg-red-500 rounded-full"></div>
|
| 535 |
+
<span className="text-foreground"><strong>Insufficient (1 pt):</strong> Few evals reported</span>
|
| 536 |
+
</div>
|
| 537 |
+
</div>
|
| 538 |
+
</div>
|
| 539 |
+
</TooltipContent>
|
| 540 |
+
</Tooltip>
|
| 541 |
</div>
|
| 542 |
</div>
|
| 543 |
</div>
|
|
|
|
| 723 |
Overall Statistics
|
| 724 |
</CardTitle>
|
| 725 |
</CardHeader>
|
| 726 |
+
<CardContent className="space-y-8">
|
| 727 |
+
{/* Capabilities Section */}
|
| 728 |
+
<div>
|
| 729 |
+
<div
|
| 730 |
+
className="cursor-pointer hover:bg-muted/50 transition-colors p-3 -m-3 rounded-lg mb-4"
|
| 731 |
+
onClick={toggleCapabilitiesStats}
|
| 732 |
+
>
|
| 733 |
+
<h3 className="flex items-center justify-between text-lg font-semibold">
|
| 734 |
+
<div className="flex items-center gap-2">
|
| 735 |
+
<Brain className="h-4 w-4 text-blue-600" />
|
| 736 |
+
Capabilities
|
| 737 |
+
</div>
|
| 738 |
+
<div className="flex items-center gap-2">
|
| 739 |
+
<span className="text-sm font-normal text-muted-foreground">
|
| 740 |
+
{capabilitiesStatsExpanded ? 'Hide details' : 'Show details'}
|
| 741 |
+
</span>
|
| 742 |
+
{capabilitiesStatsExpanded ? (
|
| 743 |
+
<ChevronUp className="h-4 w-4 text-muted-foreground" />
|
| 744 |
+
) : (
|
| 745 |
+
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
| 746 |
+
)}
|
| 747 |
+
</div>
|
| 748 |
+
</h3>
|
| 749 |
</div>
|
| 750 |
+
|
| 751 |
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
| 752 |
+
<div className="text-center p-4 bg-green-50 dark:bg-green-950 rounded-lg">
|
| 753 |
+
<div className="text-2xl font-bold text-green-700 dark:text-green-300">
|
| 754 |
+
{computedStats.strongCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').length}
|
| 755 |
+
</div>
|
| 756 |
+
<div className="text-sm text-green-600 dark:text-green-400">Strong</div>
|
| 757 |
+
</div>
|
| 758 |
+
<div className="text-center p-4 bg-blue-50 dark:bg-blue-950 rounded-lg">
|
| 759 |
+
<div className="text-2xl font-bold text-blue-700 dark:text-blue-300">
|
| 760 |
+
{computedStats.adequateCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').length}
|
| 761 |
+
</div>
|
| 762 |
+
<div className="text-sm text-blue-600 dark:text-blue-400">Adequate</div>
|
| 763 |
+
</div>
|
| 764 |
+
<div className="text-center p-4 bg-yellow-50 dark:bg-yellow-950 rounded-lg">
|
| 765 |
+
<div className="text-2xl font-bold text-yellow-700 dark:text-yellow-300">
|
| 766 |
+
{computedStats.weakCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').length}
|
| 767 |
+
</div>
|
| 768 |
+
<div className="text-sm text-yellow-600 dark:text-yellow-400">Weak</div>
|
| 769 |
+
</div>
|
| 770 |
+
<div className="text-center p-4 bg-red-50 dark:bg-red-950 rounded-lg">
|
| 771 |
+
<div className="text-2xl font-bold text-red-700 dark:text-red-300">
|
| 772 |
+
{computedStats.insufficientCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').length}
|
| 773 |
+
</div>
|
| 774 |
+
<div className="text-sm text-red-600 dark:text-red-400">Insufficient</div>
|
| 775 |
</div>
|
|
|
|
| 776 |
</div>
|
| 777 |
+
|
| 778 |
+
{capabilitiesStatsExpanded && (
|
| 779 |
+
<div className="mt-6 space-y-6">
|
| 780 |
+
{/* Strong Capabilities */}
|
| 781 |
+
{computedStats.strongCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').length > 0 && (
|
| 782 |
+
<div>
|
| 783 |
+
<h4 className="font-semibold text-green-700 dark:text-green-300 mb-3 flex items-center gap-2">
|
| 784 |
+
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
|
| 785 |
+
Strong ({computedStats.strongCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').length})
|
| 786 |
+
</h4>
|
| 787 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
| 788 |
+
{computedStats.strongCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').map((catId: string) => {
|
| 789 |
+
const category = getCategoryById(catId)
|
| 790 |
+
const IconComponent = getCategoryIcon(catId)
|
| 791 |
+
return (
|
| 792 |
+
<div key={catId} className="flex items-center gap-3 p-2 bg-green-50 dark:bg-green-950/50 rounded">
|
| 793 |
+
<IconComponent className="h-4 w-4 text-green-600 dark:text-green-400" />
|
| 794 |
+
<span className="text-sm">{category?.name || catId}</span>
|
| 795 |
+
</div>
|
| 796 |
+
)
|
| 797 |
+
})}
|
| 798 |
+
</div>
|
| 799 |
+
</div>
|
| 800 |
+
)}
|
| 801 |
+
|
| 802 |
+
{/* Adequate Capabilities */}
|
| 803 |
+
{computedStats.adequateCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').length > 0 && (
|
| 804 |
+
<div>
|
| 805 |
+
<h4 className="font-semibold text-blue-700 dark:text-blue-300 mb-3 flex items-center gap-2">
|
| 806 |
+
<div className="w-3 h-3 bg-blue-500 rounded-full"></div>
|
| 807 |
+
Adequate ({computedStats.adequateCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').length})
|
| 808 |
+
</h4>
|
| 809 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
| 810 |
+
{computedStats.adequateCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').map((catId: string) => {
|
| 811 |
+
const category = getCategoryById(catId)
|
| 812 |
+
const IconComponent = getCategoryIcon(catId)
|
| 813 |
+
return (
|
| 814 |
+
<div key={catId} className="flex items-center gap-3 p-2 bg-blue-50 dark:bg-blue-950/50 rounded">
|
| 815 |
+
<IconComponent className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
| 816 |
+
<span className="text-sm">{category?.name || catId}</span>
|
| 817 |
+
</div>
|
| 818 |
+
)
|
| 819 |
+
})}
|
| 820 |
+
</div>
|
| 821 |
+
</div>
|
| 822 |
+
)}
|
| 823 |
+
|
| 824 |
+
{/* Weak Capabilities */}
|
| 825 |
+
{computedStats.weakCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').length > 0 && (
|
| 826 |
+
<div>
|
| 827 |
+
<h4 className="font-semibold text-yellow-700 dark:text-yellow-300 mb-3 flex items-center gap-2">
|
| 828 |
+
<div className="w-3 h-3 bg-yellow-500 rounded-full"></div>
|
| 829 |
+
Weak ({computedStats.weakCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').length})
|
| 830 |
+
</h4>
|
| 831 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
| 832 |
+
{computedStats.weakCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').map((catId: string) => {
|
| 833 |
+
const category = getCategoryById(catId)
|
| 834 |
+
const IconComponent = getCategoryIcon(catId)
|
| 835 |
+
return (
|
| 836 |
+
<div key={catId} className="flex items-center gap-3 p-2 bg-yellow-50 dark:bg-yellow-950/50 rounded">
|
| 837 |
+
<IconComponent className="h-4 w-4 text-yellow-600 dark:text-yellow-400" />
|
| 838 |
+
<span className="text-sm">{category?.name || catId}</span>
|
| 839 |
+
</div>
|
| 840 |
+
)
|
| 841 |
+
})}
|
| 842 |
+
</div>
|
| 843 |
+
</div>
|
| 844 |
+
)}
|
| 845 |
+
|
| 846 |
+
{/* Insufficient Capabilities */}
|
| 847 |
+
{computedStats.insufficientCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').length > 0 && (
|
| 848 |
+
<div>
|
| 849 |
+
<h4 className="font-semibold text-red-700 dark:text-red-300 mb-3 flex items-center gap-2">
|
| 850 |
+
<div className="w-3 h-3 bg-red-500 rounded-full"></div>
|
| 851 |
+
Insufficient ({computedStats.insufficientCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').length})
|
| 852 |
+
</h4>
|
| 853 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
| 854 |
+
{computedStats.insufficientCategories.filter((cat: string) => getCategoryById(cat)?.type === 'capability').map((catId: string) => {
|
| 855 |
+
const category = getCategoryById(catId)
|
| 856 |
+
const IconComponent = getCategoryIcon(catId)
|
| 857 |
+
return (
|
| 858 |
+
<div key={catId} className="flex items-center gap-3 p-2 bg-red-50 dark:bg-red-950/50 rounded">
|
| 859 |
+
<IconComponent className="h-4 w-4 text-red-600 dark:text-red-400" />
|
| 860 |
+
<span className="text-sm">{category?.name || catId}</span>
|
| 861 |
+
</div>
|
| 862 |
+
)
|
| 863 |
+
})}
|
| 864 |
+
</div>
|
| 865 |
+
</div>
|
| 866 |
+
)}
|
| 867 |
</div>
|
| 868 |
+
)}
|
| 869 |
+
</div>
|
| 870 |
+
|
| 871 |
+
{/* Divider */}
|
| 872 |
+
<div className="border-t border-muted"></div>
|
| 873 |
+
|
| 874 |
+
{/* Risks Section */}
|
| 875 |
+
<div>
|
| 876 |
+
<div
|
| 877 |
+
className="cursor-pointer hover:bg-muted/50 transition-colors p-3 -m-3 rounded-lg mb-4"
|
| 878 |
+
onClick={toggleRisksStats}
|
| 879 |
+
>
|
| 880 |
+
<h3 className="flex items-center justify-between text-lg font-semibold">
|
| 881 |
+
<div className="flex items-center gap-2">
|
| 882 |
+
<ShieldAlert className="h-4 w-4 text-red-600" />
|
| 883 |
+
Risks
|
| 884 |
+
</div>
|
| 885 |
+
<div className="flex items-center gap-2">
|
| 886 |
+
<span className="text-sm font-normal text-muted-foreground">
|
| 887 |
+
{risksStatsExpanded ? 'Hide details' : 'Show details'}
|
| 888 |
+
</span>
|
| 889 |
+
{risksStatsExpanded ? (
|
| 890 |
+
<ChevronUp className="h-4 w-4 text-muted-foreground" />
|
| 891 |
+
) : (
|
| 892 |
+
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
| 893 |
+
)}
|
| 894 |
+
</div>
|
| 895 |
+
</h3>
|
| 896 |
</div>
|
| 897 |
+
|
| 898 |
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
| 899 |
+
<div className="text-center p-4 bg-green-50 dark:bg-green-950 rounded-lg">
|
| 900 |
+
<div className="text-2xl font-bold text-green-700 dark:text-green-300">
|
| 901 |
+
{computedStats.strongCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').length}
|
| 902 |
+
</div>
|
| 903 |
+
<div className="text-sm text-green-600 dark:text-green-400">Strong</div>
|
| 904 |
+
</div>
|
| 905 |
+
<div className="text-center p-4 bg-blue-50 dark:bg-blue-950 rounded-lg">
|
| 906 |
+
<div className="text-2xl font-bold text-blue-700 dark:text-blue-300">
|
| 907 |
+
{computedStats.adequateCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').length}
|
| 908 |
+
</div>
|
| 909 |
+
<div className="text-sm text-blue-600 dark:text-blue-400">Adequate</div>
|
| 910 |
+
</div>
|
| 911 |
+
<div className="text-center p-4 bg-yellow-50 dark:bg-yellow-950 rounded-lg">
|
| 912 |
+
<div className="text-2xl font-bold text-yellow-700 dark:text-yellow-300">
|
| 913 |
+
{computedStats.weakCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').length}
|
| 914 |
+
</div>
|
| 915 |
+
<div className="text-sm text-yellow-600 dark:text-yellow-400">Weak</div>
|
| 916 |
+
</div>
|
| 917 |
+
<div className="text-center p-4 bg-red-50 dark:bg-red-950 rounded-lg">
|
| 918 |
+
<div className="text-2xl font-bold text-red-700 dark:text-red-300">
|
| 919 |
+
{computedStats.insufficientCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').length}
|
| 920 |
+
</div>
|
| 921 |
+
<div className="text-sm text-red-600 dark:text-red-400">Insufficient</div>
|
| 922 |
</div>
|
|
|
|
| 923 |
</div>
|
| 924 |
+
|
| 925 |
+
{risksStatsExpanded && (
|
| 926 |
+
<div className="mt-6 space-y-6">
|
| 927 |
+
{/* Strong Risks */}
|
| 928 |
+
{computedStats.strongCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').length > 0 && (
|
| 929 |
+
<div>
|
| 930 |
+
<h4 className="font-semibold text-green-700 dark:text-green-300 mb-3 flex items-center gap-2">
|
| 931 |
+
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
|
| 932 |
+
Strong ({computedStats.strongCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').length})
|
| 933 |
+
</h4>
|
| 934 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
| 935 |
+
{computedStats.strongCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').map((catId: string) => {
|
| 936 |
+
const category = getCategoryById(catId)
|
| 937 |
+
const IconComponent = getCategoryIcon(catId)
|
| 938 |
+
return (
|
| 939 |
+
<div key={catId} className="flex items-center gap-3 p-2 bg-green-50 dark:bg-green-950/50 rounded">
|
| 940 |
+
<IconComponent className="h-4 w-4 text-green-600 dark:text-green-400" />
|
| 941 |
+
<span className="text-sm">{category?.name || catId}</span>
|
| 942 |
+
</div>
|
| 943 |
+
)
|
| 944 |
+
})}
|
| 945 |
+
</div>
|
| 946 |
+
</div>
|
| 947 |
+
)}
|
| 948 |
+
|
| 949 |
+
{/* Adequate Risks */}
|
| 950 |
+
{computedStats.adequateCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').length > 0 && (
|
| 951 |
+
<div>
|
| 952 |
+
<h4 className="font-semibold text-blue-700 dark:text-blue-300 mb-3 flex items-center gap-2">
|
| 953 |
+
<div className="w-3 h-3 bg-blue-500 rounded-full"></div>
|
| 954 |
+
Adequate ({computedStats.adequateCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').length})
|
| 955 |
+
</h4>
|
| 956 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
| 957 |
+
{computedStats.adequateCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').map((catId: string) => {
|
| 958 |
+
const category = getCategoryById(catId)
|
| 959 |
+
const IconComponent = getCategoryIcon(catId)
|
| 960 |
+
return (
|
| 961 |
+
<div key={catId} className="flex items-center gap-3 p-2 bg-blue-50 dark:bg-blue-950/50 rounded">
|
| 962 |
+
<IconComponent className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
| 963 |
+
<span className="text-sm">{category?.name || catId}</span>
|
| 964 |
+
</div>
|
| 965 |
+
)
|
| 966 |
+
})}
|
| 967 |
+
</div>
|
| 968 |
+
</div>
|
| 969 |
+
)}
|
| 970 |
+
|
| 971 |
+
{/* Weak Risks */}
|
| 972 |
+
{computedStats.weakCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').length > 0 && (
|
| 973 |
+
<div>
|
| 974 |
+
<h4 className="font-semibold text-yellow-700 dark:text-yellow-300 mb-3 flex items-center gap-2">
|
| 975 |
+
<div className="w-3 h-3 bg-yellow-500 rounded-full"></div>
|
| 976 |
+
Weak ({computedStats.weakCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').length})
|
| 977 |
+
</h4>
|
| 978 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
| 979 |
+
{computedStats.weakCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').map((catId: string) => {
|
| 980 |
+
const category = getCategoryById(catId)
|
| 981 |
+
const IconComponent = getCategoryIcon(catId)
|
| 982 |
+
return (
|
| 983 |
+
<div key={catId} className="flex items-center gap-3 p-2 bg-yellow-50 dark:bg-yellow-950/50 rounded">
|
| 984 |
+
<IconComponent className="h-4 w-4 text-yellow-600 dark:text-yellow-400" />
|
| 985 |
+
<span className="text-sm">{category?.name || catId}</span>
|
| 986 |
+
</div>
|
| 987 |
+
)
|
| 988 |
+
})}
|
| 989 |
+
</div>
|
| 990 |
+
</div>
|
| 991 |
+
)}
|
| 992 |
+
|
| 993 |
+
{/* Insufficient Risks */}
|
| 994 |
+
{computedStats.insufficientCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').length > 0 && (
|
| 995 |
+
<div>
|
| 996 |
+
<h4 className="font-semibold text-red-700 dark:text-red-300 mb-3 flex items-center gap-2">
|
| 997 |
+
<div className="w-3 h-3 bg-red-500 rounded-full"></div>
|
| 998 |
+
Insufficient ({computedStats.insufficientCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').length})
|
| 999 |
+
</h4>
|
| 1000 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
| 1001 |
+
{computedStats.insufficientCategories.filter((cat: string) => getCategoryById(cat)?.type === 'risk').map((catId: string) => {
|
| 1002 |
+
const category = getCategoryById(catId)
|
| 1003 |
+
const IconComponent = getCategoryIcon(catId)
|
| 1004 |
+
return (
|
| 1005 |
+
<div key={catId} className="flex items-center gap-3 p-2 bg-red-50 dark:bg-red-950/50 rounded">
|
| 1006 |
+
<IconComponent className="h-4 w-4 text-red-600 dark:text-red-400" />
|
| 1007 |
+
<span className="text-sm">{category?.name || catId}</span>
|
| 1008 |
+
</div>
|
| 1009 |
+
)
|
| 1010 |
+
})}
|
| 1011 |
+
</div>
|
| 1012 |
+
</div>
|
| 1013 |
+
)}
|
| 1014 |
+
</div>
|
| 1015 |
+
)}
|
| 1016 |
</div>
|
| 1017 |
</CardContent>
|
| 1018 |
</Card>
|
| 1019 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1020 |
{/* Evaluation Details */}
|
| 1021 |
{evaluation.categoryEvaluations &&
|
| 1022 |
Object.entries(evaluation.categoryEvaluations)
|
app/page.tsx
CHANGED
|
@@ -380,7 +380,7 @@ export default function HomePage() {
|
|
| 380 |
loadData()
|
| 381 |
}, [])
|
| 382 |
|
| 383 |
-
const [sortBy, setSortBy] = useState<"date-newest" | "date-oldest">("date-newest")
|
| 384 |
const [filterByProvider, setFilterByProvider] = useState<string>("all")
|
| 385 |
const [filterByModality, setFilterByModality] = useState<string>("all")
|
| 386 |
|
|
@@ -390,12 +390,21 @@ export default function HomePage() {
|
|
| 390 |
}, [evaluationsData])
|
| 391 |
|
| 392 |
const uniqueModalities = useMemo(() => {
|
| 393 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
evaluationsData.forEach((item) => {
|
| 395 |
-
item.inputModalities.forEach((mod) =>
|
| 396 |
-
item.outputModalities.forEach((mod) =>
|
| 397 |
})
|
| 398 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
}, [evaluationsData])
|
| 400 |
|
| 401 |
const filteredAndSortedEvaluations = useMemo(() => {
|
|
@@ -413,13 +422,38 @@ export default function HomePage() {
|
|
| 413 |
}
|
| 414 |
|
| 415 |
filtered = [...filtered].sort((a, b) => {
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
} else {
|
| 422 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
}
|
| 424 |
})
|
| 425 |
|
|
@@ -497,13 +531,15 @@ export default function HomePage() {
|
|
| 497 |
<div className="flex items-center gap-2">
|
| 498 |
<ArrowUpDown className="h-4 w-4 text-muted-foreground" />
|
| 499 |
<span className="text-sm font-medium">Sort by:</span>
|
| 500 |
-
<Select value={sortBy} onValueChange={(value: "date-newest" | "date-oldest") => setSortBy(value)}>
|
| 501 |
-
<SelectTrigger className="w-
|
| 502 |
<SelectValue />
|
| 503 |
</SelectTrigger>
|
| 504 |
<SelectContent>
|
| 505 |
<SelectItem value="date-newest">Date (Newest)</SelectItem>
|
| 506 |
<SelectItem value="date-oldest">Date (Oldest)</SelectItem>
|
|
|
|
|
|
|
| 507 |
</SelectContent>
|
| 508 |
</Select>
|
| 509 |
</div>
|
|
|
|
| 380 |
loadData()
|
| 381 |
}, [])
|
| 382 |
|
| 383 |
+
const [sortBy, setSortBy] = useState<"date-newest" | "date-oldest" | "completeness-highest" | "completeness-lowest">("date-newest")
|
| 384 |
const [filterByProvider, setFilterByProvider] = useState<string>("all")
|
| 385 |
const [filterByModality, setFilterByModality] = useState<string>("all")
|
| 386 |
|
|
|
|
| 390 |
}, [evaluationsData])
|
| 391 |
|
| 392 |
const uniqueModalities = useMemo(() => {
|
| 393 |
+
// Define all possible modalities to ensure complete filter options
|
| 394 |
+
const allModalities = ["Text", "Image", "Audio", "Video", "Tabular", "Robotics/Action", "Other"]
|
| 395 |
+
|
| 396 |
+
// Get modalities that actually exist in the data
|
| 397 |
+
const existingModalities = new Set<string>()
|
| 398 |
evaluationsData.forEach((item) => {
|
| 399 |
+
item.inputModalities.forEach((mod) => existingModalities.add(mod))
|
| 400 |
+
item.outputModalities.forEach((mod) => existingModalities.add(mod))
|
| 401 |
})
|
| 402 |
+
|
| 403 |
+
// Return all predefined modalities, with existing ones first, then missing ones
|
| 404 |
+
const existing = allModalities.filter(mod => existingModalities.has(mod)).sort()
|
| 405 |
+
const missing = allModalities.filter(mod => !existingModalities.has(mod)).sort()
|
| 406 |
+
|
| 407 |
+
return [...existing, ...missing]
|
| 408 |
}, [evaluationsData])
|
| 409 |
|
| 410 |
const filteredAndSortedEvaluations = useMemo(() => {
|
|
|
|
| 422 |
}
|
| 423 |
|
| 424 |
filtered = [...filtered].sort((a, b) => {
|
| 425 |
+
if (sortBy === "completeness-highest" || sortBy === "completeness-lowest") {
|
| 426 |
+
// Calculate completeness scores for both items
|
| 427 |
+
const calculateCompletenessScore = (evaluation: EvaluationCardData) => {
|
| 428 |
+
const capTotal = evaluation.capabilityEval.strong + evaluation.capabilityEval.adequate + evaluation.capabilityEval.weak + evaluation.capabilityEval.insufficient
|
| 429 |
+
const riskTotal = evaluation.riskEval.strong + evaluation.riskEval.adequate + evaluation.riskEval.weak + evaluation.riskEval.insufficient
|
| 430 |
+
const totalItems = capTotal + riskTotal
|
| 431 |
+
|
| 432 |
+
if (totalItems === 0) return 0
|
| 433 |
+
|
| 434 |
+
const capScore = evaluation.capabilityEval.strong * 4 + evaluation.capabilityEval.adequate * 3 + evaluation.capabilityEval.weak * 2 + evaluation.capabilityEval.insufficient * 1
|
| 435 |
+
const riskScore = evaluation.riskEval.strong * 4 + evaluation.riskEval.adequate * 3 + evaluation.riskEval.weak * 2 + evaluation.riskEval.insufficient * 1
|
| 436 |
+
|
| 437 |
+
return Math.round(((capScore + riskScore) / (totalItems * 4)) * 100)
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
const scoreA = calculateCompletenessScore(a)
|
| 441 |
+
const scoreB = calculateCompletenessScore(b)
|
| 442 |
+
|
| 443 |
+
if (sortBy === "completeness-highest") {
|
| 444 |
+
return scoreB - scoreA
|
| 445 |
+
} else {
|
| 446 |
+
return scoreA - scoreB
|
| 447 |
+
}
|
| 448 |
} else {
|
| 449 |
+
const dateA = new Date(a.completedDate)
|
| 450 |
+
const dateB = new Date(b.completedDate)
|
| 451 |
+
|
| 452 |
+
if (sortBy === "date-newest") {
|
| 453 |
+
return dateB.getTime() - dateA.getTime()
|
| 454 |
+
} else {
|
| 455 |
+
return dateA.getTime() - dateB.getTime()
|
| 456 |
+
}
|
| 457 |
}
|
| 458 |
})
|
| 459 |
|
|
|
|
| 531 |
<div className="flex items-center gap-2">
|
| 532 |
<ArrowUpDown className="h-4 w-4 text-muted-foreground" />
|
| 533 |
<span className="text-sm font-medium">Sort by:</span>
|
| 534 |
+
<Select value={sortBy} onValueChange={(value: "date-newest" | "date-oldest" | "completeness-highest" | "completeness-lowest") => setSortBy(value)}>
|
| 535 |
+
<SelectTrigger className="w-48">
|
| 536 |
<SelectValue />
|
| 537 |
</SelectTrigger>
|
| 538 |
<SelectContent>
|
| 539 |
<SelectItem value="date-newest">Date (Newest)</SelectItem>
|
| 540 |
<SelectItem value="date-oldest">Date (Oldest)</SelectItem>
|
| 541 |
+
<SelectItem value="completeness-highest">Completeness (Highest)</SelectItem>
|
| 542 |
+
<SelectItem value="completeness-lowest">Completeness (Lowest)</SelectItem>
|
| 543 |
</SelectContent>
|
| 544 |
</Select>
|
| 545 |
</div>
|
components/evaluation-card.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { MoreHorizontal, Eye, Download, Trash2 } from "lucide-react"
|
|
| 8 |
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
| 9 |
import { useRouter } from "next/navigation"
|
| 10 |
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
|
|
|
| 11 |
|
| 12 |
export type EvaluationCardData = {
|
| 13 |
id: string
|
|
@@ -68,6 +69,43 @@ export function EvaluationCard({ evaluation, onView, onDelete }: EvaluationCardP
|
|
| 68 |
const [expandedAreas, setExpandedAreas] = useState<Record<string, boolean>>({})
|
| 69 |
const toggleArea = (area: string) => setExpandedAreas((p) => ({ ...p, [area]: !p[area] }))
|
| 70 |
const router = useRouter()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
const modalityMap: Record<string, { label: string; emoji?: string; variant?: string }> = {
|
| 72 |
"Text": { label: "Text", emoji: "📝" },
|
| 73 |
"Image": { label: "Image", emoji: "🖼️" },
|
|
@@ -171,12 +209,15 @@ export function EvaluationCard({ evaluation, onView, onDelete }: EvaluationCardP
|
|
| 171 |
|
| 172 |
return (
|
| 173 |
<TooltipProvider>
|
| 174 |
-
<Card
|
|
|
|
|
|
|
|
|
|
| 175 |
<CardHeader className="pb-3">
|
| 176 |
<div className="flex items-start justify-between">
|
| 177 |
<div className="space-y-1 flex-1 min-w-0">
|
| 178 |
-
<CardTitle className="text-
|
| 179 |
-
<p className="text-sm text-muted-foreground truncate">{evaluation.provider}</p>
|
| 180 |
{/* Enhanced modality badge with emoji and hover detail */}
|
| 181 |
{(() => {
|
| 182 |
const info = getModalityDisplay(evaluation.inputModalities, evaluation.outputModalities)
|
|
@@ -199,18 +240,19 @@ export function EvaluationCard({ evaluation, onView, onDelete }: EvaluationCardP
|
|
| 199 |
})()}
|
| 200 |
</div>
|
| 201 |
<div className="flex items-center gap-2 flex-shrink-0">
|
| 202 |
-
<
|
| 203 |
-
<
|
| 204 |
-
<
|
| 205 |
-
<
|
| 206 |
-
|
| 207 |
-
|
|
|
|
| 208 |
<DropdownMenuContent align="end">
|
| 209 |
<DropdownMenuItem onClick={handleViewDetails}>
|
| 210 |
<Eye className="h-4 w-4 mr-2" />
|
| 211 |
View Details
|
| 212 |
</DropdownMenuItem>
|
| 213 |
-
<DropdownMenuItem>
|
| 214 |
<Download className="h-4 w-4 mr-2" />
|
| 215 |
Export Report
|
| 216 |
</DropdownMenuItem>
|
|
@@ -220,265 +262,165 @@ export function EvaluationCard({ evaluation, onView, onDelete }: EvaluationCardP
|
|
| 220 |
</DropdownMenuItem>
|
| 221 |
</DropdownMenuContent>
|
| 222 |
</DropdownMenu>
|
|
|
|
| 223 |
</div>
|
| 224 |
</div>
|
| 225 |
</CardHeader>
|
| 226 |
<CardContent className="space-y-4">
|
|
|
|
| 227 |
<div className="grid grid-cols-2 gap-4">
|
| 228 |
<div className="space-y-1">
|
| 229 |
<span className="text-sm text-muted-foreground font-medium">Completeness</span>
|
| 230 |
-
<
|
| 231 |
-
<
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
<TooltipContent className="max-w-xs">
|
| 239 |
-
<p className="text-xs">
|
| 240 |
-
Weighted average: (Strong×4 + Adequate×3 + Weak×2 + Insufficient×1) ÷ (Total×4) × 100
|
| 241 |
-
<br />
|
| 242 |
-
Capability: {evaluation.capabilityEval.totalApplicable} categories
|
| 243 |
-
<br />
|
| 244 |
-
Risk: {evaluation.riskEval.totalApplicable} categories
|
| 245 |
-
</p>
|
| 246 |
-
</TooltipContent>
|
| 247 |
-
</Tooltip>
|
| 248 |
</div>
|
| 249 |
<div className="space-y-1">
|
| 250 |
-
<span className="text-sm text-muted-foreground font-medium">
|
| 251 |
<p className="text-sm font-semibold">{evaluation.completedDate}</p>
|
| 252 |
</div>
|
| 253 |
</div>
|
| 254 |
|
|
|
|
| 255 |
<div className="space-y-3">
|
| 256 |
-
<div className="
|
| 257 |
-
<
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
<
|
| 263 |
-
<
|
| 264 |
-
<
|
| 265 |
-
<div className="
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
</div>
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
<Tooltip>
|
| 289 |
-
<TooltipTrigger asChild>
|
| 290 |
-
<div className="text-center cursor-help">
|
| 291 |
-
<div className="w-full h-20 flex items-end justify-center mb-2">
|
| 292 |
-
<div
|
| 293 |
-
className="w-full rounded-t-md bg-blue-500"
|
| 294 |
-
style={{
|
| 295 |
-
height: `${Math.round(
|
| 296 |
-
(evaluation.capabilityEval.adequate / Math.max(1, capTotalComputed)) * 100
|
| 297 |
-
)}%`,
|
| 298 |
-
}}
|
| 299 |
-
/>
|
| 300 |
</div>
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
<Tooltip>
|
| 315 |
-
<TooltipTrigger asChild>
|
| 316 |
-
<div className="text-center cursor-help">
|
| 317 |
-
<div className="w-full h-20 flex items-end justify-center mb-2">
|
| 318 |
-
<div
|
| 319 |
-
className="w-full rounded-t-md bg-amber-500"
|
| 320 |
-
style={{
|
| 321 |
-
height: `${Math.round(
|
| 322 |
-
(evaluation.capabilityEval.weak / Math.max(1, capTotalComputed)) * 100
|
| 323 |
-
)}%`,
|
| 324 |
-
}}
|
| 325 |
-
/>
|
| 326 |
</div>
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
<Tooltip>
|
| 341 |
-
<TooltipTrigger asChild>
|
| 342 |
-
<div className="text-center cursor-help">
|
| 343 |
-
<div className="w-full h-20 flex items-end justify-center mb-2">
|
| 344 |
-
<div
|
| 345 |
-
className="w-full rounded-t-md bg-red-500"
|
| 346 |
-
style={{
|
| 347 |
-
height: `${Math.round(
|
| 348 |
-
(evaluation.capabilityEval.insufficient / Math.max(1, capTotalComputed)) * 100
|
| 349 |
-
)}%`,
|
| 350 |
-
}}
|
| 351 |
-
/>
|
| 352 |
</div>
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
</TooltipTrigger>
|
| 357 |
-
<TooltipContent>
|
| 358 |
-
<p className="text-xs">
|
| 359 |
-
{evaluation.capabilityEval.insufficientCategories.length > 0
|
| 360 |
-
? evaluation.capabilityEval.insufficientCategories.join(", ")
|
| 361 |
-
: "No categories"}
|
| 362 |
-
</p>
|
| 363 |
-
</TooltipContent>
|
| 364 |
-
</Tooltip>
|
| 365 |
</div>
|
| 366 |
-
</div>
|
| 367 |
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
/>
|
| 386 |
</div>
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
<Tooltip>
|
| 401 |
-
<TooltipTrigger asChild>
|
| 402 |
-
<div className="text-center cursor-help">
|
| 403 |
-
<div className="w-full h-20 flex items-end justify-center mb-2">
|
| 404 |
-
<div
|
| 405 |
-
className="w-full rounded-t-md bg-blue-500"
|
| 406 |
-
style={{
|
| 407 |
-
height: `${Math.round(
|
| 408 |
-
(evaluation.riskEval.adequate / Math.max(1, riskTotalComputed)) * 100
|
| 409 |
-
)}%`,
|
| 410 |
-
}}
|
| 411 |
-
/>
|
| 412 |
</div>
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
<Tooltip>
|
| 427 |
-
<TooltipTrigger asChild>
|
| 428 |
-
<div className="text-center cursor-help">
|
| 429 |
-
<div className="w-full h-20 flex items-end justify-center mb-2">
|
| 430 |
-
<div
|
| 431 |
-
className="w-full rounded-t-md bg-amber-500"
|
| 432 |
-
style={{
|
| 433 |
-
height: `${Math.round(
|
| 434 |
-
(evaluation.riskEval.weak / Math.max(1, riskTotalComputed)) * 100
|
| 435 |
-
)}%`,
|
| 436 |
-
}}
|
| 437 |
-
/>
|
| 438 |
</div>
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
<Tooltip>
|
| 453 |
-
<TooltipTrigger asChild>
|
| 454 |
-
<div className="text-center cursor-help">
|
| 455 |
-
<div className="w-full h-20 flex items-end justify-center mb-2">
|
| 456 |
-
<div
|
| 457 |
-
className="w-full rounded-t-md bg-red-500"
|
| 458 |
-
style={{
|
| 459 |
-
height: `${Math.round(
|
| 460 |
-
(evaluation.riskEval.insufficient / Math.max(1, riskTotalComputed)) * 100
|
| 461 |
-
)}%`,
|
| 462 |
-
}}
|
| 463 |
-
/>
|
| 464 |
</div>
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
</TooltipTrigger>
|
| 469 |
-
<TooltipContent>
|
| 470 |
-
<p className="text-xs">
|
| 471 |
-
{evaluation.riskEval.insufficientCategories.length > 0
|
| 472 |
-
? evaluation.riskEval.insufficientCategories.join(", ")
|
| 473 |
-
: "No categories"}
|
| 474 |
-
</p>
|
| 475 |
-
</TooltipContent>
|
| 476 |
-
</Tooltip>
|
| 477 |
</div>
|
| 478 |
</div>
|
| 479 |
</div>
|
| 480 |
</CardContent>
|
| 481 |
-
{/* priorityAreas/details intentionally removed from summary card — shown on details page */}
|
| 482 |
</Card>
|
| 483 |
</TooltipProvider>
|
| 484 |
)
|
|
|
|
| 8 |
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
| 9 |
import { useRouter } from "next/navigation"
|
| 10 |
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
| 11 |
+
import { getAllCategories } from "@/lib/schema"
|
| 12 |
|
| 13 |
export type EvaluationCardData = {
|
| 14 |
id: string
|
|
|
|
| 69 |
const [expandedAreas, setExpandedAreas] = useState<Record<string, boolean>>({})
|
| 70 |
const toggleArea = (area: string) => setExpandedAreas((p) => ({ ...p, [area]: !p[area] }))
|
| 71 |
const router = useRouter()
|
| 72 |
+
|
| 73 |
+
const handleCardClick = () => {
|
| 74 |
+
router.push(`/evaluation/${evaluation.id}`)
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
const handleMenuClick = (e: React.MouseEvent) => {
|
| 78 |
+
e.stopPropagation() // Prevent card click when clicking menu
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
const handleExport = (e: React.MouseEvent) => {
|
| 82 |
+
e.stopPropagation() // Prevent card click when clicking export
|
| 83 |
+
|
| 84 |
+
const reportData = {
|
| 85 |
+
id: evaluation.id,
|
| 86 |
+
systemName: evaluation.systemName,
|
| 87 |
+
provider: evaluation.provider,
|
| 88 |
+
completedDate: evaluation.completedDate,
|
| 89 |
+
exportDate: new Date().toISOString(),
|
| 90 |
+
inputModalities: evaluation.inputModalities,
|
| 91 |
+
outputModalities: evaluation.outputModalities,
|
| 92 |
+
completenessScore: Math.round((evaluation.completedCategories / evaluation.applicableCategories) * 100),
|
| 93 |
+
status: evaluation.status,
|
| 94 |
+
capabilityEvaluation: evaluation.capabilityEval,
|
| 95 |
+
riskEvaluation: evaluation.riskEval,
|
| 96 |
+
priorityAreas: evaluation.priorityAreas,
|
| 97 |
+
priorityDetails: evaluation.priorityDetails
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
const blob = new Blob([JSON.stringify(reportData, null, 2)], { type: "application/json" })
|
| 101 |
+
const url = URL.createObjectURL(blob)
|
| 102 |
+
const a = document.createElement("a")
|
| 103 |
+
a.href = url
|
| 104 |
+
a.download = `evaluation-summary-${evaluation.systemName.replace(/[^a-zA-Z0-9]/g, '-')}-${new Date().toISOString().split("T")[0]}.json`
|
| 105 |
+
a.click()
|
| 106 |
+
URL.revokeObjectURL(url)
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
const modalityMap: Record<string, { label: string; emoji?: string; variant?: string }> = {
|
| 110 |
"Text": { label: "Text", emoji: "📝" },
|
| 111 |
"Image": { label: "Image", emoji: "🖼️" },
|
|
|
|
| 209 |
|
| 210 |
return (
|
| 211 |
<TooltipProvider>
|
| 212 |
+
<Card
|
| 213 |
+
className="hover:shadow-lg transition-all duration-200 cursor-pointer hover:border-primary/20 hover:shadow-primary/5"
|
| 214 |
+
onClick={handleCardClick}
|
| 215 |
+
>
|
| 216 |
<CardHeader className="pb-3">
|
| 217 |
<div className="flex items-start justify-between">
|
| 218 |
<div className="space-y-1 flex-1 min-w-0">
|
| 219 |
+
<CardTitle className="text-xl font-bold truncate">{evaluation.systemName}</CardTitle>
|
| 220 |
+
<p className="text-sm text-muted-foreground truncate font-medium">{evaluation.provider}</p>
|
| 221 |
{/* Enhanced modality badge with emoji and hover detail */}
|
| 222 |
{(() => {
|
| 223 |
const info = getModalityDisplay(evaluation.inputModalities, evaluation.outputModalities)
|
|
|
|
| 240 |
})()}
|
| 241 |
</div>
|
| 242 |
<div className="flex items-center gap-2 flex-shrink-0">
|
| 243 |
+
<div onClick={handleMenuClick}>
|
| 244 |
+
<DropdownMenu>
|
| 245 |
+
<DropdownMenuTrigger asChild>
|
| 246 |
+
<Button variant="ghost" size="sm" className="h-8 w-8 p-0 hover:bg-muted">
|
| 247 |
+
<MoreHorizontal className="h-4 w-4" />
|
| 248 |
+
</Button>
|
| 249 |
+
</DropdownMenuTrigger>
|
| 250 |
<DropdownMenuContent align="end">
|
| 251 |
<DropdownMenuItem onClick={handleViewDetails}>
|
| 252 |
<Eye className="h-4 w-4 mr-2" />
|
| 253 |
View Details
|
| 254 |
</DropdownMenuItem>
|
| 255 |
+
<DropdownMenuItem onClick={handleExport}>
|
| 256 |
<Download className="h-4 w-4 mr-2" />
|
| 257 |
Export Report
|
| 258 |
</DropdownMenuItem>
|
|
|
|
| 262 |
</DropdownMenuItem>
|
| 263 |
</DropdownMenuContent>
|
| 264 |
</DropdownMenu>
|
| 265 |
+
</div>
|
| 266 |
</div>
|
| 267 |
</div>
|
| 268 |
</CardHeader>
|
| 269 |
<CardContent className="space-y-4">
|
| 270 |
+
{/* Top row: Key metrics */}
|
| 271 |
<div className="grid grid-cols-2 gap-4">
|
| 272 |
<div className="space-y-1">
|
| 273 |
<span className="text-sm text-muted-foreground font-medium">Completeness</span>
|
| 274 |
+
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-6 overflow-hidden">
|
| 275 |
+
<div
|
| 276 |
+
className={`h-full rounded-full transition-all duration-300 flex items-center justify-center text-xs font-bold text-white ${getCompletenessColor(completenessScore)}`}
|
| 277 |
+
style={{ width: `${Math.max(completenessScore, 8)}%` }}
|
| 278 |
+
>
|
| 279 |
+
{completenessScore}%
|
| 280 |
+
</div>
|
| 281 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
</div>
|
| 283 |
<div className="space-y-1">
|
| 284 |
+
<span className="text-sm text-muted-foreground font-medium">Submitted</span>
|
| 285 |
<p className="text-sm font-semibold">{evaluation.completedDate}</p>
|
| 286 |
</div>
|
| 287 |
</div>
|
| 288 |
|
| 289 |
+
{/* Quick summary stats */}
|
| 290 |
<div className="space-y-3">
|
| 291 |
+
<div className="grid grid-cols-2 gap-3">
|
| 292 |
+
<div className="space-y-2">
|
| 293 |
+
<div className="flex items-center justify-between">
|
| 294 |
+
<span className="text-sm text-muted-foreground font-medium">Capability Eval</span>
|
| 295 |
+
<span className="text-xs text-muted-foreground">({evaluation.capabilityEval.totalApplicable} applicable)</span>
|
| 296 |
+
</div>
|
| 297 |
+
<div className="flex gap-1">
|
| 298 |
+
<Tooltip>
|
| 299 |
+
<TooltipTrigger asChild>
|
| 300 |
+
<div className="flex-1 h-2 bg-emerald-500 rounded-full cursor-help" style={{
|
| 301 |
+
opacity: evaluation.capabilityEval.strong > 0 ? 1 : 0.2,
|
| 302 |
+
flexGrow: evaluation.capabilityEval.strong
|
| 303 |
+
}} />
|
| 304 |
+
</TooltipTrigger>
|
| 305 |
+
<TooltipContent>
|
| 306 |
+
<div className="flex items-center gap-2">
|
| 307 |
+
<div className="w-3 h-3 bg-emerald-500 rounded-full"></div>
|
| 308 |
+
<span className="text-xs"><strong>Strong:</strong> {evaluation.capabilityEval.strong} categories - Most evals reported</span>
|
| 309 |
</div>
|
| 310 |
+
</TooltipContent>
|
| 311 |
+
</Tooltip>
|
| 312 |
+
<Tooltip>
|
| 313 |
+
<TooltipTrigger asChild>
|
| 314 |
+
<div className="flex-1 h-2 bg-blue-500 rounded-full cursor-help" style={{
|
| 315 |
+
opacity: evaluation.capabilityEval.adequate > 0 ? 1 : 0.2,
|
| 316 |
+
flexGrow: evaluation.capabilityEval.adequate
|
| 317 |
+
}} />
|
| 318 |
+
</TooltipTrigger>
|
| 319 |
+
<TooltipContent>
|
| 320 |
+
<div className="flex items-center gap-2">
|
| 321 |
+
<div className="w-3 h-3 bg-blue-500 rounded-full"></div>
|
| 322 |
+
<span className="text-xs"><strong>Adequate:</strong> {evaluation.capabilityEval.adequate} categories - Many evals reported</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
</div>
|
| 324 |
+
</TooltipContent>
|
| 325 |
+
</Tooltip>
|
| 326 |
+
<Tooltip>
|
| 327 |
+
<TooltipTrigger asChild>
|
| 328 |
+
<div className="flex-1 h-2 bg-yellow-500 rounded-full cursor-help" style={{
|
| 329 |
+
opacity: evaluation.capabilityEval.weak > 0 ? 1 : 0.2,
|
| 330 |
+
flexGrow: evaluation.capabilityEval.weak
|
| 331 |
+
}} />
|
| 332 |
+
</TooltipTrigger>
|
| 333 |
+
<TooltipContent>
|
| 334 |
+
<div className="flex items-center gap-2">
|
| 335 |
+
<div className="w-3 h-3 bg-yellow-500 rounded-full"></div>
|
| 336 |
+
<span className="text-xs"><strong>Weak:</strong> {evaluation.capabilityEval.weak} categories - Some evals reported</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
</div>
|
| 338 |
+
</TooltipContent>
|
| 339 |
+
</Tooltip>
|
| 340 |
+
<Tooltip>
|
| 341 |
+
<TooltipTrigger asChild>
|
| 342 |
+
<div className="flex-1 h-2 bg-red-500 rounded-full cursor-help" style={{
|
| 343 |
+
opacity: evaluation.capabilityEval.insufficient > 0 ? 1 : 0.2,
|
| 344 |
+
flexGrow: evaluation.capabilityEval.insufficient
|
| 345 |
+
}} />
|
| 346 |
+
</TooltipTrigger>
|
| 347 |
+
<TooltipContent>
|
| 348 |
+
<div className="flex items-center gap-2">
|
| 349 |
+
<div className="w-3 h-3 bg-red-500 rounded-full"></div>
|
| 350 |
+
<span className="text-xs"><strong>Insufficient:</strong> {evaluation.capabilityEval.insufficient} categories - Few evals reported</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
</div>
|
| 352 |
+
</TooltipContent>
|
| 353 |
+
</Tooltip>
|
| 354 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
</div>
|
|
|
|
| 356 |
|
| 357 |
+
<div className="space-y-2">
|
| 358 |
+
<div className="flex items-center justify-between">
|
| 359 |
+
<span className="text-sm text-muted-foreground font-medium">Risk Eval</span>
|
| 360 |
+
<span className="text-xs text-muted-foreground">({evaluation.riskEval.totalApplicable} applicable)</span>
|
| 361 |
+
</div>
|
| 362 |
+
<div className="flex gap-1">
|
| 363 |
+
<Tooltip>
|
| 364 |
+
<TooltipTrigger asChild>
|
| 365 |
+
<div className="flex-1 h-2 bg-emerald-500 rounded-full cursor-help" style={{
|
| 366 |
+
opacity: evaluation.riskEval.strong > 0 ? 1 : 0.2,
|
| 367 |
+
flexGrow: evaluation.riskEval.strong
|
| 368 |
+
}} />
|
| 369 |
+
</TooltipTrigger>
|
| 370 |
+
<TooltipContent>
|
| 371 |
+
<div className="flex items-center gap-2">
|
| 372 |
+
<div className="w-3 h-3 bg-emerald-500 rounded-full"></div>
|
| 373 |
+
<span className="text-xs"><strong>Strong:</strong> {evaluation.riskEval.strong} categories - Most evals reported</span>
|
|
|
|
| 374 |
</div>
|
| 375 |
+
</TooltipContent>
|
| 376 |
+
</Tooltip>
|
| 377 |
+
<Tooltip>
|
| 378 |
+
<TooltipTrigger asChild>
|
| 379 |
+
<div className="flex-1 h-2 bg-blue-500 rounded-full cursor-help" style={{
|
| 380 |
+
opacity: evaluation.riskEval.adequate > 0 ? 1 : 0.2,
|
| 381 |
+
flexGrow: evaluation.riskEval.adequate
|
| 382 |
+
}} />
|
| 383 |
+
</TooltipTrigger>
|
| 384 |
+
<TooltipContent>
|
| 385 |
+
<div className="flex items-center gap-2">
|
| 386 |
+
<div className="w-3 h-3 bg-blue-500 rounded-full"></div>
|
| 387 |
+
<span className="text-xs"><strong>Adequate:</strong> {evaluation.riskEval.adequate} categories - Many evals reported</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
</div>
|
| 389 |
+
</TooltipContent>
|
| 390 |
+
</Tooltip>
|
| 391 |
+
<Tooltip>
|
| 392 |
+
<TooltipTrigger asChild>
|
| 393 |
+
<div className="flex-1 h-2 bg-yellow-500 rounded-full cursor-help" style={{
|
| 394 |
+
opacity: evaluation.riskEval.weak > 0 ? 1 : 0.2,
|
| 395 |
+
flexGrow: evaluation.riskEval.weak
|
| 396 |
+
}} />
|
| 397 |
+
</TooltipTrigger>
|
| 398 |
+
<TooltipContent>
|
| 399 |
+
<div className="flex items-center gap-2">
|
| 400 |
+
<div className="w-3 h-3 bg-yellow-500 rounded-full"></div>
|
| 401 |
+
<span className="text-xs"><strong>Weak:</strong> {evaluation.riskEval.weak} categories - Some evals reported</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
</div>
|
| 403 |
+
</TooltipContent>
|
| 404 |
+
</Tooltip>
|
| 405 |
+
<Tooltip>
|
| 406 |
+
<TooltipTrigger asChild>
|
| 407 |
+
<div className="flex-1 h-2 bg-red-500 rounded-full cursor-help" style={{
|
| 408 |
+
opacity: evaluation.riskEval.insufficient > 0 ? 1 : 0.2,
|
| 409 |
+
flexGrow: evaluation.riskEval.insufficient
|
| 410 |
+
}} />
|
| 411 |
+
</TooltipTrigger>
|
| 412 |
+
<TooltipContent>
|
| 413 |
+
<div className="flex items-center gap-2">
|
| 414 |
+
<div className="w-3 h-3 bg-red-500 rounded-full"></div>
|
| 415 |
+
<span className="text-xs"><strong>Insufficient:</strong> {evaluation.riskEval.insufficient} categories - Few evals reported</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 416 |
</div>
|
| 417 |
+
</TooltipContent>
|
| 418 |
+
</Tooltip>
|
| 419 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
</div>
|
| 421 |
</div>
|
| 422 |
</div>
|
| 423 |
</CardContent>
|
|
|
|
| 424 |
</Card>
|
| 425 |
</TooltipProvider>
|
| 426 |
)
|