# Project export: CourtVision

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: VR 3D Replay Sports Tool using Gaussian Splatting Video
- Devpost: https://devpost.com/software/courtvision-gtui7w
- GitHub: https://github.com/aurelisajuan/ai2025.git
- Team: 4 GitHub contributor(s) — AureliaSindhu (34 commits), albertusjuan (19 commits), bill zhang (8 commits), Aurelisa Juan (7 commits)

## Devpost submission (written by the team)

### Overview

3D AI Playback for Athletes, Trainers & Analysts — Built with Gaussian Splatting 🚨 The Problem Athletes watch film. But the camera lies. It’s flat. It’s fixed. It misses the details that actually matter. You can’t see your shoulder angle. Your foot placement. The hesitation before the shot. You know something felt off—but you can’t pinpoint what. 💡 Our Solution: CourtVision CourtVision turns standard game footage into a fully interactive 3D replay using Gaussian Splatting. You don’t just watch the play — you step back into it. Then you ask the system what could’ve gone better. “What if I stepped left before the screen?” “Was my posture clean on that jumper?” “Why did I get beat on that cut?” You can now see the difference — not just imagine it. 🧠 What It Does 🧍 Step Into Your Play Converts 2D game clips into a time-accurate 3D Gaussian splat Lets you rotate, zoom, and slow down your own movements View from first-person, third-person, or anywhere in between 🎙️ Ask the CourtBot Voice-controlled breakdowns of mechanics and movement Ask questions like: “Was my back straight on that drive?” “Was I off-balance on that shot?” Ask questions like: “Was my back straight on that drive?” “Was I off-balance on that shot?” It doesn’t just answer — it justifies: It doesn’t just answer — it justifies: “Weight on back foot + early jump = short release” 🛠️ How We Built It Video Gaussians – Reconstruct 3D scenes from 2D footage, with time-consistent splats Multimodal LLM – Interprets your posture, intent, and motion frame by frame Vapi Voice API – Enables natural, real-time Q&A about form, decision-making, and body control 🎯 Use Cases 🧠 Skill Review – Study what you did, not what the camera captured 🏋️ Posture & Form – Catch rounded backs, bent knees, off-center landings 📈 Gameplay Decisions – Identify better movement options (“Should’ve cut baseline”) 🎓 Trainer Feedback – Remote coaching with shared 3D walkthroughs 📊 Performance Gains 🔮 What’s Next 🧑‍🤝‍🧑 Shared VR film sessions with teammates 📊 Automated body tracking and stat overlays 🗣️ Trainer/athlete voice conversations mid-review 🧠 "What if" simulation tools to test alternate decisions 💬 Designed for Athletes Desktop-friendly, VR-ready, and voice-first No buttons. No menus. Just talk to the play. It listens. It shows. It explains. With CourtVision—you can. The film room isn’t flat anymore. It’s intelligent. It’s immersive. It talks back.

## README (from the GitHub repository)

# [Berkeley AI Hack 2025](#)
> 3D AI Replay for Coaches, Analysts, and Refs

<div align="center">
    **Built with**

    *Video Gaussians · Retell AI · OpenAI Multimodal · ElevenLabs V3*
</div>

## Detected evidence (automated analysis)

Indexed codebase: 1376 recognized source files, 16005 KB.
- C (language) — detected in the code
- C++ (language) — detected in the code
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 1577)

