# Project export: LearnActively

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: OpenAI Build Week
- Tagline: LearnActively is an AI tutor that turns every conversation into active learning through interactive explanations, retrieval practice, and quizzes that help you truly understand and remember.
- Devpost: https://devpost.com/software/learnactively
- GitHub: https://github.com/sonyalow-smu611/LearnActively
- Video: https://www.youtube.com/embed/zzGtWu65A1Q?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Sonya low (7 commits), xuenlynn (2 commits), DarrennPoh (1 commits)

## Devpost submission (written by the team)

### Inspiration

We built LearnActively because most learning tools make it too easy to stay passive. A chatbot can explain something, but that does not mean you remember it. We wanted a tool that pushes people to think, practice, retrieve, and correct mistakes while they learn.

### What it does

LearnActively turns any topic into a structured learning workspace. It creates an intro, cheatsheet, concept roadmap, visual explanations, activities, flashcards, and quizzes. Instead of just giving an answer, it guides the learner through the topic and checks understanding along the way.

### How we built it

We built it with Next.js, React, TypeScript, Tailwind, and API routes. The AI layer uses separate agents for planning, explanations, activities, flashcards, quizzes, and feedback. We use Zod for structured outputs, Drizzle with Supabase Postgres for storage, ReactFlow for roadmaps, and Mermaid for diagrams.

### Challenges we ran into

The hardest part was making the experience feel active instead of like a normal chatbot. We also had to coordinate many generated artifacts, stream them into the UI, and keep the data structured enough to render reliably. Balancing speed, quality, and useful practice was a big challenge.

### Accomplishments we're proud of

We are proud that LearnActively feels like a real learning session, not just a wall of text. The roadmap, flashcards, activities, and quiz all work together. We are also proud of the agent-based architecture because it makes the product easier to extend.

### What we learned

We learned that good AI learning tools need more than explanations. They need structure, feedback, and moments where the learner has to do the work. We also learned how important schemas are when turning AI output into a stable interactive product.

### What's next

Next, we want to connect the agents to stronger live model generation, improve personalization, and add spaced repetition. We also want better progress tracking, richer diagrams, file uploads, and deeper feedback so LearnActively can become a long-term learning companion.

## README (from the GitHub repository)

# LearnActively

<img width="806" height="451" alt="image" src="https://github.com/user-attachments/assets/5f33d8e2-b566-4dbf-92ef-8547a26728f9" />
<img width="806" height="449" alt="image" src="https://github.com/user-attachments/assets/20f874a8-0772-4f6f-ab33-eacecfc3a006" />
<img width="796" height="573" alt="image" src="https://github.com/user-attachments/assets/0b408ecc-091b-48e2-bf44-404e0424c5f2" />


LearnActively is an AI-powered active-learning workspace built for the OpenAI hackathon. It turns any topic into a structured learning session with an introduction, visual overview, cheatsheet, concept roadmap, node-by-node explanations, practice activities, flashcards, and a quiz.

The goal is not to make learning passive. LearnActively uses OpenAI to generate material that makes learners retrieve, apply, and correct their understanding.

## Demo Flow

Youtube Demo Video Here: https://youtu.be/zzGtWu65A1Q?si=VA5p5B1owcQPD4Vq 

1. Enter a topic such as `SQL joins`, `how transformers work`, or `photosynthesis`.
2. Choose a learning depth: beginner, intermediate, or advanced.
3. The app creates a learning session and streams generated artifacts into the workspace.
4. Read the intro and cheatsheet while deeper artifacts continue generating.
5. Explore the concept roadmap, open each subtopic, complete activities, review flashcards, and take the quiz.

## How This Project Uses OpenAI

OpenAI is the core generation layer for the product. The app uses the Vercel AI SDK with `@ai-sdk/openai` to call OpenAI models from the server.

### 1. Multi-Agent Learning Orchestration

The backend runs a learning orchestrator in `lib/ai/orchestrator.ts`. For each user topic, it coordinates specialized AI agents:

