Fork of Excalidraw table mockup was forked from Fork of Excalidraw table mockup
Continue chatting to ask questions about or make changes to it.
Can you add this new component and render it above the transcript? Pass in the transcript table data but ensure it is formatted correctly. Feel free to modify this component I am giving you to conform to our mockup here
"use client";
import { useState, useEffect } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "~/components/ui/select"; import { PieChart, Pie, Tooltip, Cell } from "recharts"; import type { Speaker } from "~/server/db/queries/spokenTextQueries";
interface SpeakerDeceptionProps { speakers?: Speaker[]; }
const calculateDeceptionPercentage = (speakerStatements: Speaker[] = []) => { if (!Array.isArray(speakerStatements) || speakerStatements.length === 0) return 0;
const deceptiveCount = speakerStatements.filter( (statement) => statement.deceptionLabel >= 3, ).length;
return Math.round((deceptiveCount / speakerStatements.length) * 100); };
const groupStatementsBySpeaker = (speakers: Speaker[] = []) => { return speakers.reduce<Record<number, Speaker[]>>((acc, speaker) => { acc[speaker.speakerid] ??= []; acc[speaker.speakerid]!.push(speaker); return acc; }, {}); };
const getDeceptiveFillColor = (percent: number) => { if (percent >= 60) return "hsl(0, 80%, 60%)"; // Red if (percent >= 40) return "hsl(30, 85%, 55%)"; // Orange if (percent >= 10) return "hsl(45, 90%, 60%)"; // Yellow return "hsl(140, 50%, 45%)"; // Green };
const getDeceptionLevel = (percent: number) => { if (percent >= 60) return "High"; if (percent >= 40) return "Moderate"; if (percent >= 10) return "Low"; return "None"; };
const getDeceptionColor = (percent: number) => { if (percent >= 60) return "text-red-400"; if (percent >= 40) return "text-orange-400"; if (percent >= 10) return "text-yellow-400"; return "text-green-400"; };
export default function SpeakerDeception({ speakers = [], }: SpeakerDeceptionProps) { const [isClient, setIsClient] = useState(false); const [selectedSpeakerId, setSelectedSpeakerId] = useState<string>("");
const groupedSpeakers = groupStatementsBySpeaker(speakers); const speakerIds = Object.keys(groupedSpeakers) .map(Number) .sort((a, b) => a - b);
useEffect(() => { setIsClient(true); }, []);
useEffect(() => { if (speakerIds.length && !selectedSpeakerId) { setSelectedSpeakerId(speakerIds[0]?.toString() ?? ""); } }, [speakerIds, selectedSpeakerId]);
const selectedStatements = groupedSpeakers[parseInt(selectedSpeakerId)] ?? []; const deceptionPercentage = calculateDeceptionPercentage(selectedStatements);
const pieData = [ { name: "Deceptive", value: deceptionPercentage, fill: getDeceptiveFillColor(deceptionPercentage), }, { name: "Non-deceptive", value: 100 - deceptionPercentage, fill: "hsl(215, 25%, 27%)", }, ];
return (
<Card className="border border-slate-700 bg-slate-800/50 shadow-lg backdrop-blur-sm">
<CardHeader>
<CardTitle className="text-center text-lg text-white">
Average Deception
</CardTitle>
</CardHeader>
<CardContent className="flex flex-col items-center">
{speakerIds.length > 0 && (
<div className="mb-4 flex w-full justify-center">
<Select
value={selectedSpeakerId}
onValueChange={setSelectedSpeakerId}
>
<SelectTrigger className="w-[180px] bg-purple-700/80 text-white hover:bg-purple-700">
<SelectValue placeholder="Select speaker">
{selectedSpeakerId
? Speaker ${selectedSpeakerId}
: "Select speaker"}
</SelectValue>
</SelectTrigger>
<SelectContent className="z-50 border-slate-700 bg-slate-800 text-white">
{speakerIds.map((id) => (
<SelectItem
key={speaker-${id}
}
value={id.toString()}
className="text-white focus:bg-purple-700/30 focus:text-white"
>
Speaker {id}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{isClient && selectedSpeakerId && (
<div className="flex justify-center">
<PieChart
width={250}
height={200}
key={`pie-${selectedSpeakerId}-${deceptionPercentage}`}
>
<Pie
data={pieData}
dataKey="value"
nameKey="name"
cx="50%"
cy="50%"
innerRadius={50}
outerRadius={70}
paddingAngle={5}
label={({ value }) => `${value}%`}
>
{pieData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.fill} />
))}
</Pie>
<Tooltip
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
formatter={(value) => `${value}%`}
contentStyle={{
backgroundColor: "#1e293b",
borderColor: "#475569",
borderRadius: "0.5rem",
}}
itemStyle={{ color: "#f8fafc" }}
/>
</PieChart>
</div>
)}
<div className="mt-4 text-center text-sm text-slate-300">
{selectedSpeakerId ? (
<>
<span
className={`font-semibold ${getDeceptionColor(deceptionPercentage)}`}
>
{deceptionPercentage}%
</span>{" "}
of Speaker {selectedSpeakerId}'s statements were deceptive.
<div className="mt-2 text-xs">
Deception Level:{" "}
<span
className={`font-semibold ${getDeceptionColor(deceptionPercentage)}`}
>
{getDeceptionLevel(deceptionPercentage)}
</span>
</div>
</>
) : (
<span>Select a speaker to view deception data</span>
)}
</div>
</CardContent>
</Card>
); }
I've successfully added the SpeakerDeception component above the transcript table. Here are the key modifications I made:
The component now:
Awesome, can you modify the pie chart/data a little bit such that speaker one has one very likely statement and 1 likely in the pie chart?