'use client' import { useState } from 'react' import { trpc } from '@/lib/trpc/client' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Skeleton } from '@/components/ui/skeleton' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog' import { Bot, FileText, RefreshCw, Loader2, CheckCircle2, AlertTriangle, Clock, Users, Target, } from 'lucide-react' import { toast } from 'sonner' import { formatDistanceToNow } from 'date-fns' interface EvaluationSummaryCardProps { projectId: string roundId: string } interface BooleanStats { yesCount: number noCount: number total: number yesPercent: number trueLabel: string falseLabel: string } interface ScoringPatterns { averageGlobalScore: number | null consensus: number criterionAverages: Record booleanCriteria?: Record textResponses?: Record evaluatorCount: number } interface ThemeItem { theme: string sentiment: 'positive' | 'negative' | 'mixed' frequency: number } interface SummaryJson { overallAssessment: string strengths: string[] weaknesses: string[] themes: ThemeItem[] recommendation: string scoringPatterns: ScoringPatterns } const sentimentColors: Record = { positive: { badge: 'default', bg: 'bg-green-500/10 text-green-700' }, negative: { badge: 'destructive', bg: 'bg-red-500/10 text-red-700' }, mixed: { badge: 'secondary', bg: 'bg-amber-500/10 text-amber-700' }, } export function EvaluationSummaryCard({ projectId, roundId, }: EvaluationSummaryCardProps) { const [isGenerating, setIsGenerating] = useState(false) const { data: summary, isLoading, refetch, } = trpc.evaluation.getSummary.useQuery({ projectId, roundId }) const generateMutation = trpc.evaluation.generateSummary.useMutation({ onSuccess: () => { toast.success('AI summary generated successfully') refetch() setIsGenerating(false) }, onError: (error) => { toast.error(error.message || 'Failed to generate summary') setIsGenerating(false) }, }) const handleGenerate = () => { setIsGenerating(true) generateMutation.mutate({ projectId, roundId }) } if (isLoading) { return ( ) } // No summary exists yet if (!summary) { return ( AI Evaluation Summary Generate an AI-powered analysis of jury evaluations

No summary generated yet. Click below to analyze submitted evaluations.

) } const summaryData = summary.summaryJson as unknown as SummaryJson const patterns = summaryData.scoringPatterns return (
AI Evaluation Summary AI Generated Generated {formatDistanceToNow(new Date(summary.generatedAt), { addSuffix: true })} {' '}using {summary.model}
Regenerate Summary This will replace the existing AI summary with a new one. This uses your OpenAI API quota. Cancel Regenerate
{/* Scoring Stats */}

{patterns.averageGlobalScore !== null ? patterns.averageGlobalScore.toFixed(1) : '-'}

Avg Score

{Math.round(patterns.consensus * 100)}%

Consensus

{patterns.evaluatorCount}

Evaluators

{/* Overall Assessment */}

Overall Assessment

{summaryData.overallAssessment}

{/* Strengths & Weaknesses */}
{summaryData.strengths.length > 0 && (

Strengths

    {summaryData.strengths.map((s, i) => (
  • {s}
  • ))}
)} {summaryData.weaknesses.length > 0 && (

Weaknesses

    {summaryData.weaknesses.map((w, i) => (
  • {w}
  • ))}
)}
{/* Themes */} {summaryData.themes.length > 0 && (

Key Themes

{summaryData.themes.map((theme, i) => (
{theme.sentiment} {theme.theme}
{theme.frequency} mention{theme.frequency !== 1 ? 's' : ''}
))}
)} {/* Criterion Averages (Numeric) */} {Object.keys(patterns.criterionAverages).length > 0 && (

Score Averages

{Object.entries(patterns.criterionAverages).map(([label, avg]) => (
{label}
{avg.toFixed(1)}
))}
)} {/* Boolean Criteria (Yes/No) */} {patterns.booleanCriteria && Object.keys(patterns.booleanCriteria).length > 0 && (

Yes/No Decisions

{Object.entries(patterns.booleanCriteria).map(([label, stats]) => (
{label} {stats.yesCount} {stats.trueLabel} / {stats.noCount} {stats.falseLabel}
{stats.yesCount > 0 && (
)} {stats.noCount > 0 && (
)}
{stats.yesPercent}% {stats.trueLabel} {100 - stats.yesPercent}% {stats.falseLabel}
))}
)} {/* Text Responses */} {patterns.textResponses && Object.keys(patterns.textResponses).length > 0 && (

Text Responses

{Object.entries(patterns.textResponses).map(([label, responses]) => (

{label}

{responses.map((text, i) => (
{text}
))}
))}
)} {/* Recommendation */} {summaryData.recommendation && (

Recommendation

{summaryData.recommendation}

)} ) }