- `plannerAgent` creates a structured learning plan.
- `introductionAgent` generates the first explanation.
- `visualExplainerAgent` creates an overview visual artifact.
- `cheatsheetAgent` produces a compact reference guide.
- `conceptTreeAgent` builds the prerequisite roadmap.
- `nodeExplanationAgent` explains each roadmap node.
- `activityAgent` creates active-learning exercises.
- `flashcardAgent` creates retrieval-practice cards.
- `quizAgent` creates an assessment aligned to the roadmap.

Each agent is prompted to produce a specific learning artifact instead of a generic chat response.

### 2. Structured Outputs With Zod Validation

OpenAI responses are generated through `generateObject` from the AI SDK in `lib/ai/generate.ts`. Each artifact has a matching Zod schema in `lib/ai/schemas`.

This lets the app render AI output as real UI:

- ReactFlow roadmap nodes and edges
- Mermaid diagrams
- Quiz questions
- Flashcards
- Activity prompts
- Cheatsheet sections
- Stream events

The generated content is validated before being stored or displayed, which makes the product more reliable for a live demo.

### 3. Model Tiers

The app supports separate model settings for different generation jobs:

```env
OPENAI_MODEL_FAST=gpt-4.1-mini
OPENAI_MODEL_REASONING=gpt-5
OPENAI_MODEL_STRUCTURED=gpt-4.1
```

Defaults are defined in `lib/utils/env.ts`. The planner and chatbot use the fast tier, while structured learning artifacts use the structured tier.

### 4. OpenAI-Powered Chat Support

The app also includes a support assistant in `lib/ai/chatbot.ts`, exposed through `POST /api/chat`. It uses OpenAI text generation to help users understand and navigate the LearnActively experience.

### 5. Search-Aware Generation

For current or fast-changing topics, the planner can decide to use Tavily search. Source notes are passed into OpenAI prompts so generated explanations can account for recent information instead of relying only on model knowledge.

For stable topics, the app skips web search and generates directly from the OpenAI model.

### 6. Resilient Fallbacks

If `OPENAI_API_KEY` is missing or a model call fails, the generation wrapper returns deterministic fallback artifacts. This keeps the demo usable locally while preserving the full OpenAI-powered path when credentials are configured.

## Product Features

- Topic-to-session generation
- Beginner, intermediate, and advanced learning depths
- Streaming artifact updates with Server-Sent Events
- Interactive concept roadmap using ReactFlow
- Visual explanations with Mermaid and SVG overview artifacts
- Practice activities for each subtopic
- Flashcards for retrieval practice
- Quiz with feedback and weak-area recommendations
- Supabase/Postgres persistence through Drizzle ORM
- In-memory development fallback when `DATABASE_URL` is not configured

## Tech Stack

- Next.js 15
- React 19
- TypeScript
- Tailwind CSS
- Vercel AI SDK
- OpenAI via `@ai-sdk/openai`
- Zod
- Drizzle ORM
- Supabase Postgres
- ReactFlow
- Mermaid
- KaTeX
- Framer Motion
- Vitest
- Playwright

## Architecture

```txt
User topic
  -> POST /api/learn
  -> learning session is created
  -> GET /api/learn/:sessionId/stream
  -> runLearningOrchestrator()
  -> OpenAI-powered agents generate structured artifacts
  -> artifacts are validated with Zod
  -> artifacts are persisted with Drizzle
  -> Server-Sent Events stream updates to the UI
  -> learner interacts with roadmap, activities, flashcards, and quiz
```

## Key Files

- `lib/ai/generate.ts` - OpenAI model wrapper for structured objects and text.
- `lib/ai/orchestrator.ts` - Coordinates the full learning generation workflow.
- `lib/ai/agents/*` - Specialized artifact-generation agents.
- `lib/ai/schemas/*` - Zod schemas for AI-generated outputs.
- `app/api/learn/[sessionId]/stream/route.ts` - SSE endpoint for streaming generation progress.
- `app/api/chat/route.ts` - OpenAI-powered support chat endpoint.
- `components/workspace/workspace-tabs.tsx` - Main artifact workspace UI.
- `components/roadmap/concept-tree.tsx` - Interactive learning roadmap.
- `db/schema.ts` - Drizzle database schema.