```
.DS_Store
.gitignore
.gitmodules
backend/.gitignore
backend/app.py
backend/experiment/test_gemini.py
backend/experiment/test_groq.py
backend/experiment/test.ipynb
backend/gem.py
backend/llm.py
backend/prompts.py
backend/requirements.txt
backend/vapi.py
client/.gitignore
client/app/api/llm/route.ts
client/app/demo/page.tsx
client/app/globals.css
client/app/layout.tsx
client/app/page.tsx
client/app/vapi/page.tsx
client/components.json
client/components/4K4DPlayer.tsx
client/components/analysis-panel.tsx
client/components/assistants.tsx
client/components/ChatWidget.tsx
client/components/DemoNavbar.tsx
client/components/helper.ts
client/components/navbar.tsx
client/components/player.css
client/components/TimeSlider.tsx
client/components/ui/badge.tsx
client/components/ui/button.tsx
client/components/ui/card.tsx
client/components/ui/sheet.tsx
client/components/ui/slider.tsx
client/components/ui/tabs.tsx
client/components/ui/tooltip.tsx
client/components/VideoPlayer.tsx
client/components/voice-assistant.tsx
client/components/voiceWid.tsx
client/eslint.config.mjs
client/lib/utils.ts
client/next.config.ts
client/package.json
client/postcss.config.mjs
client/README.md
client/setup-vapi.js
client/tailwind.config.ts
client/tsconfig.json
configs/.gitkeep
custom_dataset_readme.md
gsplat-mps/.clang-format
gsplat-mps/.clangd_template
gsplat-mps/.github/workflows/building.yml
gsplat-mps/.github/workflows/core_tests.yml
gsplat-mps/.github/workflows/cuda/Linux-env.sh
gsplat-mps/.github/workflows/cuda/Linux.sh
gsplat-mps/.github/workflows/cuda/Windows-env.sh
gsplat-mps/.github/workflows/cuda/Windows.sh
gsplat-mps/.github/workflows/doc.yml
gsplat-mps/.github/workflows/publish.yml
gsplat-mps/.gitignore
gsplat-mps/.gitmodules
gsplat-mps/CITATION.cff
gsplat-mps/docs/DEV.md
gsplat-mps/docs/Makefile
gsplat-mps/docs/requirements.txt
gsplat-mps/docs/source/_templates/sidebar/brand.html
gsplat-mps/docs/source/apis/proj.rst
gsplat-mps/docs/source/apis/rast.rst
gsplat-mps/docs/source/apis/utils.rst
gsplat-mps/docs/source/conf.py
gsplat-mps/docs/source/conventions/cuda_conventions.rst
gsplat-mps/docs/source/conventions/data_conventions.rst
gsplat-mps/docs/source/examples/simple_trainer.rst
gsplat-mps/docs/source/index.rst
gsplat-mps/docs/source/references.bib
gsplat-mps/docs/source/tests/tests.rst
gsplat-mps/examples/requirements.txt
gsplat-mps/examples/simple_trainer.py
gsplat-mps/examples/test_rasterize.py
gsplat-mps/gsplat/__init__.py
gsplat-mps/gsplat/_torch_impl.py
gsplat-mps/gsplat/cuda/__init__.py
gsplat-mps/gsplat/cuda/_backend.py
gsplat-mps/gsplat/cuda/csrc/backward.cu
gsplat-mps/gsplat/cuda/csrc/backward.cuh
gsplat-mps/gsplat/cuda/csrc/bindings.cu
gsplat-mps/gsplat/cuda/csrc/bindings.h
gsplat-mps/gsplat/cuda/csrc/CMakeLists.txt
gsplat-mps/gsplat/cuda/csrc/config.h
gsplat-mps/gsplat/cuda/csrc/ext.cpp
gsplat-mps/gsplat/cuda/csrc/forward.cu
gsplat-mps/gsplat/cuda/csrc/forward.cuh
gsplat-mps/gsplat/cuda/csrc/helpers.cuh
gsplat-mps/gsplat/cuda/csrc/sh.cuh
gsplat-mps/gsplat/cuda/csrc/third_party/glm/.appveyor.yml
gsplat-mps/gsplat/cuda/csrc/third_party/glm/.github/workflows/make_light_release.yml
gsplat-mps/gsplat/cuda/csrc/third_party/glm/.gitignore
gsplat-mps/gsplat/cuda/csrc/third_party/glm/.travis.yml
gsplat-mps/gsplat/cuda/csrc/third_party/glm/cmake/cmake_uninstall.cmake.in
gsplat-mps/gsplat/cuda/csrc/third_party/glm/CMakeLists.txt
gsplat-mps/gsplat/cuda/csrc/third_party/glm/copying.txt
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00001_source.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00002_source.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00003_source.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00004_source.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00005_source.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00006_source.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00007_source.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00007.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00008_source.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00008.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00009_source.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00009.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00010_source.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00010.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00011_source.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00011.html
gsplat-mps/gsplat/cuda/csrc/third_party/glm/doc/api/a00012_source.html
[1457 more files omitted for size]
```

