back to portfolio
case study

LexAI

AI Courtroom Simulator

A full-stack web application where law students and curious users argue real cases against AI-powered courtroom personas — with real-time multiplayer, contradiction detection, and scored session replay.

What I built

LexAI is a courtroom simulator where users take the role of an attorney and argue cases against AI-powered judges, witnesses, and opposing counsel. I built it as both a learning tool for law students and a showcase of what's possible when you combine real-time infrastructure with modern AI.

The core experience: you select a case, the AI assigns roles, and you conduct cross-examination. The AI witness answers from a character brief — and will contradict earlier statements if pressed hard enough. At the end, you get a score with replay.

Features shipped

Core courtroom simulator with multiple AI personas (judge, witness, opposing counsel)
Witness cross-examination with contradiction detection
Professor dashboard with student analytics and session tracking
Real-time multiplayer via Supabase Realtime broadcast channels
Session replay with score visualization and timeline
Shareable session cards with dynamic OG images (Vercel ImageResponse)
Loading skeletons and error boundaries for production UX
Authentication and protected routes via Clerk

Tech decisions

Next.js 15

App Router, server components, and Vercel OG for dynamic social cards

TypeScript

Strict typing across all AI response schemas and Supabase tables

OpenAI GPT-4o mini

Fast, cheap inference for real-time AI personas — 10x cheaper than GPT-4o with comparable quality for this use case

Supabase

Postgres database + Realtime for multiplayer sync + Row Level Security for session isolation

Clerk

Auth with minimal setup — protected routes and user sessions out of the box

Framer Motion

Smooth courtroom transitions, score animations, and loading states

Tailwind CSS

Fast, consistent dark UI styling with utility classes

Hardest problems

Real-time multiplayer state sync

PROBLEM

Multiple users in the same courtroom session needed to see the same evidence, witness answers, and scores without reloading.

SOLUTION

Used Supabase Realtime's broadcast channel for live state updates. Each session has an isolated channel keyed by session ID. State mutations are broadcast to all connected clients and merged client-side using React's useReducer.

Contradiction detection in AI responses

PROBLEM

GPT-4o mini needed to detect when a witness contradicted an earlier statement — but the model has no memory between requests.

SOLUTION

Maintained a rolling transcript of all witness statements in the session. Each new AI prompt includes the full transcript and explicit instructions to check for contradictions. The model returns a structured JSON response with a contradiction flag and explanation.

Dynamic OG images for session sharing

PROBLEM

Shareable session cards needed unique images with the user's score, case name, and outcome — generated at request time.

SOLUTION

Used Next.js Vercel OG (ImageResponse) to generate dynamic PNG images server-side. Each card is generated from a React component rendered to an image, with the session data passed as query parameters.

What I'd improve

Streaming AI responses.Currently the AI persona's answer appears all at once. Using OpenAI's streaming API would make responses feel more like a real witness answering under pressure.

Caching session transcripts. Every new question re-sends the full conversation history. Redis or Supabase Edge Functions could cache this server-side and reduce token costs significantly.

Accessibility.The courtroom UI needs a proper keyboard navigation pass and ARIA roles for the live question/answer flow. I'd add this before recommending it to screen reader users.

What I learned

Building LexAI taught me that AI reliability is a UX problem as much as a technical one. You can't trust the model to always return valid JSON — you need to validate, retry, and degrade gracefully. It also showed me how much architecture matters: the decision to isolate each multiplayer session by ID made everything else easier. If I were starting over, I'd spend twice as long on the data model before writing a single line of UI.

back to portfolio