# Project export: Sage

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: Temp
- Devpost: https://devpost.com/software/sage-z7exsl
- GitHub: https://github.com/wangd14/berkeley-ai-hack
- Video: https://www.youtube.com/embed/8pFup-EjvRo?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — David Wang (43 commits), Abdur Aziz (12 commits)

## Devpost submission (written by the team)

### Inspiration

Test

## README (from the GitHub repository)

# berkeley-ai-hack

## Detected evidence (automated analysis)

Indexed codebase: 40 recognized source files, 244 KB.
- CSS (language) — 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
- SQL (language) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (54 of 54)

```
.gitignore
backend/init/schema.sql
backend/main.py
backend/mistral_math_english_model/.ipynb_checkpoints/tokenizer-checkpoint.json
backend/mistral_math_english_model/adapter_config.json
backend/mistral_math_english_model/adapter_model.safetensors
backend/mistral_math_english_model/chat_template.jinja
backend/mistral_math_english_model/README.md
backend/mistral_math_english_model/special_tokens_map.json
backend/mistral_math_english_model/tokenizer_config.json
backend/mistral_math_english_model/tokenizer.json
frontend/data/courses_shona.json
frontend/data/courses.json
frontend/index.html
frontend/package.json
frontend/postcss.config.js
frontend/README.md
frontend/src/App.tsx
frontend/src/components/FloatingChatButton.css
frontend/src/components/FloatingChatButton.tsx
frontend/src/components/Header.tsx
frontend/src/context/AuthContext.tsx
frontend/src/context/LanguageContext.tsx
frontend/src/index.css
frontend/src/index.tsx
frontend/src/pages/CurriculumPage.tsx
frontend/src/pages/english/AnalysisEssaysPage.tsx
frontend/src/pages/english/PersonalNarrativesPage.tsx
frontend/src/pages/english/ReadingComprehensionPage.tsx
frontend/src/pages/english/SatirePage.tsx
frontend/src/pages/EnglishArtsPage.tsx
frontend/src/pages/HomePage.tsx
frontend/src/pages/Login.tsx
frontend/src/pages/MathematicsPage.tsx
frontend/src/pages/maths/AlgebraPage.tsx
frontend/src/pages/maths/BasicMathPage.tsx
frontend/src/pages/maths/GeometryPage.tsx
frontend/src/pages/maths/PreAlgebraPage.tsx
frontend/src/pages/programming/CPage.tsx
frontend/src/pages/programming/PythonPage.tsx
frontend/src/pages/science/BiologyFundamentalsPage.tsx
frontend/src/pages/science/ChemistryBasicsPage.tsx
frontend/src/pages/science/IntroductionToSciencePage.tsx
frontend/src/pages/science/PhysicsPrinciplesPage.tsx
frontend/src/pages/SciencePage.tsx
frontend/src/pages/Signup.tsx
frontend/src/pages/TeacherDashboard.tsx
frontend/src/pages/TechnologyPage.tsx
frontend/tailwind.config.js
frontend/tsconfig.json
frontend/tsconfig.node.json
frontend/vite.config.ts
LICENSE
README.md
```

### Dependencies

- frontend/package.json: @types/node@^20.11.18, @types/react@^18.3.1, @types/react-dom@^18.3.1, @typescript-eslint/eslint-plugin@^5.54.0, @typescript-eslint/parser@^5.54.0, @vitejs/plugin-react@^4.2.1, autoprefixer@latest, eslint@^8.50.0, eslint-plugin-react-hooks@^4.6.0, eslint-plugin-react-refresh@^0.4.1, jwt-decode@^4.0.0, lucide-react@^0.441.0, postcss@latest, react@^18.3.1, react-dom@^18.3.1, react-router-dom@^7.6.2, recharts@^2.15.4, tailwindcss@3.4.17, typescript@^5.5.4, vite@^5.2.0

### Recent commits (newest first)

- Allow language change
- Dashboard stuff
- Removed graph
- Added update to teacher dashboard
- Added more content
- Possible Shona translation
- Merge pull request #7 from wangd14/david
- Created LLM chat component
- Merge branch 'david'
- Fixed teacher dashboard
- Added routes
- Merge
- Fixed teacher dashboard
- Fixed teacher dashboard
- Merge branch 'main' into abduraziz-feature
- Most of auth creation
- Added Science, Programming, and English classes
- Added Science, Programming, and English classes
- Add english math model
- Changed stat on homepage

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

### frontend/package.json