## Environment Variables

Create a `.env.local` file:

```env
OPENAI_API_KEY=your_openai_api_key
DATABASE_URL=your_supabase_postgres_connection_string
TAVILY_API_KEY=your_tavily_key

NEXT_PUBLIC_APP_NAME=LearnActively
NEXT_PUBLIC_APP_URL=http://localhost:3000

OPENAI_MODEL_FAST=gpt-4.1-mini
OPENAI_MODEL_REASONING=gpt-5
OPENAI_MODEL_STRUCTURED=gpt-4.1
```

`OPENAI_API_KEY` is required for the full OpenAI-powered experience. `DATABASE_URL` and `TAVILY_API_KEY` are optional for local demo usage because the app includes development fallbacks.

## Run Locally

```bash
npm install
npm run dev
```

Open:

```txt
http://localhost:3000
```

## Verification

```bash
npm run test
npm run build
```

## Why It Matters

Most AI learning tools answer the question and stop. LearnActively uses OpenAI to transform a topic into a complete active-learning path: plan, explain, visualize, practice, retrieve, assess, and review weak areas.

The OpenAI integration is not just a chatbot layer. It is the generation engine for a structured educational workspace.


## Detected evidence (automated analysis)

Indexed codebase: 89 recognized source files, 286 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Vercel AI SDK (technology) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- PostgreSQL (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 (97 of 97)

```
.env.example
.eslintrc.json
.gitignore
AGENT.md
app/api/chat/route.ts
app/api/chat/summarize/route.ts
app/api/learn/[sessionId]/progress/route.ts
app/api/learn/[sessionId]/quiz/route.ts
app/api/learn/[sessionId]/route.ts
app/api/learn/[sessionId]/stream/route.ts
app/api/learn/archive/route.ts
app/api/learn/route.ts
app/globals.css
app/layout.tsx
app/page.tsx
components.json
components/app-shell/learn-shell.tsx
components/archive/archive-view.tsx
components/chat/chat-panel.tsx
components/chat/chatbot-widget.tsx
components/chat/topic-popup.tsx
components/learning/cheatsheet-card.tsx
components/learning/difficulty-badge.tsx
components/learning/intro-card.tsx
components/learning/mermaid-renderer.tsx
components/learning/status-panel.tsx
components/loading-ui/wandering-eyes.tsx
components/quiz/quiz-runner.tsx
components/roadmap/concept-tree.tsx
components/roadmap/node-detail-panel.tsx
components/ui/answer-box.tsx
components/ui/badge.tsx
components/ui/flipping-card.tsx
components/ui/progress.tsx
components/ui/styled-list.tsx
components/ui/topic-learning-progress.tsx
components/workspace/topic-history-tab.tsx
components/workspace/workspace-tabs.tsx
db/client.ts
db/migrations/0000_quick_wind_dancer.sql
db/migrations/0001_allow_overview_svg_artifacts.sql
db/migrations/meta/_journal.json
db/migrations/meta/0000_snapshot.json
db/repository.ts
db/schema.ts
drizzle.config.ts
lib/ai/agents/activity-agent.ts
lib/ai/agents/cheatsheet-agent.ts
lib/ai/agents/concept-tree-agent.ts
lib/ai/agents/feedback-agent.ts
lib/ai/agents/flashcard-agent.ts
lib/ai/agents/introduction-agent.ts
lib/ai/agents/node-explanation-agent.ts
lib/ai/agents/planner-agent.ts
lib/ai/agents/quiz-agent.ts
lib/ai/agents/visual-explainer-agent.ts
lib/ai/chatbot.ts
lib/ai/generate.ts
lib/ai/orchestrator.ts
lib/ai/prompts.ts
lib/ai/schemas/activity.ts
lib/ai/schemas/api.ts
lib/ai/schemas/cheatsheet.ts
lib/ai/schemas/common.ts
lib/ai/schemas/concept-tree.ts
lib/ai/schemas/flashcards.ts
lib/ai/schemas/introduction.ts
lib/ai/schemas/learning-plan.ts
lib/ai/schemas/node-explanation.ts
lib/ai/schemas/overview-svg.ts
lib/ai/schemas/quiz.ts
lib/ai/schemas/stream-events.ts
lib/ai/search-policy.ts
lib/ai/topic-fallbacks.ts
lib/chat-history.ts
lib/tavily/client.ts
lib/tavily/search.ts
lib/topic-history.ts
lib/utils/cn.ts
lib/utils/env.ts
lib/utils/ids.ts
lib/utils/topic-progress.ts
next-env.d.ts
next.config.ts
package.json
playwright.config.ts
postcss.config.js
PRD.md
project_summary.md
README.md
tailwind.config.ts
tests/cheatsheet-card.test.ts
tests/e2e/learnactively-user-flow.spec.ts
tests/schemas.test.ts
tests/topic-progress.test.ts
tsconfig.json
vitest.config.ts
```

### Dependencies

- package.json: @ai-sdk/openai@^1.3.24, @playwright/test@^1.61.1, @supabase/ssr@^0.12.3, @supabase/supabase-js@^2.110.5, @types/node@^22.10.2, @types/react@^19.0.2, @types/react-dom@^19.0.2, @xyflow/react@^12.8.2, ai@^4.3.19, autoprefixer@^10.4.20, class-variance-authority@^0.7.1, clsx@^2.1.1, drizzle-kit@^0.31.1, drizzle-orm@^0.44.2, eslint@^8.57.1, eslint-config-next@15.3.4, framer-motion@^12.23.0, html2canvas@^1.4.1, jspdf@^4.2.1, katex@^0.16.22, lucide-react@^0.468.0, mermaid@^11.6.0, motion@^12.42.2, next@15.3.4, postcss@^8.5.6, postgres@^3.4.7, react@^19.0.0, react-dom@^19.0.0, tailwind-merge@^2.6.0, tailwindcss@^3.4.17, typescript@^5.7.2, vitest@^2.1.8, zod@^3.25.67

### Recent commits (newest first)

- Initialize README with project overview and instructions
- merging rebase
- Merge pull request #1 from sonyalow-smu611/sonya
- Merge branch 'main' into sonya
- navbar, library
- sonya
- navbar
- progress bar
- flashcards, chatbot,loading
- init commit

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

### project_summary.md

```markdown
Here’s the project architecture and how to run it.

**Big Picture**
LearnActively is a Next.js 15 app with:

- Frontend: topic popup, split-pane workspace, tabs, roadmap, quiz UI.
- Backend: Next.js API routes.
- AI layer: planner + artifact agents + orchestrator.
- Data layer: Drizzle schema targeting Supabase Postgres.
- Validation: Zod schemas shared across backend and frontend-facing data contracts.

Current generation uses deterministic structured fallback agents, so the app works locally even before real OpenAI model calls are added.

**Frontend**
[app/page.tsx](/Users/aceslow/Downloads/personal-vibecoding/openai_hackathon/app/page.tsx)
Entry page. Renders `LearnShell`.

[app/layout.tsx](/Users/aceslow/Downloads/personal-vibecoding/openai_hackathon/app/layout.tsx)
Root HTML shell and metadata.

[app/globals.css](/Users/aceslow/Downloads/personal-vibecoding/openai_hackathon/app/globals.css)
Global Tailwind styles, cream/orange app feel, focus styles, and ReactFlow node styling.

[components/app-shell/learn-shell.tsx](/Users/aceslow/Downloads/personal-vibecoding/openai_hackathon/components/app-shell/learn-shell.tsx)
Main client controller. Handles:
- topic submission
- creating a session via `POST /api/learn`
- opening the SSE stream
- merging streamed artifacts into UI state
- refreshing persisted artifacts after generation
- marking roadmap nodes complete

[components/chat/topic-popup.tsx](/Users/aceslow/Downloads/personal-vibecoding/openai_hackathon/components/chat/topic-popup.tsx)
First interaction popup: topic input, depth selector, examples, start button.

[components/chat/chat-panel.tsx](/Users/aceslow/Downloads/personal-vibecoding/openai_hackathon/components/chat/chat-panel.tsx)
Left pane. Shows topic and generation/status messages.

[components/workspace/workspace-tabs.tsx](/Users/aceslow/Downloads/personal-vibecoding/openai_hackathon/components/workspace/workspace-tabs.tsx)
Right pane tab system. Routes artifacts into:
- Overview
- Roadmap
- Cheatsheet
- Labs
- Flashcards
- Quiz

[components/roadmap/concept-tree.tsx](/Users/aceslow/Downloads/personal-vibecoding/openai_hackathon/components/roadmap/concept-tree.tsx)
ReactFlow roadmap renderer.

[components/roadmap/node-detail-panel.tsx](/Users/aceslow/Downloads/personal-vibecoding/openai_hackathon/components/roadmap/node-detail-panel.tsx)
Selected roadmap node details: explanation, diagram source, activity, completion button.

[components/quiz/quiz-runner.tsx](/Users/aceslow/Downloads/personal-vibecoding/openai_hackathon/components/quiz/quiz-runner.tsx)
One-question-at-a-time quiz UI and submission.

[components/learning/*](/Users/aceslow/Downloads/personal-vibecoding/openai_hackathon/components/learning)
Reusable artifact renderers:
- `intro-card.tsx`
- `cheatsheet-card.tsx`
- `difficulty-badge.tsx`
- `status-panel.tsx`
- `mermaid-renderer.tsx`

**Backend API**
[app/api/learn/route.ts](/Users/aceslow/Downloads/personal-vibecoding/openai_hackathon/app/api/learn/route.ts)
`POST /api/le
[truncated — 4490 more characters]
```

### AGENT.md

```markdown
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.

Tradeoff: These guidelines bias toward caution over speed. For trivial tasks, use judgment.

1. Think Before Coding
Don't assume. Don't hide confusion. Surface tradeoffs.

Before implementing:

State your assumptions explicitly. If uncertain, ask.
If multiple interpretations exist, present them - don't pick silently.
If a simpler approach exists, say so. Push back when warranted.
If something is unclear, stop. Name what's confusing. Ask.
2. Simplicity First
Minimum code that solves the problem. Nothing speculative.

No features beyond what was asked.
No abstractions for single-use code.
No "flexibility" or "configurability" that wasn't requested.
No error handling for impossible scenarios.
If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.

3. Surgical Changes
Touch only what you must. Clean up only your own mess.

When editing existing code:

Don't "improve" adjacent code, comments, or formatting.
Don't refactor things that aren't broken.
Match existing style, even if you'd do it differently.
If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:

Remove imports/variables/functions that YOUR changes made unused.
Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.

4. Goal-Driven Execution
Define success criteria. Loop until verified.

Transform tasks into verifiable goals:

"Add validation" → "Write tests for invalid inputs, then make them pass"
"Fix the bug" → "Write a test that reproduces it, then make it pass"
"Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:

1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.

5. Include comments throughout your code to explain the purpose of each function or variable. This will help others understand your reasoning and make it easier for them to review your code.


These guidelines are working if: fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.

## Project Purpose

This project is **LearnActively**, a Next.js-based active-learning AI workspace hosted on Vercel.

LearnActively turns a user's learning goal into structured learning artifacts:

- Introduction
- Cheatsheet
- Interactive concept tree
- Visual node explanations
- Hands-on activities/labs
- Flashcards
- Quiz with immediate feedback and final weakness summary

The product should not behave like a passive chatbot. It should force active recall, application, and correction so the user remembers what they learn.

## Confirmed Stack

Use:

- Nex
[truncated — 12224 more characters]
```

### package.json

```
{
  "name": "learnactively",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "test": "vitest run",
    "test:e2e": "playwright test",
    "db:generate": "drizzle-kit generate",
    "db:migrate": "drizzle-kit migrate"
  },
  "dependencies": {
    "@ai-sdk/openai": "^1.3.24",
    "@supabase/ssr": "^0.12.3",
    "@supabase/supabase-js": "^2.110.5",
    "@xyflow/react": "^12.8.2",
    "ai": "^4.3.19",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "drizzle-orm": "^0.44.2",
    "framer-motion": "^12.23.0",
    "html2canvas": "^1.4.1",
    "jspdf": "^4.2.1",
    "katex": "^0.16.22",
    "lucide-react": "^0.468.0",
    "mermaid": "^11.6.0",
    "motion": "^12.42.2",
    "next": "15.3.4",
    "postgres": "^3.4.7",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "tailwind-merge": "^2.6.0",
    "zod": "^3.25.67"
  },
  "devDependencies": {
    "@playwright/test": "^1.61.1",
    "@types/node": "^22.10.2",
    "@types/react": "^19.0.2",
    "@types/react-dom": "^19.0.2",
    "autoprefixer": "^10.4.20",
    "drizzle-kit": "^0.31.1",
    "eslint": "^8.57.1",
    "eslint-config-next": "15.3.4",
    "postcss": "^8.5.6",
    "tailwindcss": "^3.4.17",
    "typescript": "^5.7.2",
    "vitest": "^2.1.8"
  }
}

