File size: 6,895 Bytes
3a12290
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
"use client"

import type { ComponentType, CSSProperties } from "react"
import { useAudienceMode } from "@/components/audience-mode-provider"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader } from "@/components/ui/card"
import { useRouter } from "next/navigation"
import {
  AlertTriangle,
  BadgeCheck,
  BookOpenText,
  ChartNoAxesColumn,
  FlaskConical,
  Scale,
  Users,
} from "lucide-react"
import type { BenchmarkEvalListItem } from "@/lib/eval-processing"

interface EvalCardProps {
  summary: BenchmarkEvalListItem
  delayMs?: number
}

export function EvalCard({ summary, delayMs = 0 }: EvalCardProps) {
  const router = useRouter()
  const { mode } = useAudienceMode()
  const isResearchView = mode === "research"
  const scorePercent = `${Math.round(summary.avg_score_norm * 100)}%`
  const purpose = summary.factsheet?.purpose ?? "General capability evaluation"

  return (
    <Card
      className="motion-academic-enter motion-academic-surface motion-academic-hover cursor-pointer overflow-hidden border-border/70 bg-card hover:shadow-lg"
      style={{ "--enter-delay": `${delayMs}ms` } as CSSProperties}
      onClick={() => router.push(`/evals/${summary.evaluation_id}`)}
    >
      <CardHeader className="space-y-3 border-b border-border/60 pb-4">
        <div className="text-[10px] font-semibold uppercase tracking-[0.24em] text-muted-foreground">
          Eval Summary
        </div>

        <div className="min-w-0">
          <div className="text-xl font-bold">{summary.evaluation_name}</div>
          <div className="mt-1 text-sm text-muted-foreground line-clamp-2">
            {summary.metric_config.evaluation_description}
          </div>
        </div>

        <div className="flex flex-wrap gap-2">
          {summary.third_party_ratio > 0 && (
            <Badge className="bg-emerald-600 text-white hover:bg-emerald-600">
              <BadgeCheck className="mr-1 h-3 w-3" />
              Independently evaluated
            </Badge>
          )}
          {summary.missing_generation_config_count > 0 && (
            <Badge className="bg-amber-500 text-amber-950 hover:bg-amber-500">
              <AlertTriangle className="mr-1 h-3 w-3" />
              Partial config
            </Badge>
          )}
        </div>
      </CardHeader>

      <CardContent className="space-y-4 pt-4">
        {isResearchView ? (
          <>
            <div className="grid gap-2 sm:grid-cols-3">
              <MetricPill icon={FlaskConical} label="Models" value={summary.models_count.toLocaleString()} tone="bg-sky-100/80 text-sky-900 dark:bg-sky-950/40 dark:text-sky-100" />
              <MetricPill icon={ChartNoAxesColumn} label="Avg Score" value={scorePercent} tone="bg-amber-100/80 text-amber-900 dark:bg-amber-950/40 dark:text-amber-100" />
              <MetricPill icon={Users} label="Evaluators" value={summary.evaluator_names.length.toLocaleString()} tone="bg-emerald-100/80 text-emerald-900 dark:bg-emerald-950/40 dark:text-emerald-100" />
            </div>

            <div className="rounded-xl border bg-muted/10 p-3">
              <div className="mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">Methodology</div>
              <div className="space-y-1.5 text-sm">
                {summary.factsheet?.principles_tested && summary.factsheet.principles_tested !== "Not specified" && (
                  <DataRow label="Principles" value={summary.factsheet.principles_tested} />
                )}
                {summary.latest_source_name && (
                  <DataRow label="Source" value={summary.latest_source_name} />
                )}
                <DataRow
                  label="Config"
                  value={
                    summary.missing_generation_config_count > 0
                      ? `${summary.missing_generation_config_count} result${summary.missing_generation_config_count !== 1 ? "s" : ""} without config`
                      : "Fully documented"
                  }
                />
                <DataRow
                  label="Third-party"
                  value={`${Math.round(summary.third_party_ratio * 100)}%`}
                />
              </div>
            </div>
          </>
        ) : (
          <>
            <div className="rounded-xl border border-amber-200/70 bg-amber-50/75 p-3 dark:border-amber-900/40 dark:bg-amber-950/15">
              <div className="flex items-start gap-2">
                <Scale className="mt-0.5 h-4 w-4 shrink-0 text-amber-600" />
                <div>
                  <div className="text-sm font-semibold">Purpose</div>
                  <div className="text-sm text-muted-foreground">{purpose}</div>
                </div>
              </div>
            </div>

            <div className="grid gap-2 sm:grid-cols-2">
              <MetricPill icon={BadgeCheck} label="Independent" value={`${Math.round(summary.third_party_ratio * 100)}%`} tone="bg-emerald-100/80 text-emerald-900 dark:bg-emerald-950/40 dark:text-emerald-100" />
              <MetricPill icon={BookOpenText} label="Models" value={summary.models_count.toLocaleString()} tone="bg-sky-100/80 text-sky-900 dark:bg-sky-950/40 dark:text-sky-100" />
            </div>

            <div className="rounded-xl border bg-muted/10 p-3">
              <div className="space-y-1.5 text-sm">
                <DataRow
                  label="Avg score"
                  value={scorePercent}
                />
                <DataRow
                  label="Reported by"
                  value={summary.evaluator_names.join(", ") || "Unknown"}
                />
                {summary.missing_generation_config_count > 0 && (
                  <p className="pt-1 text-xs text-muted-foreground">
                    Some results lack documented generation settings — direct score comparisons should be read with care.
                  </p>
                )}
              </div>
            </div>
          </>
        )}
      </CardContent>
    </Card>
  )
}

function MetricPill({
  icon: Icon,
  label,
  value,
  tone,
}: {
  icon: ComponentType<{ className?: string }>
  label: string
  value: string
  tone: string
}) {
  return (
    <div className={`flex items-center justify-between rounded-xl px-3 py-2 ${tone}`}>
      <div className="flex items-center gap-2">
        <Icon className="h-4 w-4" />
        <span className="text-[11px] font-semibold uppercase tracking-[0.18em] opacity-80">{label}</span>
      </div>
      <span className="text-sm font-bold">{value}</span>
    </div>
  )
}

function DataRow({ label, value }: { label: string; value: string }) {
  return (
    <div className="flex items-start justify-between gap-3">
      <span className="shrink-0 text-muted-foreground">{label}</span>
      <span className="text-right font-medium text-foreground">{value}</span>
    </div>
  )
}