Avijit Ghosh commited on
Commit
94700ed
·
1 Parent(s): 56d40a2

fixed some bugs

Browse files
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
- <p className="text-lg font-semibold text-foreground mt-1">{evaluation.overallStats?.completenessScore ? `${evaluation.overallStats.completenessScore}%` : "N/A"}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
  </div>
477
  </div>
478
  </div>
@@ -658,74 +723,300 @@ export default function EvaluationDetailsPage() {
658
  Overall Statistics
659
  </CardTitle>
660
  </CardHeader>
661
- <CardContent>
662
- <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
663
- <div className="text-center p-4 bg-green-50 dark:bg-green-950 rounded-lg">
664
- <div className="text-2xl font-bold text-green-700 dark:text-green-300">
665
- {computedStats.strongCategories.length}
666
- </div>
667
- <div className="text-sm text-green-600 dark:text-green-400">Strong</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
668
  </div>
669
- <div className="text-center p-4 bg-blue-50 dark:bg-blue-950 rounded-lg">
670
- <div className="text-2xl font-bold text-blue-700 dark:text-blue-300">
671
- {computedStats.adequateCategories.length}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
672
  </div>
673
- <div className="text-sm text-blue-600 dark:text-blue-400">Adequate</div>
674
  </div>
675
- <div className="text-center p-4 bg-yellow-50 dark:bg-yellow-950 rounded-lg">
676
- <div className="text-2xl font-bold text-yellow-700 dark:text-yellow-300">
677
- {computedStats.weakCategories.length}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
  </div>
679
- <div className="text-sm text-yellow-600 dark:text-yellow-400">Weak</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
680
  </div>
681
- <div className="text-center p-4 bg-red-50 dark:bg-red-950 rounded-lg">
682
- <div className="text-2xl font-bold text-red-700 dark:text-red-300">
683
- {computedStats.insufficientCategories.length}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- const modalities = new Set<string>()
 
 
 
 
394
  evaluationsData.forEach((item) => {
395
- item.inputModalities.forEach((mod) => modalities.add(mod))
396
- item.outputModalities.forEach((mod) => modalities.add(mod))
397
  })
398
- return [...modalities].sort()
 
 
 
 
 
399
  }, [evaluationsData])
400
 