```

### app/page.tsx

```typescript
import { LearnShell } from "@/components/app-shell/learn-shell";

export default function Home() {
  return <LearnShell />;
}

```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Fraunces, Inter } from "next/font/google";
import "./globals.css";

export const metadata: Metadata = {
  title: "LearnActively",
  description: "Active-learning AI workspace",
};

const inter = Inter({
  subsets: ["latin"],
  variable: "--font-inter",
  display: "swap",
});

const fraunces = Fraunces({
  subsets: ["latin"],
  variable: "--font-fraunces",
  display: "swap",
});

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en" className={`${inter.variable} ${fraunces.variable}`}>
      <body>{children}</body>
    </html>
  );
}

```

### app/api/learn/route.ts

```typescript
import { NextResponse } from "next/server";
import { createLearningSession } from "@/db/repository";
import { createLearningSessionRequestSchema } from "@/lib/ai/schemas/api";

export async function POST(request: Request) {
  try {
    const parsed = createLearningSessionRequestSchema.safeParse(await request.json());
    if (!parsed.success) {
      return NextResponse.json({ error: "Invalid learning session request", issues: parsed.error.flatten() }, { status: 400 });
    }

    const session = await createLearningSession(parsed.data);
    return NextResponse.json({ sessionId: session.id, status: session.status });
  } catch (error) {
    const message = error instanceof Error ? error.message : "Unable to create learning session";
    console.error("Failed to create learning session", error);
    return NextResponse.json({ error: "Unable to create learning session", detail: message }, { status: 500 });
  }
}

```

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