### Dependencies

- client/package.json: @eslint/eslintrc@^3, @radix-ui/react-dialog@^1.1.14, @radix-ui/react-slider@^1.3.5, @radix-ui/react-slot@^1.2.3, @radix-ui/react-tabs@^1.1.12, @radix-ui/react-tooltip@^1.2.7, @react-three/drei@^10.3.0, @react-three/fiber@^9.1.2, @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, @vapi-ai/server-sdk@^0.9.1, @vapi-ai/web@^2.3.6, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9, eslint-config-next@15.3.4, lucide-react@^0.522.0, next@15.3.4, pako@^2.1.0, react@^19.0.0, react-dom@^19.0.0, tailwind-merge@^3.3.1, tailwindcss@^4.1.10, tw-animate-css@^1.3.4, typescript@^5
- gsplat-mps/docs/requirements.txt: furo, sphinx, sphinx-copybutton, sphinx-design, sphinxcontrib-bibtex
- gsplat-mps/examples/requirements.txt: numpy, Pillow, tyro

### Recent commits (newest first)

- merge
- my changes
- Backend vapi works
- fix: vapi ...
- Merge remote-tracking branch 'refs/remotes/origin/main'
- Merge pull request #2 from aurelisajuan/vapi-frontend
- fix: vapi calls
- Add two reproducable documentation
- video frames
- Merge remote-tracking branch 'refs/remotes/origin/main'
- WS Viewer works
- fix: agent is now listening but ended after a response
- feat: llm setup for vapi
- style: line
- test: groq
- style: landing page v2
- Merge branch 'vapi-frontend' of https://github.com/aurelisajuan/ai2025 into vapi-frontend
- test: gemini
- style: navbar
- style: landing page

## Key source files (fetched from GitHub, selected and truncated for size)

### pretrained_dataset_readme.md

```markdown
# 4 K4D Web Viewer – End-to-End Setup Guide

> **Goal:** Render any 4 K4D scene on a GPU box and stream it to a browser
> (or `<iframe>`).
> **Test scene:** `sport2` (NHR dataset).
> **Outcome:** Local network viewer with orbit, zoom, play/pause, timeline.

---

## 0 · System prerequisites

| Component  | Notes                                              |
| ---------- | -------------------------------------------------- |
| **GPU**    | NVIDIA RTX-20xx/30xx/40xx or similar (≥ 8 GB)      |
| **OS**     | Linux, WSL 2, or Windows with CUDA 11.8+           |
| **Python** | 3.9 – 3.11 (Conda recommended)                     |
| **Ports**  | TCP **1024** open inbound/outbound                 |
| **Node**   | (only for building React viewer) Node 18+ / npm 9+ |

---

## 1 · Clone & install 4 K4D

```bash
git clone https://github.com/zju3dv/4K4D.git
cd 4K4D
conda env create -f environment.yml   # creates “easyvolcap” env
pip install -e . --no-deps --no-build-isolation   # puts THIS easyvolcap on PYTHONPATH
```

> *Why?*
> `pip install -e .` ensures **this** copy of EasyVolcap (the fork inside
> 4 K4D) is the one Python imports, avoiding version drift with any other
> EasyVolcap you may have.

---

## 2 · Pin `websockets` 10.4

The upstream server thread expects the ≤ 10.x API.

```bash
pip install -U "websockets==10.4"
```

> *Why?*
> Starting with websockets 11 the “implicit event loop” vanished, causing
> `RuntimeError: no running event loop`. Version 10.4 works as-is.

*(If you prefer the latest websockets, patch
`easyvolcap/runners/websocket_server.py` to create its own loop—see
appendix.)*

---

## 3 · Download assets (minimal dataset + pretrained model)

```bash
# Assuming you already have Google-Drive links:
# put them exactly under these paths:
data/
 └─ trained_model/4k4d_sport2/1599.npz
 └─ NHR/sport2/
     ├─ videos_libx265/…          (from .tar.gz)
     └─ optimized/…               (from .tar.gz)
