# Project export: IntelliAgent

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: IntelliAgent is a multimodal multi-agent AI framework that helps students study for their AP courses. Chat, practice, quiz, and discuss with it to learn everything you need to know for your AP exam!
- Devpost: https://devpost.com/software/intelliagent-iktjh5
- GitHub: https://github.com/usrnme20/IntelliAgent
- Demo: https://v0-ai-tutoring-website.vercel.app/
- Video: https://www.youtube.com/embed/Vwcc6YjK40U?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Bhavya Paleru (13 commits), Aishwari Sirur (1 commits)

## Devpost submission (written by the team)

### Inspiration

In 2022, 1.2 million students in the US took AP exams. While the usage of one-on-one tutoring has soared in high-income communities since 1997, other students are left behind and have to make do with juggling several website and textbooks. During high school, we also took several AP exams and had to alternate studying from several different resources such as our textbooks, practice exams, online audio practice and many more. Our tool consolidates all of these into 1 singular website to help the AP student focus on studying for the exam.

### What it does

Our core features include an AI tutor chatbot Custom unit quizzes Custom remedial quizzes on topics you struggled with Expert full-length practice exams Progress tracker on each unit and topic For language courses, listening and speaking practice as well as spoken conversation with AI

### How we built it

We created Letta AI agents that were trained on AP course Course and Exam Description PDFs as well as practice free-response questions and their scoring guidelines. Then, we integrated VAPI so that students taking AP Spanish and French can practice conversation with the AI and learn how to answer questions for the AP exam. Finally, we used Vercel to build and deploy our user interface which is clean and readable in light and dark mode.

### Challenges we ran into

Of the many challenges we ran into, one of the most prominent challenges we faced was trying to integrate the AI agent with the front-end. Since this was our first time using them, there was a steep learning curve. Similarly, integrating VAPI into the final product was difficult for the same reasons. Additionally, v0 which built the frontend, hard-coded alot of the quizzes and practice exams, so we needed to rewrite many of the files so that the final product could work.

### Accomplishments we're proud of

Successfully using AI agents Integrating Voice-Powered AI Agents for Spanish and French Deploying the final product on Vercel Creating a product that would have helped us a lot back in high school and that we hope can help many more AP students in the future

### What we learned

how to use AI agents with Letta and Voice-Powered AI agents with VAPI how to debug a large project how to manage a multimodal multi-agent framework

### What's next

adding more AP courses making an enterprise option for small to large-scale businesses for onboarding making a teacher login so that they can upload their notes, so their students can practice

## README (from the GitHub repository)

# AP Tutor - AI-Powered Learning Platform

A comprehensive AI tutoring platform for AP courses using Letta's stateful agents. Each course has a dedicated AI tutor that remembers student interactions and adapts to individual learning needs.

## Features

- **6 AP Courses**: Biology, US History, Spanish, French, Chemistry, Computer Science A
- **Personalized AI Tutors**: Each course has a specialized Letta agent with deep curriculum knowledge
- **Adaptive Learning**: Agents remember student strengths/weaknesses and provide targeted help
- **Interactive Practice**: Unit quizzes, review sessions, and full-length practice exams
- **Language Learning**: Speaking/listening practice for Spanish and French courses
- **Progress Tracking**: Detailed analytics and progress visualization

## Quick Start