```typescript
import { NextResponse } from "next/server";
import { z } from "zod";
import { generateChatbotReply, hasChatbotApiKey } from "@/lib/ai/chatbot";

const chatMessageSchema = z.object({
  role: z.enum(["assistant", "user"]),
  content: z.string().trim().min(1).max(4000),
});

const chatRequestSchema = z.object({
  messages: z.array(chatMessageSchema).min(1).max(20),
});

export async function POST(request: Request) {
  try {
    if (!hasChatbotApiKey()) {
      return NextResponse.json(
        { error: "OPENAI_API_KEY is not configured" },
        { status: 503 },
      );
    }

    const parsed = chatRequestSchema.safeParse(await request.json());
    if (!parsed.success) {
      return NextResponse.json(
        { error: "Invalid chat request", issues: parsed.error.flatten() },
        { status: 400 },
      );
    }

    const reply = await generateChatbotReply(parsed.data.messages);
    return NextResponse.json({ message: { role: "assistant", content: reply } });
  } catch (error) {
    const message = error instanceof Error ? error.message : "Unable to generate chat response";
    console.error("Failed to generate chat response", error);
    return NextResponse.json(
      { error: "Unable to generate chat response", detail: message },
      { status: 500 },
    );
  }
}

```

### app/api/learn/archive/route.ts