401
  const filteredAndSortedEvaluations = useMemo(() => {
@@ -413,13 +422,38 @@ export default function HomePage() {
413
  }
414
 
415
  filtered = [...filtered].sort((a, b) => {
416
- const dateA = new Date(a.completedDate)
417
- const dateB = new Date(b.completedDate)
418
-
419
- if (sortBy === "date-newest") {
420
- return dateB.getTime() - dateA.getTime()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  } else {
422
- return dateA.getTime() - dateB.getTime()
 
 
 
 
 
 
 
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-40">
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 className="hover:shadow-lg transition-shadow duration-200">
 
 
 
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-lg font-heading truncate">{evaluation.systemName}</CardTitle>
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
- <DropdownMenu>
203
- <DropdownMenuTrigger asChild>
204
- <Button variant="ghost" size="sm" className="h-8 w-8 p-0">
205
- <MoreHorizontal className="h-4 w-4" />
206
- </Button>
207
- </DropdownMenuTrigger>
 
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
- <Tooltip>
231
- <TooltipTrigger asChild>
232
- <div
233
- className={`px-2 py-1 rounded text-sm font-semibold cursor-help ${getCompletenessColor(completenessScore)}`}
234
- >
235
- {completenessScore}%
236
- </div>
237
- </TooltipTrigger>
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">Date</span>
251
  <p className="text-sm font-semibold">{evaluation.completedDate}</p>
252
  </div>
253
  </div>
254
 
 
255
  <div className="space-y-3">
256
- <div className="space-y-2">
257
- <p className="text-sm text-muted-foreground">
258
- Capability Eval ({evaluation.capabilityEval.totalApplicable} applicable)
259
- </p>
260
- <div className="grid grid-cols-4 gap-4 text-xs items-end">
261
- {/* Vertical histogram bars for Capability */}
262
- <Tooltip>
263
- <TooltipTrigger asChild>
264
- <div className="text-center cursor-help">
265
- <div className="w-full h-20 flex items-end justify-center mb-2">
266
- <div
267
- className="w-full rounded-t-md bg-emerald-500"
268
- style={{
269
- height: `${Math.round(
270
- (evaluation.capabilityEval.strong / Math.max(1, capTotalComputed)) * 100
271
- )}%`,
272
- }}
273
- />
274
  </div>
275
- <div className="text-[10px] text-muted-foreground uppercase tracking-wide mb-1">Strong</div>
276
- <p className="font-medium">{evaluation.capabilityEval.strong}</p>
277
- </div>
278
- </TooltipTrigger>
279
- <TooltipContent>
280
- <p className="text-xs">
281
- {evaluation.capabilityEval.strongCategories.length > 0
282
- ? evaluation.capabilityEval.strongCategories.join(", ")
283
- : "No categories"}
284
- </p>
285
- </TooltipContent>
286
- </Tooltip>
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
- <div className="text-[10px] text-muted-foreground uppercase tracking-wide mb-1">Adequate</div>
302
- <p className="font-medium">{evaluation.capabilityEval.adequate}</p>
303
- </div>
304
- </TooltipTrigger>
305
- <TooltipContent>
306
- <p className="text-xs">
307
- {evaluation.capabilityEval.adequateCategories.length > 0
308
- ? evaluation.capabilityEval.adequateCategories.join(", ")
309
- : "No categories"}
310
- </p>
311
- </TooltipContent>
312
- </Tooltip>
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
- <div className="text-[10px] text-muted-foreground uppercase tracking-wide mb-1">Weak</div>
328
- <p className="font-medium">{evaluation.capabilityEval.weak}</p>
329
- </div>
330
- </TooltipTrigger>
331
- <TooltipContent>
332
- <p className="text-xs">
333
- {evaluation.capabilityEval.weakCategories.length > 0
334
- ? evaluation.capabilityEval.weakCategories.join(", ")
335
- : "No categories"}
336
- </p>
337
- </TooltipContent>
338
- </Tooltip>
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
- <div className="text-[10px] text-muted-foreground uppercase tracking-wide mb-1">Insufficient</div>
354
- <p className="font-medium">{evaluation.capabilityEval.insufficient}</p>
355
- </div>
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
- <div className="space-y-2">
369
- <p className="text-sm text-muted-foreground">
370
- Risk Eval ({evaluation.riskEval.totalApplicable} applicable)
371
- </p>
372
- <div className="grid grid-cols-4 gap-4 text-xs items-end">
373
- {/* Vertical histogram bars for Risk */}
374
- <Tooltip>
375
- <TooltipTrigger asChild>
376
- <div className="text-center cursor-help">
377
- <div className="w-full h-20 flex items-end justify-center mb-2">
378
- <div
379
- className="w-full rounded-t-md bg-emerald-500"
380
- style={{
381
- height: `${Math.round(
382
- (evaluation.riskEval.strong / Math.max(1, riskTotalComputed)) * 100
383
- )}%`,
384
- }}
385
- />
386
  </div>
387
- <div className="text-[10px] text-muted-foreground uppercase tracking-wide mb-1">Strong</div>
388
- <p className="font-medium">{evaluation.riskEval.strong}</p>
389
- </div>
390
- </TooltipTrigger>
391
- <TooltipContent>
392
- <p className="text-xs">
393
- {evaluation.riskEval.strongCategories.length > 0
394
- ? evaluation.riskEval.strongCategories.join(", ")
395
- : "No categories"}
396
- </p>
397
- </TooltipContent>
398
- </Tooltip>
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
- <div className="text-[10px] text-muted-foreground uppercase tracking-wide mb-1">Adequate</div>
414
- <p className="font-medium">{evaluation.riskEval.adequate}</p>
415
- </div>
416
- </TooltipTrigger>
417
- <TooltipContent>
418
- <p className="text-xs">
419
- {evaluation.riskEval.adequateCategories.length > 0
420
- ? evaluation.riskEval.adequateCategories.join(", ")
421
- : "No categories"}
422
- </p>
423
- </TooltipContent>
424
- </Tooltip>
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
- <div className="text-[10px] text-muted-foreground uppercase tracking-wide mb-1">Weak</div>
440
- <p className="font-medium">{evaluation.riskEval.weak}</p>
441
- </div>
442
- </TooltipTrigger>
443
- <TooltipContent>
444
- <p className="text-xs">
445
- {evaluation.riskEval.weakCategories.length > 0
446
- ? evaluation.riskEval.weakCategories.join(", ")
447
- : "No categories"}
448
- </p>
449
- </TooltipContent>
450
- </Tooltip>
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
- <div className="text-[10px] text-muted-foreground uppercase tracking-wide mb-1">Insufficient</div>
466
- <p className="font-medium">{evaluation.riskEval.insufficient}</p>
467
- </div>
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
  )