1. **Clone and Install**
   \`\`\`bash
   git clone <repository-url>
   cd ap-tutor-platform
   npm install
   \`\`\`

2. **Get Letta API Key**
   - Sign up at [app.letta.com](https://app.letta.com)
   - Create an API key at [app.letta.com/api-keys](https://app.letta.com/api-keys)

3. **Setup Environment**
   \`\`\`bash
   cp .env.example .env.local
   # Add your LETTA_API_KEY to .env.local
   \`\`\`

4. **Create AI Tutors**
   \`\`\`bash
   npm run setup-agents
   \`\`\`

5. **Start Development Server**
   \`\`\`bash
   npm run dev
   \`\`\`

## Architecture

The platform uses Letta's stateful agents [^1] to create persistent AI tutors that:

- **Remember Everything**: Each agent maintains memory of student interactions
- **Adapt Over Time**: Agents learn student strengths/weaknesses and adjust teaching
- **Provide Expertise**: Each tutor has comprehensive AP curriculum knowledge
- **Track Progress**: Agents store learning patterns in their long-term memory

### Agent Memory Structure

Each tutor agent has specialized memory blocks:
- **Persona**: Teaching style and expertise
- **Course Knowledge**: Complete AP curriculum and exam format
- **Student Progress**: Individual learning patterns and weak areas
- **Human**: Student profile and preferences

## Course Coverage

### AP Biology
- 8 units from Chemistry of Life to Ecology
- Lab analysis and data interpretation
- FRQ practice and exam strategies

### AP US History
- 9 historical periods from 1491-Present
- DBQ and LEQ writing practice
- Primary source analysis

### AP Spanish/French
- 6 thematic units with cultural focus
- Speaking and listening practice as well as AI conversation
- Authentic resource integration

### AP Chemistry
- 9 units covering all major chemistry concepts
- Mathematical problem-solving
- Laboratory skills and analysis

### AP Computer Science A
- 10 units of Java programming
- Object-oriented programming concepts
- Algorithm development and analysis

## Technology Stack

- **Frontend**: Next.js 14, React, TypeScript, Tailwind CSS
- **AI Agents**: Letta Cloud with GPT-4.1 models
- **UI Components**: shadcn/ui with Radix primitives
- **Styling**: Tailwind CSS with custom design system

## Development

### Adding New Features

The platform is designed for easy extension:

1. **New Courses**: Add course configs to `scripts/setup-agents.js`
2. **Enhanced Quizzes**: Extend `QuizSection` component with new question types
3. **Progress Analytics**: Add new metrics to `ProgressDashboard`
4. **Language Features**: Expand `LanguageSection` with more interactive elements

### API Integration

The chat interface uses the Vercel AI SDK with Letta provider [^1]:

\`\`\`typescript
import { lettaCloud } from '@letta-ai/vercel-ai-sdk-provider'
import { streamText } from 'ai'

const result = streamText({
  model: lettaCloud(agentId),
  prompt: userMessage, // Only send new message, not history
})
\`\`\`

## Deployment

1. **Environment Variables**: Ensure all `LETTA_AGENT_*` variables are set
2. **Build**: `npm run build`
3. **Deploy**: Compatible with Vercel, Netlify, or any Node.js hosting

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test with your Letta agents
5. Submit a pull request

## License

MIT License - see LICENSE file for details.


## Detected evidence (automated analysis)

Indexed codebase: 83 recognized source files, 544 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — 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
- Node.js (technology) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (89 of 89)

```
.gitignore
app/api/agents/configure/route.ts
app/api/agents/list/route.ts
app/api/agents/setup/route.ts
app/api/chat/route.ts
app/api/generate-practice-test/route.ts
app/api/generate-quiz/route.ts
app/api/get-course-info/route.ts
app/api/submit-quiz/route.ts
app/api/vapi-config/route.ts
app/api/vapi-token/route.ts
app/courses/[courseId]/page.tsx
app/globals.css
app/layout.tsx
app/page.tsx
app/setup/page.tsx
components.json
components/agent-configurator.tsx
components/chat-interface.tsx
components/course-layout.tsx
components/language-section.tsx
components/practice-test-section.tsx
components/progress-dashboard.tsx
components/quiz-loading.tsx
components/quiz-section.tsx
components/theme-provider.tsx
components/ui/accordion.tsx
components/ui/alert-dialog.tsx
components/ui/alert.tsx
components/ui/aspect-ratio.tsx
components/ui/avatar.tsx
components/ui/badge.tsx
components/ui/breadcrumb.tsx
components/ui/button.tsx
components/ui/calendar.tsx
components/ui/card.tsx
components/ui/carousel.tsx
components/ui/chart.tsx
components/ui/checkbox.tsx
components/ui/collapsible.tsx
components/ui/command.tsx
components/ui/context-menu.tsx
components/ui/dialog.tsx
components/ui/drawer.tsx
components/ui/dropdown-menu.tsx
components/ui/form.tsx
components/ui/hover-card.tsx
components/ui/input-otp.tsx
components/ui/input.tsx
components/ui/label.tsx
components/ui/menubar.tsx
components/ui/navigation-menu.tsx
components/ui/pagination.tsx
components/ui/popover.tsx
components/ui/progress.tsx
components/ui/radio-group.tsx
components/ui/resizable.tsx
components/ui/scroll-area.tsx
components/ui/select.tsx
components/ui/separator.tsx
components/ui/sheet.tsx
components/ui/sidebar.tsx
components/ui/skeleton.tsx
components/ui/slider.tsx
components/ui/sonner.tsx
components/ui/switch.tsx
components/ui/table.tsx
components/ui/tabs.tsx
components/ui/textarea.tsx
components/ui/toast.tsx
components/ui/toaster.tsx
components/ui/toggle-group.tsx
components/ui/toggle.tsx
components/ui/tooltip.tsx
components/ui/use-mobile.tsx
components/ui/use-toast.ts
components/vapi-widget.tsx
hooks/use-mobile.tsx
hooks/use-toast.ts
lib/utils.ts
next.config.mjs
package.json
postcss.config.mjs
README.md
scripts/setup-agents.js
styles/globals.css
tailwind.config.ts
tsconfig.json
vercel.json
```

### Dependencies

- package.json: @letta-ai/letta-client@latest, @radix-ui/react-accordion@latest, @radix-ui/react-alert-dialog@latest, @radix-ui/react-aspect-ratio@latest, @radix-ui/react-avatar@latest, @radix-ui/react-checkbox@latest, @radix-ui/react-collapsible@latest, @radix-ui/react-context-menu@latest, @radix-ui/react-dialog@latest, @radix-ui/react-dropdown-menu@latest, @radix-ui/react-hover-card@latest, @radix-ui/react-label@latest, @radix-ui/react-menubar@latest, @radix-ui/react-navigation-menu@latest, @radix-ui/react-popover@latest, @radix-ui/react-progress@latest, @radix-ui/react-radio-group@latest, @radix-ui/react-scroll-area@latest, @radix-ui/react-select@latest, @radix-ui/react-separator@latest, @radix-ui/react-slider@latest, @radix-ui/react-slot@latest, @radix-ui/react-switch@latest, @radix-ui/react-tabs@latest, @radix-ui/react-toast@latest, @radix-ui/react-toggle@latest, @radix-ui/react-toggle-group@latest, @radix-ui/react-tooltip@latest, @types/node@^22, @types/react@^18, @types/react-dom@^18, @vapi-ai/web@latest, autoprefixer@^10.0.1, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@latest, dotenv@^16.5.0, embla-carousel-react@latest, eslint@^8, eslint-config-next@14.0.0, fs@latest, input-otp@latest, lucide-react@^0.454.0, next@14.2.16, next-themes@latest, path@latest, postcss@^8.5, react@^18, react-day-picker@latest, react-dom@^18, react-hook-form@latest, react-resizable-panels@latest, recharts@latest, sonner@latest, tailwind-merge@^2.5.5, tailwindcss@^3.3.0, tailwindcss-animate@^1.0.7, typescript@^5, vaul@latest, zod@latest

### Recent commits (newest first)

- Update README.md
- feat: update homepage title to "IntelliAgent"
- feat: sync changes from test1 in AIAPTutor project
- Update route.ts for Vapi config
- fix: move Vapi API key to server side
- feat: enhance course content and add Vapi integration
- Fix quiz generation API: update Letta client call and response parsing
- Fix quiz submission API: update Letta client call and response parsing
- Fix Response.json TypeError and Letta response content type
- Fix Letta client API call and response parsing
- Fix Letta client API call to agents.messages.create and response parsing
- Configure Vercel with vercel.json to force pnpm install and build
- Add missing dependencies for Vercel build
- feat: use existing Letta agents instead of creating new

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

### package.json

```
{
  "name": "ap-tutor-platform",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "build": "next build",
    "dev": "next dev",
    "lint": "next lint",
    "start": "next start"
  },
  "dependencies": {
    "@letta-ai/letta-client": "latest",
    "@radix-ui/react-accordion": "latest",
    "@radix-ui/react-alert-dialog": "latest",
    "@radix-ui/react-aspect-ratio": "latest",
    "@radix-ui/react-avatar": "latest",
    "@radix-ui/react-checkbox": "latest",
    "@radix-ui/react-collapsible": "latest",
    "@radix-ui/react-context-menu": "latest",
    "@radix-ui/react-dialog": "latest",
    "@radix-ui/react-dropdown-menu": "latest",
    "@radix-ui/react-hover-card": "latest",
    "@radix-ui/react-label": "latest",
    "@radix-ui/react-menubar": "latest",
    "@radix-ui/react-navigation-menu": "latest",
    "@radix-ui/react-popover": "latest",
    "@radix-ui/react-progress": "latest",
    "@radix-ui/react-radio-group": "latest",
    "@radix-ui/react-scroll-area": "latest",
    "@radix-ui/react-select": "latest",
    "@radix-ui/react-separator": "latest",
    "@radix-ui/react-slider": "latest",
    "@radix-ui/react-slot": "latest",
    "@radix-ui/react-switch": "latest",
    "@radix-ui/react-tabs": "latest",
    "@radix-ui/react-toast": "latest",
    "@radix-ui/react-toggle": "latest",
    "@radix-ui/react-toggle-group": "latest",
    "@radix-ui/react-tooltip": "latest",
    "@vapi-ai/web": "latest",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "latest",
    "dotenv": "^16.5.0",
    "embla-carousel-react": "latest",
    "fs": "latest",
    "input-otp": "latest",
    "lucide-react": "^0.454.0",
    "next": "14.2.16",
    "next-themes": "latest",
    "path": "latest",
    "react": "^18",
    "react-day-picker": "latest",
    "react-dom": "^18",
    "react-hook-form": "latest",
    "react-resizable-panels": "latest",
    "recharts": "latest",
    "sonner": "latest",
    "tailwind-merge": "^2.5.5",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "latest",
    "zod": "latest"
  },
  "devDependencies": {
    "@types/node": "^22",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "eslint": "^8",
    "eslint-config-next": "14.0.0",
    "postcss": "^8.5",
    "tailwindcss": "^3.3.0",
    "typescript": "^5"
  }
}
```

### app/layout.tsx

```typescript
import type React from "react"
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import "./globals.css"
import { ThemeProvider } from "@/components/theme-provider"
import { Toaster } from "@/components/ui/toaster"

