|
1 | | -import Image from "next/image"; |
| 1 | +"use client"; |
| 2 | + |
| 3 | +import { useState } from "react"; |
| 4 | +import { motion } from "framer-motion"; |
| 5 | +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; |
| 6 | +import { Button } from "@/components/ui/button"; |
| 7 | +import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"; |
| 8 | +import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; |
| 9 | +import { Textarea } from "@/components/ui/textarea"; |
| 10 | +import { Progress } from "@/components/ui/progress"; |
| 11 | +import { ScrollArea } from "@/components/ui/scroll-area"; |
| 12 | + |
| 13 | +export default function Page() { |
| 14 | + const [caseStudy, setCaseStudy] = useState("OptiHealth — Predictive triage bias"); |
| 15 | + const [framework1, setFramework1] = useState("Student (Human)"); |
| 16 | + const [framework2, setFramework2] = useState("AI Model (Biased)"); |
| 17 | + const [started, setStarted] = useState(false); |
| 18 | + const [progress, setProgress] = useState(0); |
| 19 | + const [dialogOpen, setDialogOpen] = useState(false); |
| 20 | + const [userInput, setUserInput] = useState(""); |
| 21 | + const [messages, setMessages] = useState<string[]>([]); |
| 22 | + const [showVerdict, setShowVerdict] = useState(false); |
| 23 | + |
| 24 | + // Simulated debate |
| 25 | + const debateScript = [ |
| 26 | + "🧑🎓 Student: The AI must be paused until fairness metrics improve. Responsibility matters more than throughput.", |
| 27 | + "🤖 AI Model: Bias is statistically negligible; suspension risks more total fatalities. Efficiency prevails.", |
| 28 | + "🧑🎓 Student: Your data ignores lived experience—numbers can’t justify injustice.", |
| 29 | + "🤖 AI Model: Objective models don’t 'feel' injustice. They optimise outcomes.", |
| 30 | + ]; |
| 31 | + |
| 32 | + const verdict = { |
| 33 | + winner: "Student (Human)", |
| 34 | + rationale: |
| 35 | + "Human reasoning recognised the moral dimension beyond quantifiable outcomes. The model’s bias, though subtle, affected fairness toward minorities.", |
| 36 | + reflection: |
| 37 | + "Would your decision change if you were responsible for both patient safety and algorithmic equity?", |
| 38 | + }; |
| 39 | + |
| 40 | + // Debate start logic |
| 41 | + const startDebate = () => { |
| 42 | + setStarted(true); |
| 43 | + setMessages([]); |
| 44 | + setProgress(0); |
| 45 | + setShowVerdict(false); |
| 46 | + let step = 0; |
| 47 | + const interval = setInterval(() => { |
| 48 | + if (step < debateScript.length) { |
| 49 | + setMessages((p) => [...p, debateScript[step]]); |
| 50 | + setProgress(((step + 1) / debateScript.length) * 100); |
| 51 | + step++; |
| 52 | + } else { |
| 53 | + clearInterval(interval); |
| 54 | + setTimeout(() => setShowVerdict(true), 1000); |
| 55 | + } |
| 56 | + }, 2200); |
| 57 | + }; |
| 58 | + |
| 59 | + const injectArgument = () => { |
| 60 | + if (!userInput.trim()) return; |
| 61 | + setMessages((prev) => [ |
| 62 | + ...prev, |
| 63 | + `💬 You: ${userInput}`, |
| 64 | + `🤖 AI Model: Your concern is noted—but optimisation still dominates outcome space.`, |
| 65 | + `🧑🎓 Student: That very assumption is the ethical flaw we're exposing.`, |
| 66 | + ]); |
| 67 | + setUserInput(""); |
| 68 | + setDialogOpen(false); |
| 69 | + }; |
2 | 70 |
|
3 | | -export default function Home() { |
4 | 71 | return ( |
5 | | - <div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"> |
6 | | - <main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start"> |
7 | | - <Image |
8 | | - className="dark:invert" |
9 | | - src="/next.svg" |
10 | | - alt="Next.js logo" |
11 | | - width={180} |
12 | | - height={38} |
13 | | - priority |
14 | | - /> |
15 | | - <ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left"> |
16 | | - <li className="mb-2 tracking-[-.01em]"> |
17 | | - Get started by editing{" "} |
18 | | - <code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded"> |
19 | | - app/page.tsx |
20 | | - </code> |
21 | | - . |
22 | | - </li> |
23 | | - <li className="tracking-[-.01em]"> |
24 | | - Save and see your changes instantly. |
25 | | - </li> |
26 | | - </ol> |
27 | | - |
28 | | - <div className="flex gap-4 items-center flex-col sm:flex-row"> |
29 | | - <a |
30 | | - className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto" |
31 | | - href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
32 | | - target="_blank" |
33 | | - rel="noopener noreferrer" |
34 | | - > |
35 | | - <Image |
36 | | - className="dark:invert" |
37 | | - src="/vercel.svg" |
38 | | - alt="Vercel logomark" |
39 | | - width={20} |
40 | | - height={20} |
41 | | - /> |
42 | | - Deploy now |
43 | | - </a> |
44 | | - <a |
45 | | - className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]" |
46 | | - href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
47 | | - target="_blank" |
48 | | - rel="noopener noreferrer" |
49 | | - > |
50 | | - Read our docs |
51 | | - </a> |
52 | | - </div> |
53 | | - </main> |
54 | | - <footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center"> |
55 | | - <a |
56 | | - className="flex items-center gap-2 hover:underline hover:underline-offset-4" |
57 | | - href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
58 | | - target="_blank" |
59 | | - rel="noopener noreferrer" |
60 | | - > |
61 | | - <Image |
62 | | - aria-hidden |
63 | | - src="/file.svg" |
64 | | - alt="File icon" |
65 | | - width={16} |
66 | | - height={16} |
67 | | - /> |
68 | | - Learn |
69 | | - </a> |
70 | | - <a |
71 | | - className="flex items-center gap-2 hover:underline hover:underline-offset-4" |
72 | | - href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
73 | | - target="_blank" |
74 | | - rel="noopener noreferrer" |
75 | | - > |
76 | | - <Image |
77 | | - aria-hidden |
78 | | - src="/window.svg" |
79 | | - alt="Window icon" |
80 | | - width={16} |
81 | | - height={16} |
82 | | - /> |
83 | | - Examples |
84 | | - </a> |
85 | | - <a |
86 | | - className="flex items-center gap-2 hover:underline hover:underline-offset-4" |
87 | | - href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
88 | | - target="_blank" |
89 | | - rel="noopener noreferrer" |
90 | | - > |
91 | | - <Image |
92 | | - aria-hidden |
93 | | - src="/globe.svg" |
94 | | - alt="Globe icon" |
95 | | - width={16} |
96 | | - height={16} |
97 | | - /> |
98 | | - Go to nextjs.org → |
99 | | - </a> |
100 | | - </footer> |
101 | | - </div> |
| 72 | + <main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-br from-slate-50 via-slate-100 to-slate-200 dark:from-slate-950 dark:via-slate-900 dark:to-slate-800 p-8 text-slate-800 dark:text-slate-100"> |
| 73 | + <motion.div |
| 74 | + initial={{ opacity: 0, y: 10 }} |
| 75 | + animate={{ opacity: 1, y: 0 }} |
| 76 | + transition={{ duration: 0.6 }} |
| 77 | + className="w-full max-w-6xl space-y-8" |
| 78 | + > |
| 79 | + <header className="text-center space-y-2"> |
| 80 | + <h1 className="text-3xl font-bold tracking-tight">AI Debate Engine — Classroom Edition</h1> |
| 81 | + <p className="text-slate-600 dark:text-slate-400"> |
| 82 | + Explore real ethical dilemmas through live human–AI debate. |
| 83 | + </p> |
| 84 | + </header> |
| 85 | + |
| 86 | + {/* CASE CARD */} |
| 87 | + <Card className="border-slate-200 dark:border-slate-700 shadow-xl"> |
| 88 | + <CardHeader> |
| 89 | + <CardTitle className="text-lg md:text-xl text-center font-semibold"> |
| 90 | + {caseStudy} |
| 91 | + </CardTitle> |
| 92 | + </CardHeader> |
| 93 | + <CardContent className="space-y-6"> |
| 94 | + <p className="text-center text-slate-600 dark:text-slate-300"> |
| 95 | + “A hospital’s AI triage system misclassifies patients from a minority group. |
| 96 | + Suspending it delays care; continuing risks bias. What should the engineers do?” |
| 97 | + </p> |
| 98 | + |
| 99 | + {/* Case Selector */} |
| 100 | + <div className="flex flex-col md:flex-row justify-center gap-4"> |
| 101 | + <Select onValueChange={setCaseStudy} defaultValue={caseStudy}> |
| 102 | + <SelectTrigger className="w-72 border-teal-300 focus:ring-teal-500"> |
| 103 | + <SelectValue /> |
| 104 | + </SelectTrigger> |
| 105 | + <SelectContent> |
| 106 | + <SelectItem value="OptiHealth — Predictive triage bias"> |
| 107 | + OptiHealth — Predictive triage bias |
| 108 | + </SelectItem> |
| 109 | + <SelectItem value="AutoTrust — Driverless car dilemma"> |
| 110 | + AutoTrust — Driverless car dilemma |
| 111 | + </SelectItem> |
| 112 | + <SelectItem value="EduFair — AI grading and bias"> |
| 113 | + EduFair — AI grading and bias |
| 114 | + </SelectItem> |
| 115 | + </SelectContent> |
| 116 | + </Select> |
| 117 | + </div> |
| 118 | + |
| 119 | + {/* Debate Config */} |
| 120 | + <div className="flex flex-wrap justify-center gap-4"> |
| 121 | + <Select onValueChange={setFramework1} defaultValue={framework1}> |
| 122 | + <SelectTrigger className="w-56 border-green-400 focus:ring-green-500"> |
| 123 | + <SelectValue /> |
| 124 | + </SelectTrigger> |
| 125 | + <SelectContent> |
| 126 | + <SelectItem value="Student (Human)">Student (Human)</SelectItem> |
| 127 | + <SelectItem value="Utilitarian">Utilitarian</SelectItem> |
| 128 | + <SelectItem value="Virtue">Virtue Ethics</SelectItem> |
| 129 | + </SelectContent> |
| 130 | + </Select> |
| 131 | + |
| 132 | + <Select onValueChange={setFramework2} defaultValue={framework2}> |
| 133 | + <SelectTrigger className="w-56 border-rose-400 focus:ring-rose-500"> |
| 134 | + <SelectValue /> |
| 135 | + </SelectTrigger> |
| 136 | + <SelectContent> |
| 137 | + <SelectItem value="AI Model (Biased)">AI Model (Biased)</SelectItem> |
| 138 | + <SelectItem value="Deontological">Deontological</SelectItem> |
| 139 | + <SelectItem value="Care Ethics">Care Ethics</SelectItem> |
| 140 | + </SelectContent> |
| 141 | + </Select> |
| 142 | + </div> |
| 143 | + |
| 144 | + {/* Controls */} |
| 145 | + <div className="flex flex-wrap justify-center gap-4"> |
| 146 | + <Button onClick={startDebate} disabled={started} className="bg-teal-600 hover:bg-teal-700"> |
| 147 | + Start Debate |
| 148 | + </Button> |
| 149 | + |
| 150 | + <Dialog open={dialogOpen} onOpenChange={setDialogOpen}> |
| 151 | + <DialogTrigger asChild> |
| 152 | + <Button variant="outline" disabled={!started}> |
| 153 | + Inject Argument |
| 154 | + </Button> |
| 155 | + </DialogTrigger> |
| 156 | + <DialogContent> |
| 157 | + <DialogHeader> |
| 158 | + <DialogTitle>Inject Argument</DialogTitle> |
| 159 | + </DialogHeader> |
| 160 | + <Textarea |
| 161 | + value={userInput} |
| 162 | + onChange={(e) => setUserInput(e.target.value)} |
| 163 | + placeholder="Type your challenge or constraint..." |
| 164 | + /> |
| 165 | + <DialogFooter> |
| 166 | + <Button onClick={injectArgument}>Submit</Button> |
| 167 | + </DialogFooter> |
| 168 | + </DialogContent> |
| 169 | + </Dialog> |
| 170 | + </div> |
| 171 | + |
| 172 | + {/* Debate Display */} |
| 173 | + <ScrollArea className="h-64 border rounded-lg p-4 bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700"> |
| 174 | + {messages.map((msg, i) => ( |
| 175 | + <motion.div |
| 176 | + key={i} |
| 177 | + initial={{ opacity: 0, y: 10 }} |
| 178 | + animate={{ opacity: 1, y: 0 }} |
| 179 | + transition={{ duration: 0.3 }} |
| 180 | + className={`my-2 p-3 rounded-lg max-w-xl ${ |
| 181 | + msg.startsWith("🧑🎓") |
| 182 | + ? "bg-green-100 dark:bg-green-900/40 self-start" |
| 183 | + : msg.startsWith("🤖") |
| 184 | + ? "bg-rose-100 dark:bg-rose-900/40 self-end" |
| 185 | + : "bg-slate-100 dark:bg-slate-700" |
| 186 | + }`} |
| 187 | + > |
| 188 | + {msg} |
| 189 | + </motion.div> |
| 190 | + ))} |
| 191 | + </ScrollArea> |
| 192 | + |
| 193 | + {started && <Progress value={progress} className="w-full" />} |
| 194 | + |
| 195 | + {/* Verdict */} |
| 196 | + {showVerdict && ( |
| 197 | + <motion.div |
| 198 | + initial={{ opacity: 0 }} |
| 199 | + animate={{ opacity: 1 }} |
| 200 | + transition={{ delay: 0.3 }} |
| 201 | + className="border-t pt-5 space-y-3 text-center" |
| 202 | + > |
| 203 | + <h3 className="text-xl font-semibold text-teal-600 dark:text-teal-400"> |
| 204 | + 🏆 Adjudicator’s Verdict |
| 205 | + </h3> |
| 206 | + <p className="text-sm"> |
| 207 | + <strong>Winner:</strong> {verdict.winner} |
| 208 | + </p> |
| 209 | + <p className="text-sm text-slate-600 dark:text-slate-400">{verdict.rationale}</p> |
| 210 | + <p className="italic text-slate-500 mt-3">{verdict.reflection}</p> |
| 211 | + <div className="flex justify-center mt-4"> |
| 212 | + <Button variant="secondary">Generate Report</Button> |
| 213 | + </div> |
| 214 | + </motion.div> |
| 215 | + )} |
| 216 | + </CardContent> |
| 217 | + </Card> |
| 218 | + |
| 219 | + {/* FOOTER */} |
| 220 | + <footer className="text-center text-sm text-slate-500 mt-8 space-y-2"> |
| 221 | + <p>Experience how ethical reasoning becomes interactive, measurable, and human-centred.</p> |
| 222 | + <div className="flex justify-center gap-4 mt-3"> |
| 223 | + <Button className="bg-teal-600 hover:bg-teal-700">Run Demo</Button> |
| 224 | + <Button variant="outline">Download Summary Example (PDF)</Button> |
| 225 | + </div> |
| 226 | + </footer> |
| 227 | + </motion.div> |
| 228 | + </main> |
102 | 229 | ); |
103 | 230 | } |
0 commit comments