# Project export: The Duck You Mean?

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: TreeHacks 2025
- Tagline: - if you can explain it to a duck, you can explain it to anyone.
- Devpost: https://devpost.com/software/the-duck-you-mean
- GitHub: https://github.com/joycechen721/duck-you-mean/
- Demo: https://docs.google.com/presentation/d/1wbRdoGqOnnMw5rVU0nRgBMmOErweNsQdrtsrA7q-214/edit?usp=sharing
- Video: https://www.youtube.com/embed/eDDO_p1X9e8?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (ElevenLabs: Best Use of ElevenLabs (11M Credits + AirPods Max per team member [1st] & 8.11M Credits + Merch [2nd] & 5.11M Credits + Merch [3rd]))
- Team: 2 GitHub contributor(s) — Joyce Chen (20 commits), John Connell (12 commits)

## Devpost submission (written by the team)

### Inspiration

We were motivated by the power of rubber duck debugging and the wisdom of Richard Feynman’s quote: "If you can't explain it simply, you don't understand it well enough." Too often, AI is used to give answers, but true learning happens when we explain concepts ourselves. We wanted to flip AI on its head. Rather than asking the duck for answers, you teach it, and it pushes back with evaluations, scores, and follow-up questions to deepen your understanding.

### What it does

The Duck You Mean is an interactive web app that lets users pick a topic and engage in a back-and-forth conversation where AI plays the role of a curious student. Users must explain concepts, and the AI: Scores their explanations based on clarity, correctness, and depth Asks intelligent follow-up questions to test deeper understanding Provides feedback to refine and strengthen their knowledge We also created zone called "The Pond", where users can create Zoom Meetings to study with friends or chill out after completing a lesson. Featured APIs -ElevenLabs - facilitates the full conversation process from user mic input to duck voice generation -Zoom API - creates meetings for peer-to-peer study sessions -OpenAI API - generates the starting question based on the prompt, -Mistrel AI - supports topic ideation and question formation -Luma AI - powers duck thoughts through created images

### Challenges we ran into

Fine-tuning AI’s response evaluation—balancing depth of feedback while keeping interactions engaging Generating meaningful follow-up questions without redundancy Integrating voice interactions smoothly for an immersive experience

### Accomplishments we're proud of

Successfully flipped AI’s role from answering questions to being the student Developed an engaging voice-driven AI tutor experience Created an effective learning tool that enforces deeper understanding Seamlessly integrated multiple AI-powered services

### What we learned

Teaching a concept cements learning better than passive studying Thoughtful follow-up questions are key to uncovering misunderstandings Conversational AI can enhance education beyond just answering queries

### What's next

Learning History to intelligently track user progress over time Gamification: Achievements, streaks, and leaderboards Mobile app version to promote learning on the go

## README (from the GitHub repository)

## TreeHacks 2025: 2nd Place in Best Use of ElevenLabs

## Inspiration  
We were motivated by the power of **rubber duck debugging** and the wisdom of Richard Feynman’s quote:  
> *"If you can't explain it simply, you don't understand it well enough."*  

Too often, AI is used to give answers, but **true learning happens when we explain concepts ourselves**. We wanted to flip AI on its head. Rather than asking the duck for answers, **you teach it**, and it pushes back with evaluations, scores, and follow-up questions to deepen your understanding.  

## What it does  
**The Duck You Mean** is an interactive web app that lets users pick a topic and engage in a back-and-forth conversation where **AI plays the role of a curious student**. Users must explain concepts, and the AI:  
- **Scores their explanations** based on clarity, correctness, and depth  
- **Asks intelligent follow-up questions** to test deeper understanding  
- **Provides feedback** to refine and strengthen their knowledge  

We also created zone called "The Pond", where users can create Zoom Meetings to study with friends or chill out after completing a lesson.


## Featured APIs
-**ElevenLabs** - facilitates the full conversation process from user mic input to duck voice generation

-**Zoom API** - creates meetings for peer-to-peer study sessions

-**OpenAI API** - generates the starting question based on the prompt, 

-**Mistrel AI** - supports topic ideation and question formation

-**Luma AI** - powers duck thoughts through created images

## Challenges we ran into  
- **Fine-tuning AI’s response evaluation**—balancing depth of feedback while keeping interactions engaging  
- **Generating meaningful follow-up questions** without redundancy  
- **Integrating voice interactions smoothly** for an immersive experience  

## Accomplishments that we're proud of  
- Successfully **flipped AI’s role** from answering questions to **being the student**  
- Developed an engaging **voice-driven AI tutor experience**  
- Created an **effective learning tool** that enforces deeper understanding  
- Seamlessly integrated multiple AI-powered services  