const inter = Inter({ subsets: ["latin"] })

export const metadata: Metadata = {
  title: "IntelliAgent - AI-Powered Learning Platform",
  description: "Master AP courses with personalized AI tutors",
    generator: 'v0.dev'
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body className={inter.className}>
        <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
          {children}
          <Toaster />
        </ThemeProvider>
      </body>
    </html>
  )
}

```

### app/page.tsx

```typescript
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import Link from "next/link"
import { BookOpen, Brain, Target, TrendingUp, Settings } from "lucide-react"

const courses = [
  {
    id: "ap-biology",
    name: "AP Biology",
    description: "Master cellular processes, genetics, evolution, and ecology",
    color: "bg-green-500",
    units: 8,
    difficulty: "Advanced",
  },
  {
    id: "ap-us-history",
    name: "AP US History",
    description: "Explore American history from pre-Columbian to modern era",
    color: "bg-blue-500",
    units: 9,
    difficulty: "Advanced",
  },
  {
    id: "ap-spanish",
    name: "AP Spanish",
    description: "Develop fluency in Spanish language and culture",
    color: "bg-red-500",
    units: 6,
    difficulty: "Advanced",
  },
  {
    id: "ap-french",
    name: "AP French",
    description: "Master French language and francophone cultures",
    color: "bg-purple-500",
    units: 6,
    difficulty: "Advanced",
  },
  {
    id: "ap-chemistry",
    name: "AP Chemistry",
    description: "Understand chemical reactions, bonding, and thermodynamics",
    color: "bg-orange-500",
    units: 9,
    difficulty: "Advanced",
  },
  {
    id: "ap-csa",
    name: "AP Computer Science A",
    description: "Learn Java programming and computer science fundamentals",
    color: "bg-cyan-500",
    units: 10,
    difficulty: "Advanced",
  },
]