```
{
  "name": "magic-patterns-vite-template",
  "version": "0.0.1",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "npx vite",
    "build": "npx vite build",
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    "preview": "npx vite preview"
  },
  "dependencies": {
    "jwt-decode": "^4.0.0",
    "lucide-react": "^0.441.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-router-dom": "^7.6.2",
    "recharts": "^2.15.4"
  },
  "devDependencies": {
    "@types/node": "^20.11.18",
    "@types/react": "^18.3.1",
    "@types/react-dom": "^18.3.1",
    "@typescript-eslint/eslint-plugin": "^5.54.0",
    "@typescript-eslint/parser": "^5.54.0",
    "@vitejs/plugin-react": "^4.2.1",
    "autoprefixer": "latest",
    "eslint": "^8.50.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.1",
    "postcss": "latest",
    "tailwindcss": "3.4.17",
    "typescript": "^5.5.4",
    "vite": "^5.2.0"
  }
}

```

### backend/main.py

```python
import os
from flask import Flask, send_from_directory, g, jsonify, request
from flask_cors import CORS
import sqlite3
import hashlib
import secrets
from passlib.context import CryptContext
import jwt
import datetime
from typing import Optional
from collections import defaultdict

DATABASE = 'database.db'

app = Flask(__name__, static_folder="dist", static_url_path="/")
CORS(app)

SECRET_KEY = "your_secret_key_here"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = sqlite3.connect(DATABASE)
    return db

def init_db():
    with app.app_context():
        db = get_db()
        with app.open_resource('init/schema.sql', mode='r') as f:
            db.cursor().executescript(f.read())
        db.commit()

def query_db(query, args=(), one=False):
    cur = get_db().execute(query, args)
    rv = cur.fetchall()
    cur.close()
    return (rv[0] if rv else None) if one else rv

@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

@app.route("/")
def index():
    return app.send_static_file("index.html")

@app.route("/api/teacher-dashboard")
def teacher_dashboard():
    db = get_db()
    # Total students
    total_students = db.execute("SELECT COUNT(*) FROM user").fetchone()[0] - 1
    # Average score (across all interactions with correctness)
    total_correct = db.execute("SELECT SUM(correct_answers) FROM courses_stats").fetchone()[0] or 0
    total_questions = db.execute("SELECT SUM(total_questions) FROM courses_stats").fetchone()[0] or 0
    average_score = int((total_correct / total_questions) * 100) if total_questions > 0 else 0
    # Active subjects (distinct course in courses_stats with activity)
    active_subjects = db.execute("SELECT COUNT(DISTINCT course) FROM courses_stats WHERE total_questions > 0").fetchone()[0]
    # Need attention: students with <60% correctness
    need_attention = db.execute("SELECT COUNT(DISTINCT student_id) FROM (SELECT student_id, SUM(correct_answers)*1.0/SUM(total_questions) as avg_corr FROM courses_stats GROUP BY student_id HAVING avg_corr < 0.6)").fetchone()[0]

    # Topic Difficulty: correctness rate per topic
    topic_difficulty = [
        {"topic": row[0], "correctness": int((row[1] / row[2]) * 100) if row[2] > 0 else 0}
        for row in db.execute("SELECT topic, SUM(correct_answers), SUM(total_questions) FROM courses_stats GROUP BY topic").fetchall()
    ]
    
    # Engagement Heatmap: interactions per topic per day (still from student_interactions)
    heatmap_query = db.execute("SELECT topic_id, strftime('%w', timestamp) as day, COUNT(*) FROM student_interactions GROUP BY topic_id, day")
    heatmap_data = defaultdict(lambda: {"Mon":0,"Tue":0,"Wed":0,"Thu":0,"Fri":0})
    day_map = {"1":"Mon","2":"Tue","3":"Wed","4":"Thu","5":"Fri"}
    for topic, day, count in heatmap_query.fetchall():
        if day in day_map:
            heatmap_data[topic][day_map[day]] = count
    engagement_heatmap = [{"topic": topic, **days} for topic, days in heatmap_data.items()]

    # Activity Timeline: recent activity from courses_stats (most recent completions)
    activityTimeline = []
    activity_query = db.execute('''
        SELECT u.name, c.course, c.subcourse, c.topic, c.timestamp
        FROM courses_stats c
        JOIN user u ON c.student_id = u.id
        ORDER BY c.timestamp DESC
        LIMIT 30
    ''')
    for row in activity_query.fetchall():
        activityTimeline.append({
            "student_name": row[0],
            "course": row[1],
            "subcourse": row[2],
            "topic": row[3],
            "timestamp": row[4]
        })

    # Per-Student Progress (Radar): mastery per topic for each student from courses_stats
    radar_query = db.execute("SELECT topic, SUM(correct_answers)*1.0/SUM(total_questions) as mastery FROM courses_stats GROUP BY topic")
    studentRadar = []
    for row in radar_query.fetchall():
        studentRadar.append({"subject": row[0], "Student": int(row[1]*100) if row[1] is not None else 0, "ClassAvg": int(row[1]*100) if row[1] is not None else 0})

    # Student Profiles: mastery, recent activity, at-risk (still from student_interactions)
    profiles = []
    # Exclude teacher (is_teacher=1)
    student_rows = db.execute("SELECT id, name FROM user WHERE is_teacher=0").fetchall()
    for student_id, name in student_rows:
        mastery_row = db.execute("SELECT SUM(correct_answers)*1.0/SUM(total_questions) FROM courses_stats WHERE student_id = ?", (student_id,)).fetchone()
        mastery = int(mastery_row[0]*100) if mastery_row and mastery_row[0] is not None else 0
        recent_row = db.execute("SELECT question_type, timestamp FROM student_interactions WHERE student_id = ? ORDER BY timestamp DESC LIMIT 1", (student_id,)).fetchone()
        recent = recent_row[0] if recent_row else "-"
        at_risk = mastery < 60
        profiles.append({"name": name, "mastery": mastery, "recent": recent, "atRisk": at_risk})

    return jsonify({
        "total_students": total_students,
        "average_score": average_score,
        "active_subjects": active_subjects,
        "need_attention": need_attention,
        "topic_difficulty": topic_difficulty,
        "engagement_heatmap": engagement_heatmap,
        "activityTimeline": activityTimeline,
        "studentRadar": studentRadar,
        "profiles": profiles
    })

# In-memory token store (for demo only)
token_store = {}

def hash_password(password):
    return hashlib.sha256(password.encode('utf-8')).hexdigest()

def verify_password(password, hashed):
    return hash_password(password) == hashed

@app.route('/api/signup', methods=['POST'])
def signup():
    data = request.form or request.json
    username = data.get("username")
    password = data.get("password")
    name = data.get("name", "Student")
    i
[truncated — 6319 more characters]
```

