# Project export: Lexia

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 2026
- Tagline: Adaptive reading support, powered by your eyes.
- Devpost: https://devpost.com/software/lexia-245dih
- GitHub: https://github.com/abirami07/lexia
- Video: https://www.youtube.com/embed/aV6GcXM4HJY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — abirami kumar (1 commits)

## Devpost submission (written by the team)

### Inspiration

As college students, the four of us have worked with elementary and middle school students who struggle with reading comprehension and confidence while studying. One student in particular, an 11-year-old named Noah, left a lasting impression on us. While reading, Noah would often reread the same sentence several times, becoming increasingly frustrated each time. We watched his confidence fade as he second-guessed himself and hesitated to continue, even when he understood more than he thought. Through experiences like Noah's, we realized that many students show signs of dyslexia, reading fatigue, or other learning challenges that often go unnoticed. Current reading tools are largely one size fits all and depend on students recognizing their own struggles and asking for help, a step many young learners are reluctant or unable to take. This inspired us to ask a simple question: what if technology could recognize when a student was struggling and adapt in real time to their needs? We built Lexia to make reading more personalized, supportive, and accessible. By identifying signs of difficulty and providing tailored assistance as students read, we hope to bridge literacy gaps before frustration turns into discouragement and help every learner build the confidence to succeed.

### What it does

Lexia is an adaptive reading comprehension platform that combines webcam-based eye tracking, AI-powered comprehension support, and voice assistance to create a more autonomous reading experience for dyslexic students. The platform detects signals such as regressions, fixation spikes, and line-skipping to identify when a reader may be struggling. When difficulty is detected, Lexia defines words, reads out text, and adds problem words to a sidebar to be later visited. Once a reading session is over, the student can see a progress report displaying variance in words and a word count of problem words. The goal is to support readers without forcing them to stop and search for help themselves.

### How we built it

We built Lexia using React and FastAPI, integrating webcam-based eye tracking, AI-powered comprehension support, text-to-speech functionality, and dynamic content ingestion. We leveraged browser-based eye tracking to analyze reading behavior in real time and connected those signals to adaptive reading interventions. The platform combines multiple technologies to create a seamless experience: eye tracking for behavioral signals, AI-generated reading support and quizzes, voice assistance for pronunciation and comprehension, and automated extraction of reading content from websites and uploaded documents.

### Challenges we ran into

The biggest challenge was eye tracking. Building a system that could reliably estimate where a user was looking using only a webcam required extensive calibration, testing, and iteration. Small changes in lighting, head position, webcam quality, and user movement could significantly affect tracking accuracy. We spent a large portion of the hackathon experimenting with calibration methods, improving stability, and reducing noise in gaze predictions. Finding the right balance between responsiveness and accuracy was one of the most technically demanding parts of the project. Another challenge was deciding when the system should intervene. We wanted Lexia to feel supportive rather than distracted, so we carefully designed the platform to provide assistance only when consistent struggle patterns were detected.

### Accomplishments we're proud of

We're proud that we successfully built a functioning adaptive reading platform that responds to reader behavior in real time. Integrating eye tracking, AI-powered reading support, voice assistance, and content ingestion into a cohesive user experience within a hackathon timeframe was a major accomplishment. Most importantly, we're proud that Lexia tackles a meaningful educational problem that we've personally witnessed through tutoring and mentoring younger students.

### What we learned

The biggest thing we learned was how complex eye tracking is in practice. We gained hands-on experience with gaze estimation, calibration techniques, behavioral signal detection, and the challenges of making webcam-based tracking usable in real-world environments. We also learned the importance of user-centered design in educational technology. Throughout development, we found that even small interface and calibration changes dramatically affected the reading experience. Building Lexia reinforced the idea that effective accessibility tools need to adapt to users rather than forcing users to adapt to the technology.

### What's next

We're excited to continue improving Lexia beyond this prototype. Our next steps include enhancing eye-tracking accuracy, exploring signals like pupil dilation to better detect frustration and reading fatigue, and supporting dedicated IR eye-tracking hardware. We also plan to build native mobile apps, introduce multi-user accounts, and create real-time teacher alerts so educators can identify struggling students earlier. Long term, we hope to deploy Lexia as a browser extension that can support students across the web and conduct formal studies to validate its impact on reading outcomes. Our goal is to make personalized reading support available wherever and whenever students need it.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 39 recognized source files, 2698 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- Anthropic (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (50 of 50)