```

#### Extract images & masks once

```bash
python scripts/realtime4dv/extract_images.py --data_root data/NHR/sport2
python scripts/realtime4dv/extract_masks.py  --data_root data/NHR/sport2
```

> *Why?*
> Minimal dataset stores each camera stream as a small H.265 video.
> These two scripts decode PNG/JPEG frames the renderer expects.

---

## 4 · Launch headless renderer + WebSocket server

```bash
QT_QPA_PLATFORM=offscreen \              # no GUI/GL window
evc-gui \
  -c "configs/projects/realtime4dv/rendering/4k4d_sport2.yaml,\
      configs/specs/video.yaml,\
      configs/specs/server.yaml" \
  model_cfg.sampler_cfg.render_gs=True \
  viewer_cfg.headless=True \
  -v
```

* Console prints
  `Listening on 0.0.0.0:1024`
  FPS lines confirm rendering is live.

> *Why these flags?*
>
> * **`video.yaml`** – tells 4 K4D to read pre-extracted images/masks.
> * **`server.yaml`** – starts WebSocket endpoint.
> * **`render_gs=True`** – uses CUDA tile rasterizer (no CUDA-GL interop
>   needed, keeps > 60 fps on WSL).
> * **`headless=True`** – suppress
[truncated — 3871 more characters]
```

### custom_dataset_readme.md

```markdown
# EasyVolcap Custom Dataset Training Guide

---

## 1 · Purpose

These notes turn the one‑off shell session into a repeatable, **why‑focused** playbook for training a new volumetric video sequence with EasyVolcap.
They assume an Ubuntu 22.04 box with an RTX‑class GPU (CUDA 12.2+) and conda installed.

---

## 2 · Directory Conventions

```
project_root/
├── videos/            # raw multi‑view MP4s (00/, 01/, …)
├── data/              # generated dataset assets
│   └── my_project/
│       ├── images/    # all extracted JPG frames
│       ├── intri.yml  # COLMAP output – camera intrinsics
│       └── extri.yml  # COLMAP output – camera extrinsics
├── configs/           # exp & dataset YAMLs
└── scripts/           # helper bash/python
```

*Why?* EasyVolcap hard‑codes several relative paths (e.g., `images/`, `intri.yml`). Matching its expectations avoids custom code.

---

## 3 · Environment Setup

### 3.1 Create an isolated conda env

```bash
conda create -y -n easyvolcap python=3.10
conda activate easyvolcap
```

*Why 3.10?* EasyVolcap (and tiny‑cuda‑nn) are tested up to 3.10; newer versions raise ABI warnings.

### 3.2 Install CUDA‑enabled PyTorch first

```bash
pip install torch torchvision torchaudio \
  --index-url https://download.pytorch.org/whl/cu128  # CUDA 12.8 wheel
```

*Why first?* Some later `pip` installs compile against the active `torch` headers—having the correct CUDA wheel pre‑installed prevents mismatched binaries.

### 3.3 Clone source & system libs

```bash
git clone https://github.com/zju3dv/EasyVolcap.git
cd EasyVolcap
sudo apt update && sudo apt install -y \
  libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev \
  libgl1-mesa-dev libglu1-mesa-dev libx11-dev
```

*Why the X11/OpenGL dev libs?* The WebGL preview & ImGui viewer need them even in headless mode for off‑screen GL contexts.

### 3.4 Python deps

```bash
pip install -v -e .   # pulls requirements.txt & installs as editable package
```

---

## 4 · Method‑Specific Native Extensions

| Library                           | Purpose                                                           | Key build flags                                                             |
| --------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------- |
| **tiny‑cuda‑nn**                  | Instant‑NGP hash‑grid encoder used by several EasyVolcap networks | `cmake . -B build && cmake --build build` then `pip install bindings/torch` |
| **diff‑gaussian‑rasterization**   | 3D Gaussian Splatting renderer                                    | `pip install -e .`                                                          |
| **pytorch3d, open3d, simple‑knn** | Mesh fusion & point‑cloud ops                                     | Installed from GitHub wheels                                                |

*Why compile from source?* Upstream wheels often lag behind CUDA 12 and fail 
[truncated — 4753 more characters]
```