### frontend/src/index.tsx

```typescript
import './index.css';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { App } from './App';
import { AuthProvider } from './context/AuthContext';

const container = document.getElementById('root');
const root = createRoot(container!);
root.render(
  <AuthProvider>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </AuthProvider>
);
```

### frontend/src/App.tsx

```typescript
import React, { useEffect } from 'react';
import { Routes, Route, Navigate, useLocation, useNavigate } from 'react-router-dom';
import { AuthProvider, useAuth } from './context/AuthContext';
import { jwtDecode } from 'jwt-decode';

import { HomePage } from './pages/HomePage';
import { CurriculumPage } from './pages/CurriculumPage';
import { TeacherDashboard } from './pages/TeacherDashboard';
import { Login } from './pages/Login';
import { Signup } from './pages/Signup';
import { MathematicsPage } from './pages/MathematicsPage';
import { SciencePage } from './pages/SciencePage';
import { EnglishArtsPage } from './pages/EnglishArtsPage';
import { TechnologyPage } from './pages/TechnologyPage';
import { AlgebraPage } from './pages/maths/AlgebraPage';
import { PreAlgebraPage } from './pages/maths/PreAlgebraPage';
import { BasicMathPage } from './pages/maths/BasicMathPage';
import { LanguageProvider } from './context/LanguageContext';
import {GeometryPage} from './pages/maths/GeometryPage';
import CPage from './pages/programming/CPage';
import PythonPage from './pages/programming/PythonPage';
import IntroductionToSciencePage from './pages/science/IntroductionToSciencePage';
import BiologyFundamentalsPage from './pages/science/BiologyFundamentalsPage';
import ChemistryBasicsPage from './pages/science/ChemistryBasicsPage';
import PhysicsPrinciplesPage from './pages/science/PhysicsPrinciplesPage';
import { ReadingComprehensionPage } from './pages/english/ReadingComprehensionPage';
import { PersonalNarrativesPage } from './pages/english/PersonalNarrativesPage';
import { SatirePage } from './pages/english/SatirePage';
import { AnalysisEssaysPage } from './pages/english/AnalysisEssaysPage';

export function App() {
  const { token, isAuthenticated } = useAuth();
  const location = useLocation();
  const navigate = useNavigate();

  useEffect(() => {
    if (isAuthenticated && (location.pathname === '/' || location.pathname === '/login' || location.pathname === '/signup')) {
      let isTeacher = false;
      if (token) {
        try {
          const decoded: any = jwtDecode(token);
          isTeacher = !!decoded.is_teacher;
        } catch {}
      }
      if (isTeacher) {
        navigate('/teacher-dashboard', { replace: true });
      } else {
        navigate('/curriculum', { replace: true });
      }
    }
  }, [isAuthenticated, location.pathname, token, navigate]);

  return (
    <LanguageProvider>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/login" element={<Login />} />
        <Route path="/signup" element={<Signup />} />
        <Route path="/curriculum" element={<CurriculumPage />} />
        <Route path="/mathematics" element={<MathematicsPage />} />
        <Route path="/mathematics/algebra" element={<AlgebraPage />} />
        <Route path="/teacher-dashboard" element={<TeacherDashboard />} />
        <Route path="/science" element={<SciencePage />} />
        <Route path="/english-arts" element={<EnglishArtsPage />} />
        <Route path="/technology" element={<TechnologyPage />} />
        <Route path="/mathematics/pre-algebra" element={<PreAlgebraPage />} />
        <Route path="/mathematics/basic-math" element={<BasicMathPage />} />
        <Route path="/mathematics/geometry" element={<GeometryPage />} />
        <Route path="/programming/cpp" element={<CPage />} />
        <Route path="/programming/python" element={<PythonPage />} />
        <Route path="/science/introduction" element={<IntroductionToSciencePage />} />
        <Route path="/science/biology" element={<BiologyFundamentalsPage />} />
        <Route path="/science/chemistry" element={<ChemistryBasicsPage />} />
        <Route path="/science/physics" element={<PhysicsPrinciplesPage />} />
        <Route path="/english/reading-comprehension" element={<ReadingComprehensionPage />} />
        <Route path="/english/personal-narratives" element={<PersonalNarrativesPage />} />
        <Route path="/english/satire" element={<SatirePage />} />
        <Route path="/english/analysis-essays" element={<AnalysisEssaysPage />} />
      </Routes>
    </LanguageProvider>
  );
}

export default App;
```