const features = [
  {
    icon: Brain,
    title: "AI-Powered Tutors",
    description: "Personalized learning with dedicated AI tutors for each subject",
  },
  {
    icon: Target,
    title: "Adaptive Learning",
    description: "Focus on your weak areas with targeted practice and review",
  },
  {
    icon: BookOpen,
    title: "Comprehensive Content",
    description: "Complete AP curriculum coverage with practice exams",
  },
  {
    icon: TrendingUp,
    title: "Progress Tracking",
    description: "Monitor your improvement with detailed analytics",
  },
]

export default function HomePage() {
  return (
    <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800">
      {/* Header */}
      <header className="border-b bg-white/80 backdrop-blur-sm dark:bg-gray-900/80">
        <div className="container mx-auto px-4 py-4">
          <div className="flex items-center justify-between">
            <div className="flex items-center space-x-2">
              <Brain className="h-8 w-8 text-blue-600" />
              <h1 className="text-2xl font-bold text-gray-900 dark:text-white">IntelliAgent</h1>
            </div>
            <div className="flex items-center gap-2">
              <Link href="/setup">
                <Button variant="outline">
                  <Settings className="h-4 w-4 mr-2" />
                  Setup Agents
                </Button>
              </Link>
              <Button variant="outline">Sign In</Button>
            </div>
          </div>
        </div>
      </header>

      {/* Hero Section */}
      <section className="py-20">
        <div className="container mx-auto px-4 text-center">
          <h2 className="text-5xl font-bold text-gray-900 dark:text-white mb-6">Master AP Courses with IntelliAgent</h2>
          <p className="text-xl text-gray-600 dark:text-gray-300 mb-8 max-w-3xl mx-auto">
            Get personalized tutoring from AI agents specialized in each AP subject. Track your progress, identify weak
            areas, and ace your exams.
          </p>
          <div className="flex gap-4 justify-center">
            <Button size="lg" className="text-lg px-8 py-3">
              Start Learning Today
            </Button>
            <Link href="/setup">
              <Button size="lg" variant="outline" className="text-lg px-8 py-3">
                <Settings className="h-5 w-5 mr-2" />
                Configure Agents
              </Button>
            </Link>
          </div>
        </div>
      </section>

      {/* Features */}
      <section className="py-16 bg-white dark:bg-gray-900">
        <div className="container mx-auto px-4">
          <h3 className="text-3xl font-bold text-center mb-12 text-gray-900 dark:text-white">
            Why Choose IntelliAgent?
          </h3>
          <div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8">
            {features.map((feature, index) => (
              <Card key={index} className="text-center">
                <CardHeader>
                  <feature.icon className="h-12 w-12 mx-auto text-blue-600 mb-4" />
                  <CardTitle className="text-xl">{feature.title}</CardTitle>
                </CardHeader>
                <CardContent>
                  <p className="text-gray-600 dark:text-gray-300">{feature.description}</p>
                </CardContent>
              </Card>
            ))}
          </div>
        </div>
      </section>

      {/* Courses */}
      <section className="py-16">
        <div className="container mx-auto px-4">
          <h3 className="text-3xl font-bold text-center mb-12 text-gray-900 dark:text-white">Available AP Courses</h3>
          <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
            {courses.map((course) => (
              <Card key={course.id} className="hover:shadow-lg transition-shadow">
                <CardHeader>
                  <div className="flex items-center justify-between">
                    <div className={`w-4 h-4 rounded-full ${course.color}`} />
                    <Badge variant="secondary">{course.difficulty}</Badge>
                  </div>
                  <CardTitle className="text-xl">{course.name}</CardTitle>
                  <CardDescription>{course.description}</CardDescription>
                </CardHeader>
                <CardContent>
                  <div className="flex items-center justify-between mb-4">
                    <span c
[truncated — 391 more characters]
```

### app/setup/page.tsx

```typescript
import { AgentConfigurator } from "@/components/agent-configurator"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import Link from "next/link"
import { ArrowLeft, Settings } from "lucide-react"

export default function SetupPage() {
  return (
    <div className="min-h-screen bg-gray-50 dark:bg-gray-900">
      <header className="border-b bg-white dark:bg-gray-800">
        <div className="container mx-auto px-4 py-4">
          <div className="flex items-center justify-between">
            <div className="flex items-center space-x-4">
              <Link href="/">
                <Button variant="ghost" size="sm">
                  <ArrowLeft className="h-4 w-4 mr-2" />
                  Back to Home
                </Button>
              </Link>
              <div className="flex items-center space-x-2">
                <Settings className="h-6 w-6 text-blue-600" />
                <h1 className="text-2xl font-bold">Agent Setup</h1>
              </div>
            </div>
          </div>
        </div>
      </header>

      <div className="container mx-auto px-4 py-6">
        <div className="max-w-4xl mx-auto space-y-6">
          <Card>
            <CardHeader>
              <CardTitle>Welcome to AP Tutor Setup</CardTitle>
            </CardHeader>
            <CardContent className="space-y-4">
              <p className="text-gray-600 dark:text-gray-300">
                Since you've already created your Letta AI agents and added the AP course data sources, let's configure
                the platform to use your existing agents.
              </p>

              <div className="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg">
                <h3 className="font-medium mb-2">Setup Steps:</h3>
                <ol className="list-decimal list-inside space-y-1 text-sm">
                  <li>Click "Load My Agents" to fetch your existing Letta agents</li>
                  <li>Use "Auto-Configure" to automatically match agents to courses, or manually assign them</li>
                  <li>Copy the generated environment variables to your .env.local file</li>
                  <li>Restart your development server</li>
                  <li>Start using your personalized AP tutors!</li>
                </ol>
              </div>
            </CardContent>
          </Card>

          <AgentConfigurator />
        </div>
      </div>
    </div>
  )
}

```

### app/api/vapi-token/route.ts

```typescript
import { NextResponse } from "next/server"

export async function POST() {
  try {
    const apiKey = process.env.VAPI_API_KEY

    if (!apiKey) {
      return NextResponse.json({ error: "Vapi API key not configured" }, { status: 500 })
    }

    // Return the API key securely from server
    return NextResponse.json({
      token: apiKey,
    })
  } catch (error) {
    console.error("Error getting Vapi token:", error)
    return NextResponse.json({ error: "Failed to get API token" }, { status: 500 })
  }
}

```

### app/api/vapi-config/route.ts

```typescript
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  try {
    const body = await req.json();
    const { courseId } = body;

    console.log("Received Vapi config request for courseId:", courseId); // Add this log

    // Get the assistant ID based on course
    const assistantId =
      courseId === "ap-spanish"
        ? process.env.VAPI_SPANISH_ASSISTANT_ID
        : process.env.VAPI_FRENCH_ASSISTANT_ID;

    if (!assistantId) {
      console.error(
        `Error: Assistant ID not found for courseId: ${courseId}. Check VAPI_SPANISH_ASSISTANT_ID or VAPI_FRENCH_ASSISTANT_ID environment variables.`
      ); // More specific error
      return NextResponse.json(
        { error: "Assistant not configured for this course" },
        { status: 400 }
      );
    }

    if (!process.env.VAPI_API_KEY) {
      console.error("Error: VAPI_API_KEY environment variable is not set."); // New log
      return NextResponse.json(
        { error: "Vapi API key not configured" }, // More specific error message for client
        { status: 400 } // Or 500, depending on how you want to expose this
      );
    }

    console.log("Successfully retrieved assistantId:", assistantId); // Success log

    // Return only the assistant ID - API key stays on server
    return NextResponse.json({
      assistantId,
      hasApiKey: !!process.env.VAPI_API_KEY, // This should always be true if the check above passes
    });
  } catch (error) {
    console.error("Error getting Vapi config:", error);
    return NextResponse.json(
      { error: "Failed to get configuration" },
      { status: 500 }
    );
  }
}

```

### app/courses/[courseId]/page.tsx

```typescript
import { CourseLayout } from "@/components/course-layout"
import { notFound } from "next/navigation"

const courses = {
  "ap-biology": {
    name: "AP Biology",
    description: "Master cellular processes, genetics, evolution, and ecology",
    color: "bg-green-500",
    units: [
      "Chemistry of Life",
      "Cell Structure and Function",
      "Cellular Energetics",
      "Cell Communication and Cell Cycle",
      "Heredity",
      "Gene Expression and Regulation",
      "Natural Selection",
      "Ecology",
    ],
  },
  "ap-us-history": {
    name: "AP US History",
    description: "Explore American history from pre-Columbian to modern era",
    color: "bg-blue-500",
    units: [
      "Period 1: 1491-1607",
      "Period 2: 1607-1754",
      "Period 3: 1754-1800",
      "Period 4: 1800-1848",
      "Period 5: 1844-1877",
      "Period 6: 1865-1898",
      "Period 7: 1890-1945",
      "Period 8: 1945-1980",
      "Period 9: 1980-Present",
    ],
  },
  "ap-spanish": {
    name: "AP Spanish Language and Culture",
    description: "Develop fluency in Spanish language and culture",
    color: "bg-red-500",
    units: [
      "Families and Communities",
      "Science and Technology",
      "Beauty and Aesthetics",
      "Contemporary Life",
      "Global Challenges",
      "Personal and Public Identities",
    ],
  },
  "ap-french": {
    name: "AP French Language and Culture",
    description: "Master French language and francophone cultures",
    color: "bg-purple-500",
    units: [
      "Families and Communities",
      "Science and Technology",
      "Beauty and Aesthetics",
      "Contemporary Life",
      "Global Challenges",
      "Personal and Public Identities",
    ],
  },
  "ap-chemistry": {
    name: "AP Chemistry",
    description: "Understand chemical reactions, bonding, and thermodynamics",
    color: "bg-orange-500",
    units: [
      "Atomic Structure and Properties",
      "Molecular and Ionic Compound Structure",
      "Intermolecular Forces and Properties",
      "Chemical Reactions",
      "Kinetics",
      "Thermodynamics",
      "Equilibrium",
      "Acids and Bases",
      "Applications of Thermodynamics",
    ],
  },
  "ap-csa": {
    name: "AP Computer Science A",
    description: "Learn Java programming and computer science fundamentals",
    color: "bg-cyan-500",
    units: [
      "Primitive Types",
      "Using Objects",
      "Boolean Expressions and if Statements",
      "Iteration",
      "Writing Classes",
      "Array",
      "ArrayList",
      "2D Array",
      "Inheritance",
      "Recursion",
    ],
  },
}

interface CoursePageProps {
  params: {
    courseId: string
  }
}

export default function CoursePage({ params }: CoursePageProps) {
  const course = courses[params.courseId as keyof typeof courses]

  if (!course) {
    notFound()
  }

  return <CourseLayout courseId={params.courseId} course={course} />
}

```

### app/api/submit-quiz/route.ts

```typescript
import { LettaClient } from "@letta-ai/letta-client";
import { NextResponse } from 'next/server'; // <-- Import NextResponse

// Course-specific agent IDs
const COURSE_AGENTS = {
  "ap-biology": process.env.LETTA_AGENT_AP_BIOLOGY,
  "ap-us-history": process.env.LETTA_AGENT_AP_US_HISTORY,
  "ap-spanish": process.env.LETTA_AGENT_AP_SPANISH,
  "ap-french": process.env.LETTA_AGENT_AP_FRENCH,
  "ap-chemistry": process.env.LETTA_AGENT_AP_CHEMISTRY,
  "ap-csa": process.env.LETTA_AGENT_AP_CSA,
}

// Helper to extract text content from a message (copied from chat/route.ts for consistency)
function extractTextContent(content: any): string {
    if (typeof content === 'string') {
        return content;
    }
    // Assuming 'content' could be an array like [{ type: 'text', text: '...' }]
    if (Array.isArray(content)) {
        return content.map(item => (item && typeof item === 'object' && 'text' in item ? item.text : '')).join('');
    }
    return ''; // Return empty string if content is neither string nor array
}

export async function POST(req: Request) {
  try {
    const { courseId, quizResults, quizType, unit } = await req.json()

    const agentId = COURSE_AGENTS[courseId as keyof typeof COURSE_AGENTS]

    // Calculate performance metrics
    const totalQuestions = quizResults.length
    const correctAnswers = quizResults.filter((result: any) => result.correct).length
    const score = Math.round((correctAnswers / totalQuestions) * 100)

    // Identify weak areas
    const incorrectQuestions = quizResults.filter((result: any) => !result.correct)
    const weakUnits = [...new Set(incorrectQuestions.map((q: any) => q.unit))]

    let recommendations = ""

    if (agentId && process.env.LETTA_API_KEY) {
      try {
        // Initialize Letta client
        const client = new LettaClient({
          token: process.env.LETTA_API_KEY,
        })

        // Send performance data to the agent for memory storage
        const prompt = `Student completed a ${quizType} quiz${unit ? ` on ${unit}` : ""}.

Performance Summary:
- Score: ${score}% (${correctAnswers}/${totalQuestions})
- Quiz Type: ${quizType}
- Date: ${new Date().toLocaleDateString()}

${
  incorrectQuestions.length > 0
    ? `
Areas needing improvement:
${incorrectQuestions.map((q: any) => `- ${q.unit}: ${q.question.substring(0, 100)}...`).join("\n")}

Weak units identified: ${weakUnits.join(", ")}
`
    : "Excellent performance! All questions answered correctly."
}

Please update your memory of this student's progress and provide personalized recommendations for improvement. Keep your response concise and actionable.`

        console.log("Sending quiz results to Letta agent with prompt:", prompt); // Added logging

        // CHANGE START: Correct Letta API call
        const response = await client.agents.messages.create(agentId, {
          messages: [{ role: "user", content: prompt }], // Send the prompt as a user message
        });
        // CHANGE END

        console.log("Letta response received for quiz results:", response); // Added logging

        // CHANGE START: Correct response extraction
        if (response.messages && response.messages.length > 0) {
          // Look for the last message that is of type 'assistant_message'
          for (let i = response.messages.length - 1; i >= 0; i--) {
            const message = response.messages[i];
            // Check for 'assistant_message' and ensure it has content
            if (message.messageType === "assistant_message" && message.content) {
              recommendations = extractTextContent(message.content);
              break; // Found the last assistant message, exit loop
            }
            // Also consider tool returns if the agent might use a tool to provide recommendations
            if (message.messageType === "tool_return_message" && message.toolReturn) {
              // You might decide how to incorporate tool returns.
              // For simplicity, here we'll prioritize an assistant message,
              // but if only a tool return is there, we'll use it.
              if (!recommendations) { // Only assign if no assistant message found yet
                  recommendations = extractTextContent(message.toolReturn);
              }
            }
          }
        }
        // CHANGE END

      } catch (error) {
        console.error("Error sending results to Letta agent:", error)
      }
    }

    // Fallback recommendations if Letta is not available or failed
    if (!recommendations) {
      console.log("Using fallback recommendations."); // Added logging
      recommendations =
        score >= 80
          ? "Excellent work! You have a strong understanding of this material. Consider moving on to more challenging topics or taking a practice exam."
          : score >= 60
            ? "Good progress! Focus on reviewing the areas where you missed questions. Consider additional practice in those specific units."
            : "This material needs more review. I recommend going back to study the fundamental concepts and taking additional practice quizzes."
    }

    return NextResponse.json({ // <-- Changed
      score,
      totalQuestions,
      correctAnswers,
      weakUnits,
      recommendations,
      performance: score >= 80 ? "excellent" : score >= 60 ? "good" : "needs_improvement",
    })
  } catch (error) {
    console.error("Quiz submission error:", error)
    return NextResponse.json({ error: "Failed to process quiz results" }, { status: 500 }) // <-- Changed
  }
}

```

### app/api/chat/route.ts

```typescript
import { LettaClient } from "@letta-ai/letta-client";
import { NextResponse } from 'next/server'; // <-- Added for Response.json alternative

// Course-specific agent IDs
const COURSE_AGENTS = {
  "ap-biology": process.env.LETTA_AGENT_AP_BIOLOGY,
  "ap-us-history": process.env.LETTA_AGENT_AP_US_HISTORY,
  "ap-spanish": process.env.LETTA_AGENT_AP_SPANISH,
  "ap-french": process.env.LETTA_AGENT_AP_FRENCH,
  "ap-chemistry": process.env.LETTA_AGENT_AP_CHEMISTRY,
  "ap-csa": process.env.LETTA_AGENT_AP_CSA,
}

// Fallback responses for each course
const getFallbackResponse = (courseId: string, userMessage: string) => {
  const courseResponses = {
    "ap-biology": `As your AP Biology tutor, I'd be happy to help you with "${userMessage}". While I'm setting up my full capabilities, I can tell you that this topic relates to the AP Biology curriculum. Would you like me to explain any specific biological concepts or help you prepare for the AP exam?`,
    "ap-us-history": `As your AP US History tutor, I can help you understand "${userMessage}" in the context of American history. This connects to the broader themes we study in AP History. Would you like me to provide historical context or help with exam preparation?`,
    "ap-spanish": `¡Hola! Como tu tutor de AP Español, puedo ayudarte con "${userMessage}". Esto se relaciona con los temas culturales y lingüísticos que estudiamos. ¿Te gustaría practicar conversación o trabajar en algún tema específico?`,
    "ap-french": `Bonjour! En tant que votre tuteur AP Français, je peux vous aider avec "${userMessage}". Cela se rapporte aux thèmes culturels et linguísticos que nous étudions. Aimeriez-vous pratiquer la conversación ou trabajar en un tema específico?`,
    "ap-chemistry": `As your AP Chemistry tutor, I can help explain "${userMessage}" using chemical principles. This relates to the fundamental concepts we study in AP Chemistry. Would you like me to break down the chemistry concepts or help with problem-solving strategies?`,
    "ap-csa": `As your AP Computer Science A tutor, I can help you understand "${userMessage}" in the context of Java programming and computer science concepts. Would you like me to explain the programming concepts or help with coding practice?`,
  }

  return courseResponses[courseId as keyof typeof courseResponses] || courseResponses["ap-biology"]
}

// Helper to extract text content from a message (handles string or array of TextContent)
function extractTextContent(content: any): string {
    if (typeof content === 'string') {
        return content;
    }
    // Assuming 'content' could be an array like [{ type: 'text', text: '...' }]
    if (Array.isArray(content)) {
        return content.map(item => (item && typeof item === 'object' && 'text' in item ? item.text : '')).join('');
    }
    return ''; // Return empty string if content is neither string nor array
}

export async function POST(req: Request) {
  try {
    const body = await req.json()
    const { messages, courseId } = body

    console.log("Chat API called with:", { courseId, messageCount: messages?.length })

    if (!courseId) {
      console.error("No courseId provided")
      return NextResponse.json({ error: "Course ID is required" }, { status: 400 }) // <-- Changed
    }

    if (!messages || !Array.isArray(messages) || messages.length === 0) {
      console.error("No messages provided")
      return NextResponse.json({ error: "Messages are required" }, { status: 400 }) // <-- Changed
    }

    const agentId = COURSE_AGENTS[courseId as keyof typeof COURSE_AGENTS]
    const latestMessage = messages[messages.length - 1]

    console.log("Agent ID:", agentId)
    // Use the helper function to safely extract content from latestMessage
    const latestMessageContent = extractTextContent(latestMessage?.content);
    console.log("Latest message content:", latestMessageContent);


    // If no agent ID is configured, use fallback
    if (!agentId) {
      console.log("No agent ID found, using fallback response")
      return NextResponse.json({ // <-- Changed
        role: "assistant",
        content: getFallbackResponse(courseId, latestMessageContent || "your question"),
      })
    }

    // If no Letta API key, use fallback
    if (!process.env.LETTA_API_KEY) {
      console.log("No Letta API key found, using fallback response")
      return NextResponse.json({ // <-- Changed
        role: "assistant",
        content: getFallbackResponse(courseId, latestMessageContent || "your question"),
      })
    }

    try {
      // Initialize Letta client
      console.log("Initializing Letta client...")
      const client = new LettaClient({
        token: process.env.LETTA_API_KEY,
      })

      console.log("Sending message to Letta agent...")
      // Send message to Letta agent using the correct API method
      const response = await client.agents.messages.create(agentId, {
        messages: [{ role: "user", content: latestMessageContent }], // <-- Used extracted content
      });

      console.log("Letta response received:", response)

      // Extract the assistant's response
      let assistantResponse = "";
      if (response.messages && response.messages.length > 0) {
        // Look for the last message that is of type 'assistant_message'
        for (let i = response.messages.length - 1; i >= 0; i--) {
          const message = response.messages[i];
          // Check for 'assistant_message' and ensure it has content
          if (message.messageType === "assistant_message" && message.content) {
            assistantResponse = extractTextContent(message.content); // <-- Used helper here too
            break; // Found the last assistant message, exit loop
          }
          // Optionally, handle tool return messages if needed for display
          if (message.messageType === "tool_return_message" && message.toolReturn) {
              assistantResponse = extractTextContent(message.toolReturn); // <-- Used helper here too
              // You 
[truncated — 1323 more characters]
```

### app/api/generate-practice-test/route.ts

```typescript
import { LettaClient } from "@letta-ai/letta-client"
import { z } from "zod"
import { NextResponse } from "next/server"

// Schema for practice test generation
const PracticeTestSchema = z.object({
  questions: z.array(
    z.object({
      id: z.string(),
      question: z.string(),
      type: z.enum(["short-answer", "long-essay", "data-analysis", "synthesis"]),
      points: z.number(),
      timeLimit: z.string(),
      rubric: z.string(),
      unit: z.string(),
      difficulty: z.enum(["easy", "medium", "hard"]),
    }),
  ),
})

// Course-specific agent IDs
const COURSE_AGENTS = {
  "ap-biology": process.env.LETTA_AGENT_AP_BIOLOGY,
  "ap-us-history": process.env.LETTA_AGENT_AP_US_HISTORY,
  "ap-spanish": process.env.LETTA_AGENT_AP_SPANISH,
  "ap-french": process.env.LETTA_AGENT_AP_FRENCH,
  "ap-chemistry": process.env.LETTA_AGENT_AP_CHEMISTRY,
  "ap-csa": process.env.LETTA_AGENT_AP_CSA,
}

// Helper to extract text content from a message
function extractTextContent(content: any): string {
  if (typeof content === "string") {
    return content
  }
  if (Array.isArray(content)) {
    return content.map((item) => (item && typeof item === "object" && "text" in item ? item.text : "")).join("")
  }
  return ""
}

// Enhanced fallback practice test questions for each course
const getFallbackPracticeTest = (courseId: string) => {
  const fallbackTests = {
    "ap-biology": [
      {
        id: "bio_frq_1",
        question:
          "Explain the process of cellular respiration, including the three main stages. Describe how ATP is produced in each stage and explain the role of oxygen in the process. Include specific details about where each stage occurs in the cell.",
        type: "long-essay" as const,
        points: 10,
        timeLimit: "25 minutes",
        rubric:
          "4 points for explaining glycolysis, 3 points for citric acid cycle, 3 points for electron transport chain",
        unit: "Cellular Energetics",
        difficulty: "medium" as const,
      },
      {
        id: "bio_frq_2",
        question:
          "A student conducted an experiment to test the effect of different pH levels on enzyme activity. Analyze the data provided and explain the relationship between pH and enzyme function. Predict what would happen at extreme pH values.",
        type: "data-analysis" as const,
        points: 8,
        timeLimit: "20 minutes",
        rubric: "3 points for data analysis, 3 points for explanation of enzyme structure, 2 points for predictions",
        unit: "Chemistry of Life",
        difficulty: "medium" as const,
      },
    ],
    "ap-us-history": [
      {
        id: "hist_frq_1",
        question:
          "Evaluate the extent to which the American Revolution was a radical departure from previous forms of government. In your response, consider political, social, and economic changes that occurred during and after the Revolution.",
        type: "long-essay" as const,
        points: 15,
        timeLimit: "40 minutes",
        rubric: "Thesis (1 pt), Contextualization (1 pt), Evidence (3 pts), Analysis (4 pts), Reasoning (6 pts)",
        unit: "Period 3: 1754-1800",
        difficulty: "hard" as const,
      },
    ],
    "ap-chemistry": [
      {
        id: "chem_frq_1",
        question:
          "A student performs a titration of a weak acid with a strong base. Given the data below, calculate the Ka of the weak acid and explain the shape of the titration curve. Include discussion of buffer regions and equivalence point.",
        type: "data-analysis" as const,
        points: 12,
        timeLimit: "30 minutes",
        rubric: "4 points for calculations, 4 points for curve explanation, 4 points for chemical reasoning",
        unit: "Acids and Bases",
        difficulty: "hard" as const,
      },
    ],
  }

  return fallbackTests[courseId as keyof typeof fallbackTests] || fallbackTests["ap-biology"]
}

export async function POST(req: Request) {
  let courseId: string | undefined

  try {
    const body = await req.json()
    courseId = body.courseId
    const questionCount = body.questionCount || 3

    console.log("Practice test generation request:", { courseId, questionCount })

    const agentId = COURSE_AGENTS[courseId as keyof typeof COURSE_AGENTS]

    if (!agentId) {
      console.log(`No agent ID found for course: ${courseId}, using fallback`)
      return NextResponse.json({
        questions: getFallbackPracticeTest(courseId),
        metadata: {
          courseId,
          generatedAt: new Date().toISOString(),
          fallback: true,
          error: "Agent not configured",
        },
      })
    }

    if (!process.env.LETTA_API_KEY) {
      console.log("LETTA_API_KEY not found, using fallback")
      return NextResponse.json({
        questions: getFallbackPracticeTest(courseId),
        metadata: {
          courseId,
          generatedAt: new Date().toISOString(),
          fallback: true,
          error: "API key not configured",
        },
      })
    }

    try {
      // Initialize Letta client
      console.log("Initializing Letta client for practice test generation...")
      const client = new LettaClient({
        token: process.env.LETTA_API_KEY,
      })

      // Create the practice test generation prompt
      const courseName = courseId.replace("ap-", "AP ").replace("-", " ").toUpperCase()

      const prompt = `Please generate a ${questionCount}-question AP practice test with free response questions for ${courseName}.

IMPORTANT: Respond with ONLY a valid JSON object in this exact format:
{
  "questions": [
    {
      "id": "unique_id",
      "question": "Detailed free response question text here...",
      "type": "long-essay",
      "points": 10,
      "timeLimit": "25 minutes",
      "rubric": "Detailed scoring rubric",
      "unit": "Unit name",
      "difficulty": "medium"
    }
  ]
}

Requirements:
- Generate authentic AP-style free response questions
- Include a mix of question types: "short-answer"
[truncated — 3655 more characters]
```

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