### client/package.json

```
{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@radix-ui/react-dialog": "^1.1.14",
    "@radix-ui/react-slider": "^1.3.5",
    "@radix-ui/react-slot": "^1.2.3",
    "@radix-ui/react-tabs": "^1.1.12",
    "@radix-ui/react-tooltip": "^1.2.7",
    "@react-three/drei": "^10.3.0",
    "@react-three/fiber": "^9.1.2",
    "@vapi-ai/server-sdk": "^0.9.1",
    "@vapi-ai/web": "^2.3.6",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "lucide-react": "^0.522.0",
    "next": "15.3.4",
    "pako": "^2.1.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "tailwind-merge": "^3.3.1"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "15.3.4",
    "tailwindcss": "^4.1.10",
    "tw-animate-css": "^1.3.4",
    "typescript": "^5"
  }
}

```

### backend/requirements.txt

```
��a n n o t a t e d - t y p e s = = 0 . 7 . 0  
 a n y i o = = 4 . 9 . 0  
 b e a u t i f u l s o u p 4 = = 4 . 1 3 . 4  
 c a c h e t o o l s = = 5 . 5 . 2  
 c e r t i f i = = 2 0 2 5 . 6 . 1 5  
 c h a r s e t - n o r m a l i z e r = = 3 . 4 . 2  
 c o l o r a m a = = 0 . 4 . 6  
 e x c e p t i o n g r o u p = = 1 . 3 . 0  
 g o o g l e - a i - g e n e r a t i v e l a n g u a g e = = 0 . 6 . 1 5  
 g o o g l e - a p i - c o r e = = 2 . 2 5 . 1  
 g o o g l e - a p i - p y t h o n - c l i e n t = = 2 . 1 7 3 . 0  
 g o o g l e - a u t h = = 2 . 4 0 . 3  
 g o o g l e - a u t h - h t t p l i b 2 = = 0 . 2 . 0  
 g o o g l e - g e n a i = = 1 . 2 1 . 1  
 g o o g l e - g e n e r a t i v e a i = = 0 . 8 . 5  
 g o o g l e a p i s - c o m m o n - p r o t o s = = 1 . 7 0 . 0  
 g r p c i o = = 1 . 7 3 . 0  
 g r p c i o - s t a t u s = = 1 . 7 1 . 0  
 h 1 1 = = 0 . 1 6 . 0  
 h t t p c o r e = = 1 . 0 . 9  
 h t t p l i b 2 = = 0 . 2 2 . 0  
 h t t p x = = 0 . 2 8 . 1  
 i d n a = = 3 . 1 0  
 p r o t o - p l u s = = 1 . 2 6 . 1  
 p r o t o b u f = = 5 . 2 9 . 5  
 p y a s n 1 = = 0 . 6 . 1  
 p y a s n 1 _ m o d u l e s = = 0 . 4 . 2  
 p y d a n t i c = = 2 . 1 1 . 7  
 p y d a n t i c _ c o r e = = 2 . 3 3 . 2  
 p y p a r s i n g = = 3 . 2 . 3  
 p y t h o n - d o t e n v = = 1 . 1 . 0  
 r e q u e s t s = = 2 . 3 2 . 4  
 r s a = = 4 . 9 . 1  
 s n i f f i o = = 1 . 3 . 1  
 s o u p s i e v e = = 2 . 7  
 t e n a c i t y = = 8 . 5 . 0  
 t q d m = = 4 . 6 7 . 1  
 t y p i n g - i n s p e c t i o n = = 0 . 4 . 1  
 t y p i n g _ e x t e n s i o n s = = 4 . 1 4 . 0  
 u r i t e m p l a t e = = 4 . 2 . 0  
 u r l l i b 3 = = 2 . 5 . 0  
 w e b s o c k e t s = = 1 5 . 0 . 1  
 
```