### frontend/tailwind.config.js

```javascript
export default {content: [
  './index.html',
  './src/**/*.{js,ts,jsx,tsx}'
],}
```

### frontend/postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

### frontend/vite.config.ts

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

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  build: {
    outDir: '../backend/dist',
    emptyOutDir: true,
  },
})

```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Sage</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/index.tsx"></script>
  </body>
</html>

```

### frontend/src/index.css

```css
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
```

### backend/init/schema.sql

```sql
-- User table
CREATE TABLE IF NOT EXISTS user (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    username TEXT UNIQUE NOT NULL,
    password TEXT NOT NULL,
    is_teacher INTEGER DEFAULT 0, -- 0 for student, 1 for teacher
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Student-AI interaction log for detailed analytics
CREATE TABLE IF NOT EXISTS student_interactions (
    interaction_id INTEGER PRIMARY KEY AUTOINCREMENT,
    session_id TEXT,
    student_id INTEGER NOT NULL,
    timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
    lesson_id TEXT,
    topic_id TEXT,
    question_id TEXT,
    question_type TEXT, -- e.g., 'socratic', 'exercise', 'explanation_request'
    student_input TEXT,
    ai_response TEXT,
    hint_requested BOOLEAN,
    hint_level INTEGER, -- e.g., 1=mild, 3=direct
    response_correctness TEXT, -- 'correct', 'incorrect', 'partial', 'skipped', 'not_applicable'
    attempts_on_question INTEGER,
    time_to_respond_seconds INTEGER,
    FOREIGN KEY(student_id) REFERENCES user(id)
);

-- Student-AI interaction log for detailed analytics
CREATE TABLE IF NOT EXISTS courses_stats (
    stats_id INTEGER PRIMARY KEY AUTOINCREMENT,
    student_id INTEGER NOT NULL,
    course TEXT NOT NULL,
    subcourse TEXT NOT NULL,
    topic TEXT NOT NULL,
    completed_questions INTEGER DEFAULT 0,
    total_questions INTEGER DEFAULT 0,
    correct_answers INTEGER DEFAULT 0,
    incorrect_answers INTEGER DEFAULT 0,
    timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY(student_id) REFERENCES user(id)
);

-- Indexes for analytics performance
CREATE INDEX IF NOT EXISTS idx_student_interactions_student_id ON student_interactions(student_id);
CREATE INDEX IF NOT EXISTS idx_student_interactions_lesson_id ON student_interactions(lesson_id);
CREATE INDEX IF NOT EXISTS idx_student_interactions_topic_id ON student_interactions(topic_id);
CREATE INDEX IF NOT EXISTS idx_student_interactions_timestamp ON student_interactions(timestamp);
```

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