```
.gitignore
backend/.env.example
backend/config.py
backend/main.py
backend/requirements.txt
backend/routers/__init__.py
backend/routers/claude_router.py
backend/routers/content.py
backend/routers/deepgram_router.py
backend/services/__init__.py
backend/services/browserbase_service.py
backend/services/claude_service.py
backend/services/deepgram_service.py
frontend/index.html
frontend/package.json
frontend/public/mediapipe/face_mesh/face_mesh_solution_packed_assets_loader.js
frontend/public/mediapipe/face_mesh/face_mesh_solution_packed_assets.data
frontend/public/mediapipe/face_mesh/face_mesh_solution_simd_wasm_bin.data
frontend/public/mediapipe/face_mesh/face_mesh_solution_simd_wasm_bin.js
frontend/public/mediapipe/face_mesh/face_mesh_solution_simd_wasm_bin.wasm
frontend/public/mediapipe/face_mesh/face_mesh_solution_wasm_bin.js
frontend/public/mediapipe/face_mesh/face_mesh_solution_wasm_bin.wasm
frontend/public/mediapipe/face_mesh/face_mesh.binarypb
frontend/public/mediapipe/face_mesh/face_mesh.js
frontend/public/mediapipe/face_mesh/index.d.ts
frontend/public/mediapipe/face_mesh/package.json
frontend/public/mediapipe/face_mesh/README.md
frontend/public/webgazer.js
frontend/src/App.css
frontend/src/App.jsx
frontend/src/components/ContentInput.css
frontend/src/components/ContentInput.jsx
frontend/src/components/EyeCalibration.css
frontend/src/components/EyeCalibration.jsx
frontend/src/components/QuizMode.css
frontend/src/components/QuizMode.jsx
frontend/src/components/ReadingPane.css
frontend/src/components/ReadingPane.jsx
frontend/src/components/WordPopup.css
frontend/src/components/WordPopup.jsx
frontend/src/hooks/useDeepgram.js
frontend/src/hooks/useEyeTracking.js
frontend/src/index.css
frontend/src/main.jsx
frontend/src/services/api.js
frontend/src/services/MediaPipeTracker.js
frontend/src/services/WebGazerTracker.js
frontend/vite.config.js
package.json
start.sh
```

### Dependencies

- backend/requirements.txt: fastapi@>=0.111.0, httpx@>=0.27.0, playwright@>=1.44.0, pydantic-settings@>=2.2.0, python-dotenv@>=1.0.0, python-multipart@>=0.0.9, sentry-sdk[fastapi]@>=2.6.0, uvicorn[standard]@>=0.30.0, websockets@>=12.0
- frontend/package.json: @sentry/react@^8.0.0, @vitejs/plugin-react@^4.3.0, axios@^1.7.0, pdfjs-dist@^4.4.0, react@^18.3.0, react-dom@^18.3.0, vite@^5.3.0, webgazer@^3.5.3
- package.json: gh@^2.8.9

### Recent commits (newest first)

- Add gitignore
- Update WebGazer tracker
- Initial commit
- Initial commit

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

### package.json

```
{
  "dependencies": {
    "gh": "^2.8.9"
  }
}

```

### backend/requirements.txt

```
fastapi>=0.111.0
uvicorn[standard]>=0.30.0
python-dotenv>=1.0.0
pydantic-settings>=2.2.0
httpx>=0.27.0
sentry-sdk[fastapi]>=2.6.0
playwright>=1.44.0
python-multipart>=0.0.9
websockets>=12.0

```

### frontend/package.json

```
{
  "name": "readease-frontend",
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@sentry/react": "^8.0.0",
    "axios": "^1.7.0",
    "pdfjs-dist": "^4.4.0",
    "react": "^18.3.0",
    "react-dom": "^18.3.0",
    "webgazer": "^3.5.3"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^4.3.0",
    "vite": "^5.3.0"
  }
}

```

### frontend/public/mediapipe/face_mesh/package.json

```
{
  "name": "@mediapipe/face_mesh",
  "version": "0.4.1633559619",
  "description": "Mediapipe Face Mesh Solution",
  "main": "face_mesh.js",
  "module": "face_mesh.js",
  "jsdelivr": "face_mesh.js",
  "unpkg": "face_mesh.js",
  "types": "index.d.ts",
  "author": "mhays@google.com",
  "license": "Apache-2.0",
  "homepage": "https://google.github.io/mediapipe/solutions/face_mesh",
  "keywords": [ "AR", "ML", "Augmented" ],
  "devDependencies": {
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
  },
  "browser": {
    "node-fetch": false,
    "util": false,
    "crypto": false
  },
  "sideEffects" : []
}

```

### backend/main.py

```python
import sentry_sdk
from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.starlette import StarletteIntegration
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from config import settings
from routers import content, claude_router, deepgram_router

if settings.SENTRY_DSN:
    sentry_sdk.init(
        dsn=settings.SENTRY_DSN,
        integrations=[StarletteIntegration(), FastApiIntegration()],
        traces_sample_rate=1.0,
        environment="hackathon",
    )

app = FastAPI(title="ReadEase API", version="0.1.0")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(content.router, prefix="/api/content")
app.include_router(claude_router.router, prefix="/api/claude")
app.include_router(deepgram_router.router, prefix="/api/deepgram")


@app.get("/health")
def health():
    return {"status": "ok"}

```

### frontend/src/main.jsx

```javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
import * as Sentry from '@sentry/react'
import App from './App.jsx'
import './index.css'

Sentry.init({
  dsn: 'https://b4c25a86280e84175bd0f0d9019f8e7b@o4511599329542144.ingest.us.sentry.io/4511599365849088',
  tracesSampleRate: 1.0,
  environment: 'hackathon',
})

ReactDOM.createRoot(document.getElementById('root')).render(
  <Sentry.ErrorBoundary fallback={<p style={{padding: 32, fontFamily: 'sans-serif'}}>Something went wrong. Check the console.</p>}>
    <App />
  </Sentry.ErrorBoundary>
)

```