### gsplat-mps/examples/requirements.txt

```
numpy
tyro
Pillow
```

### gsplat-mps/docs/requirements.txt

```
furo
sphinx
sphinx-copybutton
sphinx-design
sphinxcontrib-bibtex
```

### backend/app.py

```python
import os
import time
import json
from flask import Flask, request, Response, stream_with_context
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

try:
    client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
except KeyError:
    raise ValueError("OPENAI_API_KEY environment variable not set. Please add it to your .env file.")


MODEL_NAME = "gpt-4o-mini"

app = Flask(__name__)

@app.route("/chat/completions", methods=["POST"])
def chat_completions():
    """
    Handles chat completion requests and streams the response back to the client.
    This is the format required for real-time voice agents like Vapi.
    """
    data = request.get_json()
    print("Received data:")
    print(data)
    print("\n")

    def generate():
        try:
            # Create a streaming chat completion request to OpenAI
            stream = client.chat.completions.create(
                model=MODEL_NAME,
                messages=data.get("messages", []),
                stream=True
            )

            for chunk in stream:
                chunk_json = chunk.model_dump_json()
                print(f"Streaming chunk: {chunk_json}") # Optional: log the chunks
                # Format as a Server-Sent Event (SSE)
                yield f"data: {chunk_json}\n\n"

            # After the stream is finished, send the Vapi/OpenAI-compliant [DONE] message
            print("Stream finished. Sending [DONE].")
            yield "data: [DONE]\n\n"

        except Exception as e:
            print(f"An error occurred during stream generation: {e}")
            error_payload = {
                "error": {
                    "message": str(e),
                    "type": "server_error"
                }
            }
            yield f"data: {json.dumps(error_payload)}\n\n"
            yield "data: [DONE]\n\n"

    return Response(stream_with_context(generate()), mimetype="text/event-stream")

if __name__ == "__main__":
    port = int(os.getenv("PORT", 5001))
    # Set debug=False for production environments
    app.run(host="0.0.0.0", port=port, debug=True)
```

### new_backend/main.py

```python
from dotenv import load_dotenv

load_dotenv()  # take environment variables from .env.
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, File, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from typing import Optional
import uvicorn
from openai import OpenAI
import os   
from pydantic import BaseModel
from typing import List
from fastapi import Request
from fastapi.responses import StreamingResponse

app = FastAPI()

origins = ["*"]
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

class Message(BaseModel):
    role: str
    content: str

class ChatCompletionRequest(BaseModel):
    model: str
    messages: List[Message]
    temperature: Optional[float] = 1.0
    top_p: Optional[float] = 1.0
    n: Optional[int] = 1
    stream: Optional[bool] = False
    max_tokens: Optional[int] = None
    presence_penalty: Optional[float] = 0.0
    frequency_penalty: Optional[float] = 0.0


app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

def generate_streaming_response(data):
    for message in data:
        json_data = message.model_dump_json()
        print(f"JSON data: {json_data}")
        """
        {"id":"chatcmpl-BlJpC2pvOi1C2kfmWSlGg2rhYwvju","choices":[{"delta":{"content":"?","function_call":null,"refusal":null,"role":null,"tool_calls":null},"finish_reason":null,"index":0,"logprobs":null}],"created":1750617322,"model":"gpt-4o-mini-2024-07-18","object":"chat.completion.chunk","service_tier":"default","system_fingerprint":"fp_34a54ae93c","usage":null}
        """
        yield f"data: {json_data}\n\n"

@app.get("/")
def read_root():
    return {"Hello": "World"}

@app.post("/chat/completions")
def chat_completion(request: ChatCompletionRequest):
    print(f"Request object: {request}")
    print(f"Request type: {type(request)}")
    
    try:
        request_data = request.model_dump()
        print(f"Request data: {request_data}")
    except Exception as e:
        print(f"Error parsing JSON: {e}")
        return {"error": "Invalid JSON"}
    
    streaming = request_data.get("stream", False)
    print(f"Streaming: {streaming}")

    if streaming:
        print("Streaming")
        chat_completion_stream = client.chat.completions.create(**request_data)
        print(chat_completion_stream)
        return StreamingResponse(generate_streaming_response(chat_completion_stream), media_type="text/event-stream")
    else:
        print("Not Streaming")
        chat_completion = client.chat.completions.create(**request_data)
        print(chat_completion)
        return {"content": chat_completion.choices[0].message.content}

if __name__ == "__main__":
    # uvicorn main:app --reload
    # ws://localhost:8000/ws?client_id=123
    uvicorn.run(app, host="127.0.0.1", port=8000)
```