## What we learned  
- Teaching a concept **cements learning better than passive studying**  
- Thoughtful follow-up questions **are key to uncovering misunderstandings**  
- **Conversational AI** can enhance education beyond just answering queries

## What's next for The Duck You Mean?  
- **Learning History** to intelligently track user progress over time
- **Gamification:** Achievements, streaks, and leaderboards  
- **Mobile app version** to promote learning on the go

DevPost Submission: https://devpost.com/software/the-duck-you-mean


## Detected evidence (automated analysis)

Indexed codebase: 21 recognized source files, 44 KB.
- CSS (language) — detected in the code
- Firebase (technology) — 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
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Mistral AI (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (26 of 26)

```
.gitignore
eslint.config.mjs
next.config.ts
package.json
postcss.config.mjs
README.md
src/app/api/get-luma/route.ts
src/app/api/mistral/route.ts
src/app/api/open-ai/calcScore/route.ts
src/app/api/open-ai/extractKeywords/route.ts
src/app/api/open-ai/generateElements/route.ts
src/app/api/open-ai/generateImg/route.ts
src/app/api/open-ai/genFirstQuestion/route.ts
src/app/api/zoom/route.ts
src/app/components/conversation.tsx
src/app/components/navbar.tsx
src/app/components/projector.tsx
src/app/globals.css
src/app/layout.tsx
src/app/learn/page.tsx
src/app/lib/Firebase.js
src/app/page.tsx
src/app/pond/page.tsx
src/app/subjects/meetings/page.tsx
tailwind.config.ts
tsconfig.json
```

### Dependencies

- package.json: @11labs/react@^0.0.7, @eslint/eslintrc@^3, @mistralai/mistralai@^1.5.0, @types/node@^20, @types/react@^19, @types/react-dom@^19, axios@^1.7.9, eslint@^9, eslint-config-next@15.1.7, firebase@^11.3.1, fs@^0.0.1-security, lucide-react@^0.475.0, lumaai@^1.4.0, next@15.1.7, node-fetch@^3.3.2, openai@^4.85.1, postcss@^8, react@^19.0.0, react-dom@^19.0.0, tailwindcss@^3.4.1, typescript@^5

### Recent commits (newest first)

- Update README.md
- Update README.md
- added new prompt
- Merge branch 'main' into talkingduck
- add luma functions
- the pond overflows.......
- idk what i did
- Merge branch 'main' of https://github.com/joycechen721/duck-you-mean into talkingduck
- adfasfa
- fix the finish lesson and add talking duck
- got that drip got that style (yuh)
- Mistral AI Addition
- Create Zoom Meeting in the pond
- ahhhh
- failed attempt at prompting
- styling
- WE GOT STYLE
- Set up firebase auth
- styling
- fixed some routes

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

### package.json

```
{
  "name": "duckyoumean",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@11labs/react": "^0.0.7",
    "@mistralai/mistralai": "^1.5.0",
    "axios": "^1.7.9",
    "firebase": "^11.3.1",
    "fs": "^0.0.1-security",
    "lucide-react": "^0.475.0",
    "lumaai": "^1.4.0",
    "next": "15.1.7",
    "node-fetch": "^3.3.2",
    "openai": "^4.85.1",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "15.1.7",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

```

### src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import Navbar from "./components/navbar";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
        style={{ height: "100vh" }}
      >
        <Navbar />
        {children}
      </body>
    </html>
  );
}

```

### src/app/page.tsx

```typescript
export default function Home() {
  const backgroundImage = '/background.png';  
  const logoImage = '/logo.png';  
  const startImage = '/start.png';
  const duckImage = '/duck.png';

  return (
    <main
      className="flex flex-col items-center justify-between"
      style={{ backgroundImage: `url(${backgroundImage})`, backgroundSize: 'cover', backgroundPosition: 'center' }}
    >
      <div className="z-10 max-w-5xl w-full flex flex-col items-center font-mono text-sm">
        <h1 className="text-6xl font-bold" style={{marginTop: '50px'}}>the duck you mean?</h1>
        <p style={{marginTop: '50px', fontSize: '15px', fontStyle: 'italic'}}>- if you can explain it to a duck, you can explain it to anyone.</p> 
        <p style={{marginTop: '10px', fontSize: '15px', fontStyle: 'italic'}}>- the ai student that quacks back.</p>
        {/* <img src={logoImage} alt="Logo" className="mb-8 w-96 h-auto" /> */}

        <a href="/learn" className="h-10">
          <img src={startImage} alt="Start" style={{width: '500px'}} />
        </a>
      </div>
      <div className="flex w-full justify-end mt-8">
        <img 
          src={duckImage} 
          alt="Duck" 
          className="w-1/2 h-auto object-contain" 
        />
      </div>
    </main>
  );
}

