|
|
|
|
|
"use client"; |
|
|
|
import React, { useState, useMemo, useEffect } from "react"; |
|
import { |
|
BarChart, |
|
Bar, |
|
XAxis, |
|
YAxis, |
|
CartesianGrid, |
|
Tooltip as RechartsTooltip, |
|
ResponsiveContainer, |
|
Cell, |
|
} from "recharts"; |
|
import { |
|
getMetricTooltip, |
|
getScoreBadgeColor, |
|
formatDisplayKey, |
|
camelToTitle, |
|
} from "../lib/utils"; |
|
|
|
|
|
const InfoTooltip = ({ text }) => { |
|
|
|
const [isVisible, setIsVisible] = useState(false); |
|
return ( |
|
<div className="relative inline-block ml-1 align-middle"> |
|
<button |
|
className="text-gray-400 hover:text-gray-600 focus:outline-none" |
|
onMouseEnter={() => setIsVisible(true)} |
|
onMouseLeave={() => setIsVisible(false)} |
|
onClick={(e) => { |
|
e.stopPropagation(); |
|
setIsVisible(!isVisible); |
|
}} |
|
aria-label="Info" |
|
> |
|
<svg |
|
xmlns="http://www.w3.org/2000/svg" |
|
className="h-4 w-4" |
|
viewBox="0 0 20 20" |
|
fill="currentColor" |
|
> |
|
<path |
|
fillRule="evenodd" |
|
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" |
|
clipRule="evenodd" |
|
/> |
|
</svg>{" "} |
|
</button>{" "} |
|
{isVisible && ( |
|
<div className="absolute z-10 w-64 p-2 bg-white border rounded shadow-lg text-xs text-gray-700 -translate-x-1/2 left-1/2 mt-1"> |
|
{text} |
|
</div> |
|
)}{" "} |
|
</div> |
|
); |
|
}; |
|
|
|
|
|
const CustomTooltip = ({ active, payload, label }) => { |
|
|
|
if (active && payload && payload.length) { |
|
const sortedPayload = [...payload].sort( |
|
(a, b) => (b.value || 0) - (a.value || 0) |
|
); |
|
return ( |
|
<div className="bg-white p-3 border rounded shadow-lg max-w-xs"> |
|
<p className="font-medium text-sm">{label}</p>{" "} |
|
{sortedPayload.map((entry, index) => ( |
|
<div key={`item-${index}`} className="flex items-center mt-1"> |
|
<div |
|
className="w-3 h-3 mr-2 rounded-full flex-shrink-0" |
|
style={{ |
|
backgroundColor: |
|
entry.payload?.color || entry.color || "#8884d8", |
|
}} |
|
></div>{" "} |
|
<span className="text-xs flex-grow pr-2">{entry.name}: </span>{" "} |
|
<span className="text-xs font-medium ml-1 whitespace-nowrap"> |
|
{typeof entry.value === "number" ? entry.value.toFixed(1) : "N/A"} |
|
</span>{" "} |
|
</div> |
|
))}{" "} |
|
</div> |
|
); |
|
} |
|
return null; |
|
}; |
|
|
|
|
|
const TabButton = ({ active, onClick, children }) => ( |
|
<button |
|
aria-pressed={active} |
|
className={`px-4 py-1.5 text-sm font-medium rounded-md transition-colors duration-150 ${ |
|
active |
|
? "bg-white shadow text-blue-600" |
|
: "text-gray-600 hover:text-gray-800" |
|
}`} |
|
onClick={onClick} |
|
> |
|
{children}{" "} |
|
</button> |
|
); |
|
|
|
|
|
const TaskPerformance = ({ |
|
rawData, |
|
modelsMeta, |
|
metricsData, // Expects Title Case keys (e.g., Context Memory) containing internalMetricKey |
|
overviewCardData, |
|
}) => { |
|
const [activeTab, setActiveTab] = useState("top-performers"); |
|
|
|
|
|
const highLevelMetricDisplayKeys = useMemo( |
|
() => Object.keys(metricsData?.highLevelCategories || {}).sort(), |
|
[metricsData?.highLevelCategories] |
|
); |
|
const lowLevelMetricDisplayKeys = useMemo( |
|
() => Object.keys(metricsData?.lowLevelMetrics || {}).sort(), |
|
[metricsData?.lowLevelMetrics] |
|
); |
|
|
|
|
|
|
|
const { taskLevelPerformance = {}, tasks = [] } = rawData || {}; |
|
const { bestModelPerTask = {} } = overviewCardData || {}; |
|
const models = modelsMeta || []; |
|
|
|
|
|
const [selectedTask, setSelectedTask] = useState( |
|
tasks.length > 0 ? tasks[0] : "all" |
|
); |
|
const [selectedMetricType, setSelectedMetricType] = useState("high"); |
|
|
|
const [selectedMetricDisplayKey, setSelectedMetricDisplayKey] = useState(""); |
|
|
|
const [selectedModels, setSelectedModels] = useState([]); |
|
|
|
|
|
const currentMetricDisplayKeysList = useMemo( |
|
() => |
|
selectedMetricType === "high" |
|
? highLevelMetricDisplayKeys |
|
: lowLevelMetricDisplayKeys, |
|
[selectedMetricType, highLevelMetricDisplayKeys, lowLevelMetricDisplayKeys] |
|
); |
|
|
|
|
|
useEffect(() => { |
|
if (models.length > 0 && selectedModels.length === 0) { |
|
setSelectedModels(models.map((m) => m.model)); |
|
} |
|
}, [models, selectedModels.length]); |
|
|
|
|
|
useEffect(() => { |
|
if (currentMetricDisplayKeysList.length > 0) { |
|
if ( |
|
!selectedMetricDisplayKey || |
|
!currentMetricDisplayKeysList.includes(selectedMetricDisplayKey) |
|
) { |
|
setSelectedMetricDisplayKey(currentMetricDisplayKeysList[0]); |
|
} |
|
} else { |
|
setSelectedMetricDisplayKey(""); |
|
} |
|
}, [currentMetricDisplayKeysList, selectedMetricDisplayKey]); |
|
|
|
|
|
const chartData = useMemo(() => { |
|
if ( |
|
!taskLevelPerformance || |
|
!selectedMetricDisplayKey || |
|
selectedModels.length === 0 |
|
) |
|
return []; |
|
|
|
|
|
const allMetricsProcessed = { |
|
...(metricsData?.highLevelCategories || {}), |
|
...(metricsData?.lowLevelMetrics || {}), |
|
}; |
|
const metricInfo = allMetricsProcessed[selectedMetricDisplayKey]; |
|
const internalMetricKey = metricInfo?.internalMetricKey; |
|
|
|
if (!internalMetricKey) { |
|
console.warn( |
|
`Could not find internal key for selected metric: ${selectedMetricDisplayKey}` |
|
); |
|
return []; |
|
} |
|
|
|
let data = []; |
|
if (selectedTask === "all") { |
|
const modelAggregates = {}; |
|
tasks.forEach((task) => { |
|
if (taskLevelPerformance[task]) { |
|
Object.entries(taskLevelPerformance[task]).forEach( |
|
([model, metrics]) => { |
|
if (selectedModels.includes(model)) { |
|
|
|
const score = metrics?.[internalMetricKey]; |
|
if (score !== undefined && score !== null && score !== "N/A") { |
|
const numScore = parseFloat(score); |
|
if (!isNaN(numScore)) { |
|
if (!modelAggregates[model]) |
|
modelAggregates[model] = { sum: 0, count: 0 }; |
|
modelAggregates[model].sum += numScore; |
|
modelAggregates[model].count++; |
|
} |
|
} |
|
} |
|
} |
|
); |
|
} |
|
}); |
|
data = Object.entries(modelAggregates).map(([model, aggregates]) => { |
|
const modelMeta = models.find((m) => m.model === model) || {}; |
|
return { |
|
model: model, |
|
score: |
|
aggregates.count > 0 ? aggregates.sum / aggregates.count : null, |
|
color: modelMeta.color || "#999999", |
|
}; |
|
}); |
|
} else if (taskLevelPerformance[selectedTask]) { |
|
data = Object.entries(taskLevelPerformance[selectedTask]) |
|
.filter(([model, _metrics]) => selectedModels.includes(model)) |
|
.map(([model, metrics]) => { |
|
|
|
const score = metrics?.[internalMetricKey]; |
|
const modelMeta = models.find((m) => m.model === model) || {}; |
|
return { |
|
model: model, |
|
score: |
|
score !== undefined && score !== null && score !== "N/A" |
|
? parseFloat(score) |
|
: null, |
|
color: modelMeta.color || "#999999", |
|
}; |
|
}); |
|
} |
|
|
|
return data |
|
.filter((item) => item.score !== null && !isNaN(item.score)) |
|
.sort((a, b) => b.score - a.score); |
|
|
|
}, [ |
|
selectedTask, |
|
selectedMetricDisplayKey, |
|
selectedModels, |
|
taskLevelPerformance, |
|
models, |
|
metricsData, |
|
tasks, |
|
]); |
|
|
|
|
|
const featuredTasks = useMemo( |
|
() => [ |
|
{ |
|
id: "Generating a Creative Idea", |
|
title: "Generating Creative Ideas", |
|
description: "Brainstorming unique birthday gift ideas.", |
|
icon: (color) => ( |
|
<svg |
|
style={{ color: color || "#6b7280" }} |
|
className="h-8 w-8" |
|
fill="none" |
|
viewBox="0 0 24 24" |
|
stroke="currentColor" |
|
> |
|
<path |
|
strokeLinecap="round" |
|
strokeLinejoin="round" |
|
strokeWidth={2} |
|
d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" |
|
/> |
|
</svg> |
|
), |
|
}, |
|
{ |
|
id: "Creating a Travel Itinerary", |
|
title: "Creating Travel Itinerary", |
|
description: "Planning a European city break.", |
|
icon: (color) => ( |
|
<svg |
|
style={{ color: color || "#6b7280" }} |
|
className="h-8 w-8" |
|
fill="none" |
|
viewBox="0 0 24 24" |
|
stroke="currentColor" |
|
> |
|
<path |
|
strokeLinecap="round" |
|
strokeLinejoin="round" |
|
strokeWidth={2} |
|
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" |
|
/> |
|
<path |
|
strokeLinecap="round" |
|
strokeLinejoin="round" |
|
strokeWidth={2} |
|
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" |
|
/> |
|
</svg> |
|
), |
|
}, |
|
{ |
|
id: "Following Up on a Job Application", |
|
title: "Following Up on Job App", |
|
description: "Drafting a professional follow-up email.", |
|
icon: (color) => ( |
|
<svg |
|
style={{ color: color || "#6b7280" }} |
|
className="h-8 w-8" |
|
fill="none" |
|
viewBox="0 0 24 24" |
|
stroke="currentColor" |
|
> |
|
<path |
|
strokeLinecap="round" |
|
strokeLinejoin="round" |
|
strokeWidth={2} |
|
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" |
|
/> |
|
</svg> |
|
), |
|
}, |
|
{ |
|
id: "Planning Your Weekly Meals", |
|
title: "Planning Weekly Meals", |
|
description: "Creating a meal plan accommodating dietary restrictions.", |
|
icon: (color) => ( |
|
<svg |
|
style={{ color: color || "#6b7280" }} |
|
className="h-8 w-8" |
|
fill="none" |
|
viewBox="0 0 24 24" |
|
stroke="currentColor" |
|
> |
|
<path |
|
strokeLinecap="round" |
|
strokeLinejoin="round" |
|
strokeWidth={2} |
|
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" |
|
/> |
|
</svg> |
|
), |
|
}, |
|
{ |
|
id: "Making a Decision Between Options", |
|
title: "Making a Decision", |
|
description: "Comparing tech products for purchase.", |
|
icon: (color) => ( |
|
<svg |
|
style={{ color: color || "#6b7280" }} |
|
className="h-8 w-8" |
|
fill="none" |
|
viewBox="0 0 24 24" |
|
stroke="currentColor" |
|
strokeWidth={2} |
|
> |
|
<path |
|
strokeLinecap="round" |
|
strokeLinejoin="round" |
|
d="M14 5l7 7m0 0l-7 7m7-7H3" |
|
/>{" "} |
|
<path |
|
strokeLinecap="round" |
|
strokeLinejoin="round" |
|
d="M10 19l-7-7m0 0l7-7m-7 7h17" |
|
/> |
|
</svg> |
|
), |
|
}, |
|
{ |
|
id: "Understanding a Complex Topic", |
|
title: "Understanding a Complex Topic", |
|
description: "Learning about day trading concepts.", |
|
icon: (color) => ( |
|
<svg |
|
style={{ color: color || "#6b7280" }} |
|
className="h-8 w-8" |
|
fill="none" |
|
viewBox="0 0 24 24" |
|
stroke="currentColor" |
|
> |
|
<path |
|
strokeLinecap="round" |
|
strokeLinejoin="round" |
|
strokeWidth={2} |
|
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" |
|
/> |
|
</svg> |
|
), |
|
}, |
|
], |
|
[] |
|
); |
|
const tasksToDisplay = useMemo(() => { |
|
const availableTaskKeys = bestModelPerTask |
|
? Object.keys(bestModelPerTask) |
|
: []; |
|
return featuredTasks.filter((ft) => availableTaskKeys.includes(ft.id)); |
|
}, [bestModelPerTask, featuredTasks]); |
|
const taskRankings = useMemo(() => { |
|
const rankings = {}; |
|
tasksToDisplay.forEach((task) => { |
|
const taskId = task.id; |
|
if (!taskLevelPerformance[taskId]) { |
|
rankings[taskId] = []; |
|
return; |
|
} |
|
const taskScores = models |
|
.map((modelMeta) => { |
|
const modelData = taskLevelPerformance[taskId][modelMeta.model]; |
|
if (!modelData) return null; |
|
const scores = Object.values(modelData) |
|
.map((s) => parseFloat(s)) |
|
.filter((s) => !isNaN(s)); |
|
if (scores.length === 0) return null; |
|
const avgScore = |
|
scores.reduce((sum, score) => sum + score, 0) / scores.length; |
|
return { |
|
model: modelMeta.model, |
|
taskAvgScore: avgScore, |
|
color: modelMeta.color || "#999999", |
|
}; |
|
}) |
|
.filter((item) => item !== null) |
|
.sort((a, b) => b.taskAvgScore - a.taskAvgScore); |
|
rankings[taskId] = taskScores; |
|
}); |
|
return rankings; |
|
}, [tasksToDisplay, taskLevelPerformance, models]); |
|
|
|
const renderTopPerformersTab = () => ( |
|
<div className="mb-6"> |
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> |
|
{tasksToDisplay.length === 0 && ( |
|
<p className="col-span-full text-center text-gray-500 py-8"> |
|
No task performance data available. |
|
</p> |
|
)} |
|
{tasksToDisplay.map((task) => { |
|
const bestModelInfo = bestModelPerTask?.[task.id]; |
|
const topModelsForTask = taskRankings[task.id] || []; |
|
if (!bestModelInfo || bestModelInfo.model === "N/A") return null; |
|
const modelColor = bestModelInfo.color || "#6b7280"; |
|
return ( |
|
<div |
|
key={task.id} |
|
className="border rounded-lg overflow-hidden shadow-sm bg-white flex flex-col" |
|
> |
|
<div className="px-4 py-2 bg-gray-50 border-b flex items-center flex-shrink-0"> |
|
<h3 |
|
className="font-semibold text-sm flex-grow truncate pr-2" |
|
title={task.title} |
|
> |
|
{task.title} |
|
</h3> |
|
<div |
|
className="ml-1 w-2 h-2 rounded-full flex-shrink-0" |
|
style={{ backgroundColor: modelColor }} |
|
aria-hidden="true" |
|
></div> |
|
</div> |
|
<div className="p-4 flex-grow flex flex-col"> |
|
<div className="flex items-center mb-4 flex-shrink-0"> |
|
<div |
|
className="p-2 rounded-full flex-shrink-0" |
|
style={{ backgroundColor: `${modelColor}20` }} |
|
> |
|
{task.icon(modelColor)} |
|
</div> |
|
<div className="ml-4 overflow-hidden"> |
|
<h4 |
|
className="text-lg font-semibold truncate" |
|
title={bestModelInfo.model} |
|
> |
|
{bestModelInfo.model} |
|
</h4> |
|
<p className="text-sm text-gray-600"> |
|
Avg. Score: {bestModelInfo.score?.toFixed(1) ?? "N/A"} |
|
</p> |
|
</div> |
|
</div> |
|
<div className="mb-4 flex-grow"> |
|
<h5 className="text-sm font-semibold mb-2">Task Ranking</h5> |
|
{topModelsForTask.length > 0 ? ( |
|
<ol className="space-y-1.5 list-none pl-0"> |
|
{topModelsForTask.map((rankedModel, index) => ( |
|
<li |
|
key={rankedModel.model} |
|
className="text-sm flex items-center justify-between" |
|
> |
|
<div className="flex items-center truncate mr-2"> |
|
<span className="font-medium w-4 mr-1.5 text-gray-500"> |
|
{index + 1}. |
|
</span> |
|
<div |
|
className="w-2.5 h-2.5 rounded-full mr-1.5 flex-shrink-0" |
|
style={{ backgroundColor: rankedModel.color }} |
|
></div> |
|
<span |
|
className="truncate" |
|
title={rankedModel.model} |
|
> |
|
{rankedModel.model} |
|
</span> |
|
</div> |
|
<span |
|
className={`font-medium flex-shrink-0 px-1.5 py-0.5 text-xs rounded ${getScoreBadgeColor( |
|
rankedModel.taskAvgScore |
|
)}`} |
|
> |
|
{rankedModel.taskAvgScore?.toFixed(1) ?? "N/A"} |
|
</span> |
|
</li> |
|
))} |
|
</ol> |
|
) : ( |
|
<p className="text-xs text-gray-500 italic"> |
|
Ranking data not available. |
|
</p> |
|
)} |
|
</div> |
|
<p className="text-xs text-gray-600 mt-auto pt-2 flex-shrink-0"> |
|
Task Example: {task.description} |
|
</p> |
|
</div> |
|
</div> |
|
); |
|
})} |
|
</div> |
|
</div> |
|
); |
|
|
|
|
|
const renderModelPerformanceTab = () => ( |
|
<div> |
|
{/* Controls Panel */} |
|
<div className="border rounded-lg overflow-hidden mb-6 shadow-sm"> |
|
<div className="px-4 py-3 bg-gray-50 border-b"> |
|
<h3 className="font-semibold text-gray-800"> |
|
Task Analysis Controls |
|
</h3> |
|
</div> |
|
<div className="p-4 flex flex-wrap items-center gap-4"> |
|
{/* Task Selector */} |
|
<div className="w-full sm:w-auto"> |
|
<label |
|
htmlFor="taskSelect" |
|
className="block text-sm font-medium text-gray-700 mb-1" |
|
> |
|
Task |
|
</label> |
|
<select |
|
id="taskSelect" |
|
className="w-full sm:w-64 border rounded-md px-3 py-2 bg-white shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500" |
|
value={selectedTask} |
|
onChange={(e) => setSelectedTask(e.target.value)} |
|
> |
|
<option value="all">All Tasks (Average)</option> |
|
{tasks.sort().map((task) => ( |
|
<option key={task} value={task}> |
|
{task} |
|
</option> |
|
))} |
|
</select> |
|
</div> |
|
{/* Metric Type Selector Pills */} |
|
<div className="flex flex-col"> |
|
<label className="block text-sm font-medium text-gray-700 mb-1"> |
|
Metric Type |
|
</label> |
|
<div className="flex space-x-1 p-1 bg-gray-200 rounded-lg"> |
|
<TabButton |
|
active={selectedMetricType === "high"} |
|
onClick={() => setSelectedMetricType("high")} |
|
> |
|
High-Level |
|
</TabButton> |
|
<TabButton |
|
active={selectedMetricType === "low"} |
|
onClick={() => setSelectedMetricType("low")} |
|
> |
|
Low-Level |
|
</TabButton> |
|
</div> |
|
</div> |
|
{/* Metric Selector - VALUE is Title Case key, displays Title Case */} |
|
<div className="w-full sm:w-auto"> |
|
<label |
|
htmlFor="metricSelect" |
|
className="block text-sm font-medium text-gray-700 mb-1" |
|
> |
|
{selectedMetricType === "high" |
|
? "High-Level Metric" |
|
: "Low-Level Metric"} |
|
</label> |
|
<select |
|
id="metricSelect" |
|
className="w-full sm:w-48 border rounded-md px-3 py-2 bg-white shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500" |
|
value={selectedMetricDisplayKey} // VALUE is the Title Case key |
|
onChange={(e) => setSelectedMetricDisplayKey(e.target.value)} // Store Title Case key |
|
disabled={currentMetricDisplayKeysList.length === 0} |
|
> |
|
{currentMetricDisplayKeysList.length === 0 && ( |
|
<option value="">No metrics</option> |
|
)} |
|
{/* Iterate through Title Case keys, display Title Case */} |
|
{currentMetricDisplayKeysList.map((displayKey) => ( |
|
<option key={displayKey} value={displayKey}> |
|
{displayKey} |
|
</option> |
|
))} |
|
</select> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
{/* Chart Visualization */} |
|
<div className="border rounded-lg overflow-hidden mb-6 shadow-sm"> |
|
{/* Use selectedMetricDisplayKey for title */} |
|
<div className="px-4 py-3 bg-gray-50 border-b"> |
|
<h3 className="font-semibold text-gray-800"> |
|
{`${selectedMetricDisplayKey || "Selected Metric"} Comparison for `} |
|
<span className="font-normal"> |
|
{selectedTask === "all" |
|
? "All Tasks (Average)" |
|
: `"${selectedTask}"`} |
|
</span> |
|
</h3> |
|
</div> |
|
<div className="p-4"> |
|
{chartData.length > 0 ? ( |
|
<div className="h-80"> |
|
<ResponsiveContainer width="100%" height="100%"> |
|
<BarChart |
|
data={chartData} |
|
margin={{ top: 5, right: 5, left: 0, bottom: 5 }} |
|
barCategoryGap="20%" |
|
> |
|
<CartesianGrid strokeDasharray="3 3" vertical={false} /> |
|
<XAxis dataKey="model" hide /> |
|
<YAxis domain={[0, 100]} width={30} tick={{ fontSize: 11 }} /> |
|
<RechartsTooltip |
|
content={<CustomTooltip />} |
|
wrapperStyle={{ zIndex: 10 }} |
|
/> |
|
{/* Use Title Case key for Bar name */} |
|
<Bar |
|
dataKey="score" |
|
name={selectedMetricDisplayKey || "Score"} |
|
radius={[4, 4, 0, 0]} |
|
> |
|
{chartData.map((entry, index) => ( |
|
<Cell key={`cell-${index}`} fill={entry.color} /> |
|
))} |
|
</Bar> |
|
</BarChart> |
|
</ResponsiveContainer> |
|
<div className="flex flex-wrap justify-center gap-x-4 gap-y-1 mt-4 text-xs"> |
|
{chartData.map((entry) => ( |
|
<div key={entry.model} className="flex items-center"> |
|
<div |
|
className="w-2.5 h-2.5 rounded-full mr-1.5" |
|
style={{ backgroundColor: entry.color }} |
|
></div> |
|
<span>{entry.model}</span> |
|
</div> |
|
))} |
|
</div> |
|
</div> |
|
) : ( |
|
<div className="flex items-center justify-center h-60 bg-gray-50 rounded"> |
|
<div className="text-center p-4"> |
|
<svg |
|
xmlns="http://www.w3.org/2000/svg" |
|
className="h-10 w-10 mx-auto text-gray-400 mb-3" |
|
fill="none" |
|
viewBox="0 0 24 24" |
|
stroke="currentColor" |
|
> |
|
<path |
|
strokeLinecap="round" |
|
strokeLinejoin="round" |
|
strokeWidth={2} |
|
d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V7a2 2 0 012-2h2l2-3h6l2 3h2a2 2 0 012 2v10a2 2 0 01-2 2h-1" |
|
/> |
|
</svg> |
|
<h3 className="text-lg font-medium text-gray-900 mb-1"> |
|
No Data Available |
|
</h3> |
|
<p className="text-sm text-gray-600"> |
|
No data available for the selected task, metric, and models. |
|
</p> |
|
</div> |
|
</div> |
|
)} |
|
<div className="mt-15 text-xs text-gray-500"> |
|
{/* Corrected margin-top */} |
|
{/* Use Title Case key for display and lookup */} |
|
<p> |
|
This chart shows{" "} |
|
<strong> |
|
{selectedMetricDisplayKey || "the selected metric"} |
|
</strong>{" "} |
|
scores (0-100, higher is better) for models on |
|
{selectedTask === "all" |
|
? "average across all tasks" |
|
: `the "${selectedTask}" task`} |
|
. |
|
{selectedMetricDisplayKey && |
|
` Metric definition: ${getMetricTooltip( |
|
selectedMetricDisplayKey |
|
)}`} |
|
</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
|
|
|
|
return ( |
|
<div> |
|
<div className="mb-6 flex flex-col md:flex-row justify-between items-center gap-4"> |
|
<div className="flex space-x-1 p-1 bg-gray-200 rounded-lg"> |
|
<TabButton |
|
active={activeTab === "top-performers"} |
|
onClick={() => setActiveTab("top-performers")} |
|
> |
|
Top Performing Models by Task |
|
</TabButton>{" "} |
|
<TabButton |
|
active={activeTab === "model-performance"} |
|
onClick={() => setActiveTab("model-performance")} |
|
> |
|
Model Performance Comparison |
|
</TabButton>{" "} |
|
</div>{" "} |
|
</div> |
|
{activeTab === "top-performers" |
|
? renderTopPerformersTab() |
|
: renderModelPerformanceTab()} |
|
</div> |
|
); |
|
}; |
|
|
|
export default TaskPerformance; |
|
|