### client/app/layout.tsx

```typescript
import type React from "react";
import type { Metadata } from "next";
import { Montserrat } from "next/font/google";
import "./globals.css";

const montserrat = Montserrat({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "CourtVision",
  description:
    "Transform 2D sports footage into immersive, interactive 3D experiences for coaches, analysts, and referees.",
  icons: {
    icon: '/favicon.ico'
  },
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head>
        <link rel="icon" href="/favicon.ico" />
      </head>
      <body className={montserrat.className}>{children}</body>
    </html>
  );
}

```

### client/app/page.tsx

```typescript
import Link from "next/link";
import { Button } from "../components/ui/button";
import Navbar from "../components/navbar";
import Image from "next/image";

export default function Home() {
  return (
    <div
      className="relative min-h-screen bg-cover bg-center"
      style={{ backgroundImage: "url('/bg.png')" }}
    >
      <Navbar />

      <main className="relative z-10 flex flex-col items-start justify-center h-screen container mx-auto px-4">
        <h1 className="text-white text-5xl lg:text-8xl font-black uppercase leading-none mt-72">
          The Best 3D
          <br />
          AI Replay
        </h1>

        <Link href="/demo">
          <Button className="mt-6 bg-orange-600 hover:bg-orange-700 text-white px-8 py-6 rounded-lg text-lg cursor-pointer">
            Try Demo
          </Button>
        </Link>

        {/* Orange Dots */}
        <div className="absolute top-[30%] left-[45%] w-40 h-40 z-100">
          <Image
            src="/dots.png"
            alt="Orange graphic"
            width={160}
            height={160}
          />
        </div>

        {/* Floating Cards */}
        <div className="absolute top-1/6 left-16 backdrop-blur-md bg-white/10 border border-white/20 rounded-4xl p-4 flex items-center max-w-xl space-x-6 shadow-lg">
          <Image
            src="/coachbot.png"
            alt="CoachBot"
            width={300}
            height={300}
            className="rounded-lg object-cover"
          />
          <div>
            <h3 className="text-white text-2xl font-semibold">
              Talk to our <span className="text-orange-400">CoachBot</span>
            </h3>
            <p className="text-gray-200 text-base mt-2">
              VAPI-powered voice assistant that can answer natural questions,
              pause, replay, and much more via voice.
            </p>
          </div>
        </div>

        <div className="absolute bottom-25 right-2 backdrop-blur-md bg-white/10 border border-white/20 rounded-4xl p-4 flex items-center max-w-xl space-x-6 shadow-lg">
          <Image
            src="/walkplay.png"
            alt="Walk the Play"
            width={300}
            height={300}
            className="rounded-lg object-cover"
          />
          <div>
            <h3 className="text-white text-2xl font-semibold">
              <span className="text-orange-400">Walk</span> the Play
            </h3>
            <p className="text-gray-200 text-base mt-2">
              With our 3D Gaussian-splat representations of game footage.
              You&apos;ll be able to stand on the court mid-replay.
            </p>
          </div>
        </div>
      </main>

      {/* Optional: add a dark overlay for better text legibility */}
      <div className="absolute inset-0 bg-black opacity-50"></div>
    </div>
  );
}

```

[1316 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]