### frontend/src/App.jsx

```javascript
import { useState, useCallback } from 'react'
import ContentInput from './components/ContentInput'
import EyeCalibration from './components/EyeCalibration'
import ReadingPane from './components/ReadingPane'
import QuizMode from './components/QuizMode'
import './App.css'

export default function App() {
  const [mode, setMode] = useState('input') // 'input' | 'calibrating' | 'reading' | 'quiz'
  const [content, setContent] = useState({ title: '', text: '', sourceUrl: null })
  const [sessionStats, setSessionStats] = useState({
    struggleWords: [],
    startTime: null,
    wordsRead: 0,
  })

  const handleContentLoaded = useCallback((data) => {
    setContent(data)
    setMode('calibrating')
  }, [])

  const handleCalibrationDone = useCallback(() => {
    setSessionStats(s => ({ ...s, startTime: Date.now() }))
    setMode('reading')
  }, [])

  const handleSessionEnd = useCallback((stats) => {
    setSessionStats(s => ({ ...s, ...stats }))
    setMode('quiz')
  }, [])

  const handleRestart = useCallback(() => {
    setContent({ title: '', text: '', sourceUrl: null })
    setSessionStats({ struggleWords: [], startTime: null, wordsRead: 0 })
    setMode('input')
  }, [])

  const handleSkipCalibration = useCallback(() => {
    setSessionStats(s => ({ ...s, startTime: Date.now() }))
    setMode('reading')
  }, [])

  const handleRecalibrate = useCallback(() => {
    setMode('calibrating')
  }, [])

  return (
    <div className="app">
      <header className="app-header">
        <div className="app-logo">
          <span className="logo-text">Lexia</span>
        </div>
        {mode === 'reading' && (
          <span className="header-title">{content.title}</span>
        )}
        <div className="header-actions">
          {mode === 'reading' && (
            <button className="btn btn-ghost btn-sm" onClick={handleRecalibrate}>
              Recalibrate
            </button>
          )}
          {mode !== 'input' && (
            <button className="btn btn-ghost btn-sm" onClick={handleRestart}>
              ← New Text
            </button>
          )}
        </div>
      </header>

      <main className="app-main">
        {mode === 'input' && (
          <ContentInput onContentLoaded={handleContentLoaded} />
        )}
        {mode === 'calibrating' && (
          <EyeCalibration
            onDone={handleCalibrationDone}
            onSkip={handleSkipCalibration}
          />
        )}
        {mode === 'reading' && (
          <ReadingPane
            content={content}
            onSessionEnd={handleSessionEnd}
          />
        )}
        {mode === 'quiz' && (
          <QuizMode
            content={content}
            sessionStats={sessionStats}
            onRestart={handleRestart}
          />
        )}
      </main>
    </div>
  )
}

```

### start.sh

```shell
#!/bin/bash
set -e

ROOT="$(cd "$(dirname "$0")" && pwd)"
BACKEND="$ROOT/backend"
FRONTEND="$ROOT/frontend"

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  ReadEase — Starting dev servers"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

# ── Backend ──────────────────────────────────────────
echo ""
echo "📦 Setting up Python backend..."
cd "$BACKEND"

if [ ! -d ".venv" ]; then
  python3 -m venv .venv
fi
source .venv/bin/activate

pip install -q -r requirements.txt

# Install Playwright Chromium (needed for Browserbase)
python3 -m playwright install chromium 2>/dev/null || true

echo "▶ Starting FastAPI on http://localhost:8000"
uvicorn main:app --reload --port 8000 2>&1 &
BACKEND_PID=$!

# ── Frontend ─────────────────────────────────────────
echo ""
echo "📦 Installing frontend dependencies..."
cd "$FRONTEND"
npm install --silent

echo "▶ Starting React on http://localhost:5173"
npm run dev &
FRONTEND_PID=$!

echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  ✓ ReadEase running!"
echo "  Frontend → http://localhost:5173"
echo "  Backend  → http://localhost:8000"
echo ""
echo "  ⚠  Add ANTHROPIC_API_KEY to backend/.env"
echo "     for word definitions and quiz generation"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

# Wait and clean up on Ctrl-C
trap "kill $BACKEND_PID $FRONTEND_PID 2>/dev/null; exit 0" INT TERM
wait

```

### frontend/vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    proxy: {
      '/api': 'http://localhost:8000',
    },
  },
  optimizeDeps: {
    exclude: ['pdfjs-dist'],
  },
})

```

### backend/config.py

```python
from pydantic_settings import BaseSettings
from functools import lru_cache


class Settings(BaseSettings):
    ANTHROPIC_API_KEY: str = ""
    DEEPGRAM_API_KEY: str = ""
    BROWSERBASE_API_KEY: str = ""
    BROWSERBASE_PROJECT_ID: str = ""
    SENTRY_DSN: str = ""

    model_config = {"env_file": ".env"}


@lru_cache
def get_settings() -> Settings:
    return Settings()


settings = get_settings()

```

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