```

### src/app/learn/page.tsx

```typescript
import { Conversation } from "../components/conversation";

export default function Home() {
  return (
    <main className="flex flex-col items-center justify-between">
      <div className="z-10 w-full items-center justify-between font-mono text-sm">
        <Conversation />
      </div>
    </main>
  );
}



```

### src/app/pond/page.tsx

```typescript
"use client";

import { useState } from "react";
import axios from "axios";
import Image from "next/image";

export default function SubjectsPage() {
    const [meetingLink, setMeetingLink] = useState<string | null>(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState<string | null>(null);

    const createMeeting = async () => {
        setLoading(true);
        setError(null);

        try {
            const response = await axios.post("/api/zoom");
            setMeetingLink(response.data.join_url);
        } catch (err) {
            if (err.response) {
                console.error("Response error:", err.response);
                setError(`Zoom API error: ${err.response.data.message}`);
            } else if (err.request) {
                console.error("Request error:", err.request);
                setError("No response received from Zoom API.");
            } else {
                console.error("Error", err.message);
                setError("Failed to create meeting. Try again.");
            }
        } finally {
            setLoading(false);
        }
    }

    return (
        <div className="relative min-h-screen">
            {/* Background Image */}
            <Image
                src="/pond.PNG"
                alt="Pond Background"
                fill
                className="object-cover"
                priority
            />
            
            {/* Content Overlay */}
            <div className="relative z-10 p-6 flex flex-col items-center min-h-screen">
                <h1 className="text-4xl font-bold text-center text-blue-800">Welcome to the Pond</h1>
                <p className="text-gray-800 text-center mt-2">
                    Study with friends, enjoy some ducks, or chill after a tough lesson.
                </p>

                <button 
                    onClick={createMeeting} 
                    className="mt-4 bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition-colors shadow-lg disabled:bg-gray-400"
                    disabled={loading}
                >
                    {loading ? "Creating..." : "Create a Zoom Study Room"}
                </button>

                {error && (
                    <p className="text-red-600 mt-4 p-2 bg-white/90 rounded">{error}</p>
                )}

                {meetingLink && (
                    <div className="mt-4 text-center p-4 bg-white/90 rounded-lg shadow-lg max-w-xl mx-auto">
                        <p className="text-green-600 font-semibold">✅ Meeting Created!</p>
                        <p className="mt-2 text-blue-700">Join here:</p>
                        <a 
                            href={meetingLink} 
                            target="_blank" 
                            rel="noopener noreferrer" 
                            className="text-blue-500 underline hover:text-blue-700"
                        >
                            {meetingLink}
                        </a>
                    </div>
                )}

                {/* Vertical Cards with Image for Study Rooms */}
                <div className="mt-8 w-full max-w-4xl mx-auto grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-6">

                    {/* Notional Card 4 */}
                    <div className="bg-white rounded-lg shadow-lg overflow-hidden">
                        <Image 
                            src="/LanguageDuck.png" 
                            alt="Literature Review Techniques" 
                            width={300} 
                            height={200} 
                            className="object-cover w-full h-48"
                        />
                        <div className="p-4">
                            <h3 className="text-xl font-semibold text-blue-800">Literature Review Techniques</h3>
                            <a 
                                href="https://zoom.us/j/4567890123" 
                                target="_blank" 
                                rel="noopener noreferrer" 
                                className="text-blue-600 font-semibold hover:text-blue-800 underline mt-2 block"
                            >
                                Join Study Room
                            </a>
                        </div>
                    </div>
                    {/* Notional Card 1 */}
                    <div className="bg-white rounded-lg shadow-lg overflow-hidden">
                        <Image 
                            src="/MathDuck.PNG" 
                            alt="Math 101" 
                            width={300} 
                            height={200} 
                            className="object-cover w-full h-48"
                        />
                        <div className="p-4">
                            <h3 className="text-xl font-semibold text-blue-800">Math 101</h3>
                            <a 
                                href="https://zoom.us/j/1234567890" 
                                target="_blank" 
                                rel="noopener noreferrer" 
                                className="text-blue-600 font-semibold hover:text-blue-800 underline mt-2 block"
                            >
                                Join Study Room
                            </a>
                        </div>
                    </div>
                    
                    {/* Notional Card 2 */}
                    <div className="bg-white rounded-lg shadow-lg overflow-hidden">
                        <Image 
                            src="/ScienceDuck.png" 
                            alt="Chemistry Basics" 
                            width={300} 
                            height={200} 
                            className="object-cover w-full h-48"
                        />
                        <div className="p-4">
                            <h3 className="text-xl font-semibold text-blue-800">Compute
[truncated — 1702 more characters]
```

### src/app/api/mistral/route.ts

```typescript
import { Mistral } from '@mistralai/mistralai';

const apiKey = process.env.MISTRAL_API_KEY;

const client = new Mistral({apiKey: process.env.OPENAI_API_KEY});

const chatResponse = await client.chat.complete({
  model: 'mistral-large-latest',
  messages: [{role: 'user', content: 'What is the best French cheese?'}],
});

console.log('Chat:', chatResponse.choices[0].message.content);
```

### src/app/api/get-luma/route.ts

```typescript
// app/api/generateImage/route.js
import fs from 'fs';
import path from 'path';
import { LumaAI } from 'lumaai';

const client = new LumaAI({ authToken: process.env.LUMALABS_API_KEY });

export async function POST(req:any) {
    try {
        const { prompt } = await req.json();
        let generation = await client.generations.image.create({
            prompt,
        });

        let completed = false;

        while (!completed) {
            generation = await client.generations.get(generation.id);

            if (generation.state === "completed") {
                completed = true;
            } else if (generation.state === "failed") {
                return Response.json({ error: `Generation failed: ${generation.failure_reason}` }, { status: 500 });
            } else {
                console.log("Dreaming...");
                await new Promise(r => setTimeout(r, 3000)); // Wait for 3 seconds
            }
        }

        const imageUrl = generation.assets.image;
        const response = await fetch(imageUrl);
        const buffer = await response.arrayBuffer();

        // Define the public folder path
        const fileName = `${generation.id}.jpg`;
        const filePath = path.join(process.cwd(), 'public', 'images', fileName);

        // Ensure the directory exists
        fs.mkdirSync(path.dirname(filePath), { recursive: true });

        // Write the file to the public directory
        fs.writeFileSync(filePath, Buffer.from(buffer));

        // Return the public URL of the saved image
        return Response.json({ imageUrl: `/images/${fileName}` }, { status: 200 });

    } catch (error) {
        console.error(error);
        return Response.json({ error: 'Failed to generate image' }, { status: 500 });
    }
}

```

### src/app/api/zoom/route.ts

```typescript
import { NextResponse } from 'next/server';
import axios from 'axios';

// Export the POST handler
export async function POST(request: Request) {
  try {
    // Get access token
    const tokenResponse = await axios.post(
      "https://zoom.us/oauth/token",
      new URLSearchParams({
        grant_type: "account_credentials",
        account_id: process.env.ZOOM_ACCOUNT_ID || "",
      }),
      {
        headers: {
          Authorization: `Basic ${Buffer.from(
            `${process.env.ZOOM_CLIENT_ID}:${process.env.ZOOM_CLIENT_SECRET}`
          ).toString("base64")}`,
          "Content-Type": "application/x-www-form-urlencoded",
        },
      }
    );

    console.log("Token Response:", tokenResponse.data); // Debug log

    const accessToken = tokenResponse.data.access_token;

    // Create Zoom meeting
    const zoomResponse = await axios.post(
      "https://api.zoom.us/v2/users/me/meetings",
      {
        topic: "Scheduled Meeting",
        type: 2,
        start_time: new Date().toISOString(),
        duration: 30,
        timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
        settings: {
          host_video: true,
          participant_video: true,
        },
      },
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
          "Content-Type": "application/json",
        },
      }
    );

    console.log("Zoom Response:", zoomResponse.data); // Debug log

    return NextResponse.json({
      join_url: zoomResponse.data.join_url,
      meeting_id: zoomResponse.data.id,
      password: zoomResponse.data.password,
    });
  } catch (error: any) {
    console.error('Zoom API Error:', error.response?.data || error.message);
    return NextResponse.json(
      { error: "Failed to create Zoom meeting", details: error.message },
      { status: 500 }
    );
  }
}
```

### src/app/subjects/meetings/page.tsx

```typescript
"use client";

import { useState } from "react";
import axios from "axios";
import Image from "next/image";

export default function SubjectsPage() {
    const [meetingLink, setMeetingLink] = useState<string | null>(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState<string | null>(null);

    const createMeeting = async () => {
        setLoading(true);
        setError(null);

        try {
            const response = await axios.post("/api/zoom");
            setMeetingLink(response.data.join_url);
        } catch (err) {
            if (err.response) {
                console.error("Response error:", err.response);
                setError(`Zoom API error: ${err.response.data.message}`);
            } else if (err.request) {
                console.error("Request error:", err.request);
                setError("No response received from Zoom API.");
            } else {
                console.error("Error", err.message);
                setError("Failed to create meeting. Try again.");
            }
        } finally {
            setLoading(false);
        }
    }

    return (
        <div className="relative min-h-screen">
            {/* Background Image */}
            <Image
                src="/pond.PNG"
                alt="Pond Background"
                fill
                className="object-cover"
                priority
            />
            
            {/* Content Overlay */}
            <div className="relative z-10 p-6 flex flex-col items-center min-h-screen">
                <h1 className="text-4xl font-bold text-center text-blue-800">Welcome to the Pond</h1>
                <p className="text-gray-800 text-center mt-2">
                    Study with friends, enjoy some ducks, or chill after a tough lesson.
                </p>

                <button 
                    onClick={createMeeting} 
                    className="mt-4 bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition-colors shadow-lg disabled:bg-gray-400"
                    disabled={loading}
                >
                    {loading ? "Creating..." : "Create a Zoom Study Room"}
                </button>

                {error && (
                    <p className="text-red-600 mt-4 p-2 bg-white/90 rounded">{error}</p>
                )}

                {meetingLink && (
                    <div className="mt-4 text-center p-4 bg-white/90 rounded-lg shadow-lg max-w-xl mx-auto">
                        <p className="text-green-600 font-semibold">✅ Meeting Created!</p>
                        <p className="mt-2 text-blue-700">Join here:</p>
                        <a 
                            href={meetingLink} 
                            target="_blank" 
                            rel="noopener noreferrer" 
                            className="text-blue-500 underline hover:text-blue-700"
                        >
                            {meetingLink}
                        </a>
                    </div>
                )}

                {/* Vertical Cards with Image for Study Rooms */}
                <div className="mt-8 w-full max-w-4xl mx-auto grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-6">
                    {/* Notional Card 1 */}
                    <div className="bg-white rounded-lg shadow-lg overflow-hidden">
                        <Image 
                            src="/pond-image.jpg" 
                            alt="Math 101" 
                            width={300} 
                            height={200} 
                            className="object-cover w-full h-48"
                        />
                        <div className="p-4">
                            <h3 className="text-xl font-semibold text-blue-800">Math 101</h3>
                            <a 
                                href="https://zoom.us/j/1234567890" 
                                target="_blank" 
                                rel="noopener noreferrer" 
                                className="text-blue-600 font-semibold hover:text-blue-800 underline mt-2 block"
                            >
                                Join Study Room
                            </a>
                        </div>
                    </div>
                    
                    {/* Notional Card 2 */}
                    <div className="bg-white rounded-lg shadow-lg overflow-hidden">
                        <Image 
                            src="/pond-image.jpg" 
                            alt="Computer Science Basics" 
                            width={300} 
                            height={200} 
                            className="object-cover w-full h-48"
                        />
                        <div className="p-4">
                            <h3 className="text-xl font-semibold text-blue-800">Computer Science Basics</h3>
                            <a 
                                href="https://zoom.us/j/2345678901" 
                                target="_blank" 
                                rel="noopener noreferrer" 
                                className="text-blue-600 font-semibold hover:text-blue-800 underline mt-2 block"
                            >
                                Join Study Room
                            </a>
                        </div>
                    </div>

                    {/* Notional Card 3 */}
                    <div className="bg-white rounded-lg shadow-lg overflow-hidden">
                        <Image 
                            src="/pond-image.jpg" 
                            alt="History of Science" 
                            width={300} 
                            height={200} 
                            className="object-cover w-full h-48"
                        />
                        <div className="p-4">
                            <h3 className="text-xl font-semibold text-blue-800">History of Scien
[truncated — 1685 more characters]
```

### src/app/api/open-ai/generateImg/route.ts

```typescript
import OpenAI from "openai";

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export async function POST(req:any) {
    try {
        const { prompt } = await req.json();
        // console.log(prompt)

        const response = await openai.images.generate({
            model: "dall-e-3",
            prompt,
            n: 1,
            size: "1024x1024",
        });

        return Response.json({ imageUrl: response.data[0].url }, { status: 200 });
    } catch (error) {
        console.error("Error generating image:", error);
        return Response.json({ error: "Failed to generate image" }, { status: 500 });
    }
}

```

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