```typescript
import { NextResponse } from "next/server";
import { listArchivedLearningSessions } from "@/db/repository";

export async function GET() {
  try {
    const sessions = await listArchivedLearningSessions();
    return NextResponse.json({ sessions });
  } catch (error) {
    const message = error instanceof Error ? error.message : "Unable to load archive";
    console.error("Failed to load learning archive", error);
    return NextResponse.json({ error: "Unable to load archive", detail: message }, { status: 500 });
  }
}

```

### app/api/learn/[sessionId]/route.ts

```typescript
import { NextResponse } from "next/server";
import { getLearningSession, listArtifacts, listNodeProgress } from "@/db/repository";

export async function GET(_: Request, context: { params: Promise<{ sessionId: string }> }) {
  const { sessionId } = await context.params;
  const session = await getLearningSession(sessionId);
  if (!session) {
    return NextResponse.json({ error: "Learning session not found" }, { status: 404 });
  }

  const [artifacts, nodeProgress] = await Promise.all([
    listArtifacts(sessionId),
    listNodeProgress(sessionId),
  ]);

  return NextResponse.json({ session, artifacts, nodeProgress });
}

```

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

```typescript
import { NextResponse } from "next/server";
import { z } from "zod";
import { generateText } from "ai";
import { createOpenAI } from "@ai-sdk/openai";
import { env } from "@/lib/utils/env";

const chatMessageSchema = z.object({
  role: z.enum(["assistant", "user"]),
  content: z.string().trim().min(1),
});

const summarizeRequestSchema = z.object({
  messages: z.array(chatMessageSchema).min(1).max(200),
});

const openai = createOpenAI({
  apiKey: env.OPENAI_API_KEY?.trim(),
  compatibility: "strict",
});

function hasOpenAiKey() {
  return Boolean(env.OPENAI_API_KEY?.trim());
}

export async function POST(request: Request) {
  try {
    if (!hasOpenAiKey()) {
      return NextResponse.json(
        { error: "OPENAI_API_KEY is not configured" },
        { status: 503 },
      );
    }

    const parsed = summarizeRequestSchema.safeParse(await request.json());
    if (!parsed.success) {
      return NextResponse.json(
        { error: "Invalid summarize request", issues: parsed.error.flatten() },
        { status: 400 },
      );
    }

    const messages = parsed.data.messages;

    // Build a concise conversation context
    const conversationText = messages
      .map((msg) => `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}`)
      .join("\n");

    const result = await generateText({
      model: openai(env.OPENAI_MODEL_FAST),
      system:
        "You are summarizing a user's learning session conversation. Create a brief, 1-2 sentence summary that captures the main topic or learning goal discussed. Be concise and actionable.",
      prompt: `Summarize this conversation in 1-2 sentences:\n\n${conversationText}`,
      temperature: 0.7,
      maxTokens: 80,
    });

    const summary = result.text.trim() || "Learning session completed";

    return NextResponse.json({ summary });
  } catch (error) {
    const message = error instanceof Error ? error.message : "Unable to generate summary";
    console.error("Failed to generate chat summary", error);
    return NextResponse.json(
      { error: "Unable to generate chat summary", detail: message },
      { status: 500 },
    );
  }
}

```

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