# Project export: Brain Pods

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: Cal Hacks 11.0
- Tagline: Brain Pods empowers students to connect, learn, and grow in real-time. Join live video sessions where an AI mentor sparks discussions, offers feedback, and brings together minds from around the world.
- Devpost: https://devpost.com/software/brain-pods
- GitHub: https://github.com/franciscoerramuspe/brain-pods
- Demo: https://www.brain-pods.com/
- Video: https://www.youtube.com/embed/9v2Ngqbunrk?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Emilio De Gyves García (51 commits), joscaz (42 commits), Francisco (36 commits), Diegogtzt03 (16 commits)

## Devpost submission (written by the team)

### Inspiration

The inspiration for Brain Pods came as an idea after a long path of bad teachers and the spontaneous boredom that study brings to people. We wanted to create a simple and fun tool for all students all over the world using the power of AI.

### What it does

Brain Pods allows students to quickly create or join live video calls where an AI, using context from study guides the owner of the pod uploaded, can ask game-like questions in real time and give custom feedback to the students.

### How we built it

We built our platform on top of the technology that Agora offers with their real-time video SDK. Using this, we leveraged frameworks like Supabase and NextJS to quickly iterate over the ideas we had. For AI, we leveraged the technology of Groq for small tasks such as recommendations, while using Google's Gemini for bigger context windows in live pods.

### Challenges we ran into

Working with a completely new tool such as Agora Figuring out what was the best architecture for the real-time events Managing the different contexts for the different AI sessions

### Accomplishments we're proud of

Being able to create multiple pods without them interacting with each other. Deploying our product in a production environment where others can use it.

### What we learned

No one in our team had ever worked with real-time communication to the extent this project required us to. We learned about the importance of having fallbacks and robust systems that will assure the system does not fail in case of an error, something that happens very often in real-time events.

### What's next

We believe Brain Pods has a lot of space to grow, during our process, we thought about many things that would make this THE go-to tool for studying. The first next step for Brain Pods would be to integrate several things to allow students to share other types of information, such as whiteboards, sharing screens, coding pads, etc.. We also believe that voice is an important feature for both accessibility and easiness, which is why implementing a live voice agent that could interact with the students is the big next step.

## README (from the GitHub repository)

This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.


## Detected evidence (automated analysis)

Indexed codebase: 59 recognized source files, 170 KB.
- CSS (language) — detected in the code
- Google Gemini (technology) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Express (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 (68 of 68)

```
.eslintrc.json
.gitignore
components.json
next.config.js
next.config.mjs
package.json
postcss.config.mjs
README.md
src/agoraConfig.js
src/app/api/files/route.ts
src/app/api/models/groq/route.ts
src/app/api/pods/history/route.ts
src/app/api/session/route.ts
src/app/dashboard/page.tsx
src/app/fonts/AdvercaseFont-Demo-Bold.otf
src/app/fonts/AdvercaseFont-Demo-Regular.otf
src/app/globals.css
src/app/history/[user-id]/page.tsx
src/app/layout.tsx
src/app/page.tsx
src/app/pod/[pod-id]/page.tsx
src/app/pod/new/page.tsx
src/components/Chat.tsx
src/components/ContextProvider.tsx
src/components/GoogleButton.tsx
src/components/Header.tsx
src/components/HistoryItem.tsx
src/components/Icons.tsx
src/components/InteractiveCard.tsx
src/components/Logo.tsx
src/components/multi-select.tsx
src/components/oval-room-menu.tsx
src/components/Pod.tsx
src/components/ScrollList.tsx
src/components/searchbar/Dropdown.tsx
src/components/searchbar/SearchBar.tsx
src/components/searchbar/TagMenu.tsx
src/components/searchbar/types.ts
src/components/theme-provider.tsx
src/components/ui/badge.tsx
src/components/ui/button.tsx
src/components/ui/command.tsx
src/components/ui/dialog.tsx
src/components/ui/drawer.tsx
src/components/ui/dropdown-menu.tsx
src/components/ui/input.tsx
src/components/ui/popover.tsx
src/components/ui/separator.tsx
src/components/ui/sheet.tsx
src/components/ui/snackbar.tsx
src/components/ui/textarea.tsx
src/components/ui/toast.tsx
src/components/ui/toaster.tsx
src/components/UserInfo.tsx
src/components/UserTracker.tsx
src/components/videocall/AgoraComponent.tsx
src/components/videocall/Controls.tsx
src/components/videocall/Pod.tsx
src/components/videocall/UserGrid.tsx
src/components/videocall/VideoCall.tsx
src/hooks/use-toast.ts
src/interfaces/types.ts
src/lib/podOperations.ts
src/lib/supabase.ts
src/lib/utils.ts
src/utils/id-generator.ts
tailwind.config.ts
tsconfig.json
```

### Dependencies

- package.json: @google/generative-ai@^0.21.0, @heroicons/react@^2.1.5, @radix-ui/react-dialog@^1.1.2, @radix-ui/react-dropdown-menu@^2.1.2, @radix-ui/react-popover@^1.1.2, @radix-ui/react-separator@^1.1.0, @radix-ui/react-slot@^1.1.0, @radix-ui/react-toast@^1.2.2, @supabase/auth-helpers-nextjs@^0.10.0, @supabase/supabase-js@^2.45.5, @types/node@^20, @types/react@^18, @types/react-dom@^18, agora-rtc-react@^2.3.0, agora-rtc-sdk-ng@^4.22.1, class-variance-authority@^0.7.0, clsx@^2.1.1, cmdk@^1.0.0, dotenv@^16.4.5, eslint@^8, eslint-config-next@14.2.15, framer-motion@^11.11.9, groq-sdk@^0.7.0, lucide-react@^0.453.0, next@14.2.15, next-themes@^0.3.0, postcss@^8, react@^18, react-dom@^18, react-hot-toast@^2.4.1, tailwind-merge@^2.5.4, tailwindcss@^3.4.1, tailwindcss-animate@^1.0.7, ts-node@^10.9.2, tsconfig-paths@^4.2.0, typescript@^5.6.3, vaul@^1.1.0

### Recent commits (newest first)

- Merge pull request #18 from franciscoerramuspe/dev
- Validator url exists
- corrected session
- Merge pull request #17 from franciscoerramuspe/patch4.0
- Merge branch 'main' into patch4.0
- new policies
- Groq prompt asking through dashboard
- Merge branch 'patch4.0' of https://github.com/franciscoerramuspe/brain-pods
- Merge branch 'patch4.0' of https://github.com/franciscoerramuspe/brain-pods into patch4.0
- upgrade the scrolls
- Cover the camera
- Merge pull request #15 from franciscoerramuspe/display-history-name
- added podname
- Added new no camera image
- Merge pull request #14 from franciscoerramuspe/chatscroll
- added chat scroll
- Merge branch 'usercount' of https://github.com/franciscoerramuspe/brain-pods into patch4.0
- upgrade the scrolls
- added user tracking
- upgrade the cards

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

### package.json

```
{
  "name": "brain-pods",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "ts-node": "ts-node"
  },
  "dependencies": {
    "@google/generative-ai": "^0.21.0",
    "@heroicons/react": "^2.1.5",
    "@radix-ui/react-dialog": "^1.1.2",
    "@radix-ui/react-dropdown-menu": "^2.1.2",
    "@radix-ui/react-popover": "^1.1.2",
    "@radix-ui/react-separator": "^1.1.0",
    "@radix-ui/react-slot": "^1.1.0",
    "@radix-ui/react-toast": "^1.2.2",
    "@supabase/auth-helpers-nextjs": "^0.10.0",
    "@supabase/supabase-js": "^2.45.5",
    "agora-rtc-react": "^2.3.0",
    "agora-rtc-sdk-ng": "^4.22.1",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.1",
    "cmdk": "^1.0.0",
    "dotenv": "^16.4.5",
    "framer-motion": "^11.11.9",
    "groq-sdk": "^0.7.0",
    "lucide-react": "^0.453.0",
    "next": "14.2.15",
    "next-themes": "^0.3.0",
    "react": "^18",
    "react-dom": "^18",
    "react-hot-toast": "^2.4.1",
    "tailwind-merge": "^2.5.4",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "^1.1.0"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "14.2.15",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "ts-node": "^10.9.2",
    "tsconfig-paths": "^4.2.0",
    "typescript": "^5.6.3"
  }
}

```

### src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { ThemeProvider } from "@/components/theme-provider";
import localFont from "next/font/local";
import "./globals.css";

const adversecase = localFont({
  src: "./fonts/AdvercaseFont-Demo-Bold.otf",
  variable: "--font-adversecase",
});

const adversecaseRegular = localFont({
  src: "./fonts/AdvercaseFont-Demo-Regular.otf",
  variable: "--font-adversecase-regular",
});

const geistSans = localFont({
  src: "./fonts/GeistVF.woff",
  variable: "--font-geist-sans",
  weight: "100 900",
});

export const metadata: Metadata = {
  title: "Brain Pods",
  description: "Your AI-powered study rooms",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body
        className={`${adversecase.variable} ${adversecaseRegular.variable} ${geistSans.variable} font-sans antialiased bg-[#323232] text-foreground`}
      >
        <ThemeProvider
          attribute="class"
          defaultTheme="dark"
          enableSystem
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

```

### src/app/page.tsx

```typescript
"use client";

import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { supabase } from "../lib/supabase";
import GoogleButton from "../components/GoogleButton";
import Logo from "../components/Logo";

export default function Home() {
  const router = useRouter();

  useEffect(() => {
    const { data: authListener } = supabase.auth.onAuthStateChange((event) => {
      if (event === "SIGNED_IN") {
        router.push("/dashboard");
      }
    });

    supabase.auth.getSession().then(({ data: { session } }) => {
      if (session) {
        router.push("/dashboard");
      }
    });

    return () => {
      authListener.subscription.unsubscribe();
    };
  }, [router]);

  return (
    <div className="flex flex-col items-center justify-center min-h-screen">
      <div className="flex flex-col items-center mb-12">
        <Logo className="w-20 h-20 mb-4" />
        <h1 className="text-6xl font-bold text-white font-adversecase">
          Brain Pods
        </h1>
      </div>
      <div className="flex flex-col items-center space-y-3">
        <GoogleButton className="px-4 py-2 text-base w-64" />
      </div>
    </div>
  );
}

```

### src/app/dashboard/page.tsx

```typescript
"use client";

import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { supabase } from "../../lib/supabase";
import SearchBar from "../../components/searchbar/SearchBar";
import { User } from "@supabase/supabase-js";
import Header from "../../components/Header";
import ScrollList from "../../components/ScrollList";
import { OvalRoomMenu } from "../../components/oval-room-menu";

export default function Dashboard() {
  const [user, setUser] = useState<User | null>(null);
  const router = useRouter();

  useEffect(() => {
    const { data: authListener } = supabase.auth.onAuthStateChange(
      (event, session) => {
        if (event === "SIGNED_IN") {
          setUser(session?.user ?? null);
        } else if (event === "SIGNED_OUT") {
          router.push("/");
        }
      }
    );

    supabase.auth.getSession().then(({ data: { session } }) => {
      setUser(session?.user ?? null);
    });

    return () => {
      authListener.subscription.unsubscribe();
    };
  }, [router]);

  if (!user) {
    return <div>Loading...</div>;
  }

  const navigateToHistory = () => {
    router.push(`/history/${user.id}`);
  };

  return (
    <div className="min-h-screen bg-[#323232] flex flex-col">
      {/* Header */}
      <Header user={user} textIsDisplayed={true} userIsDisplayed={true} />

      {/* SearchBar justo debajo del header */}
      <div className="p-4">
        <SearchBar />
      </div>

      {/* Contenido principal (puede crecer según el espacio disponible) */}
      <main className="p-4 flex-grow flex items-start justify-center overflow-hidden gap-15 mt-4">
        <div className="flex flex-col items-center justify-start gap-10 pt-0">
          <ScrollList title="Popular Tags" />
          <ScrollList title="General Tags" />
        </div>
      </main>

      {/* Menú de botones en un óvalo centrado */}
      <footer className="relative mb-16">
        <OvalRoomMenu />
      </footer>
    </div>
  );
}

```

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

```typescript
import dotenv from "dotenv";

dotenv.config();

export async function startSession({ podId }: { podId: string }) {
  await fetch(`https://${process.env.NEXT_PUBLIC_SERVICE_API}/start`, {
    method: "POST",
    body: JSON.stringify({ podId: podId }),
    headers: {
      "Content-Type": "application/json",
    },
  });
}

```

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

```typescript
import { supabase } from "@/lib/supabase";
// import { GoogleGenerativeAI } from "@google/generative-ai";
// import dotenv from "dotenv";
//
// dotenv.config();

export async function uploadEmbeddings({
  podId,
  context,
}: {
  podId: string;
  context: string;
}) {
  // const genAI = new GoogleGenerativeAI(
  //   process.env.NEXT_PUBLIC_GEMINI_API_KEY || ""
  // );
  // const model = genAI.getGenerativeModel({ model: "text-embedding-004" });

  // const result = await model.embedContent(context);
  // const embedding = result.embedding.values;

  // INSERT EMBEDDING INTO DATABASE
  const { data, error } = await supabase
    .from("pod")
    .update({
      embedding: context,
    })
    .eq("id", podId);

  if (error) {
    console.error("Error uploading embeddings: ", error);
    return error;
  }

  return data;
}

```

### src/app/pod/[pod-id]/page.tsx

```typescript
"use client";
import dynamic from "next/dynamic";
// import dotenv from "dotenv";
import { useEffect, useState } from "react";
import { useRouter, useParams } from "next/navigation";
import { supabase } from "@/lib/supabase";
import { Snackbar } from "@/components/ui/snackbar";
import {
  joinPodSession,
  checkPodStatus,
  leavePodSession,
  updatePodStatus,
} from "@/lib/podOperations";

const appId = process.env.NEXT_PUBLIC_AGORA_API_KEY;

const Pod = dynamic(
  () => import("../../../components/videocall/AgoraComponent"),
  {
    ssr: false,
    loading: () => (
      <div className="flex justify-center items-center h-screen">
        <div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-gray-900"></div>
      </div>
    ),
  }
);

const PodPage = () => {
  const router = useRouter();
  const params = useParams();
  const podId = params["pod-id"] as string;
  const [podExists, setPodExists] = useState<boolean | null>(null);
  const [isSnackbarOpen, setIsSnackbarOpen] = useState(false);
  const [snackbarMessage, setSnackbarMessage] = useState("");
  const [userId, setUserId] = useState<string | null>(null);

  useEffect(() => {
    let userSession: string | null = null;

    const checkAndJoinPod = async () => {
      try {
        const {
          data: { user },
        } = await supabase.auth.getUser();
        if (!user) {
          throw new Error("User not authenticated");
        }
        setUserId(user.id);
        userSession = user.id;

        const { exists, isActive, hasEnded } = await checkPodStatus(podId);

        if (!exists) {
          setPodExists(false);
          setSnackbarMessage("This pod does not exist.");
          setIsSnackbarOpen(true);
          setTimeout(() => router.push('/dashboard'), 3000);
          return;
        }

        if (!isActive || hasEnded) {
          setPodExists(false);
          setSnackbarMessage("This pod session has ended or is inactive.");
          setIsSnackbarOpen(true);
          setTimeout(() => router.push('/dashboard'), 3000);
          return;
        }

        await joinPodSession(user.id, podId);
        setPodExists(true);
      } catch (error) {
        console.error("Error joining pod:", error);
        setPodExists(false);
        setSnackbarMessage("Error joining pod");
        setIsSnackbarOpen(true);
        setTimeout(() => router.push("/dashboard"), 1000);
      }
    };

    checkAndJoinPod();

    return () => {
      if (userSession && podId) {
        leavePodSession(userSession, podId)
          .then((result) => {
            if (result) {
              console.log("Left pod session successfully");
              return updatePodStatus(podId);
            } else {
              console.log(
                "Failed to leave pod session or session was already closed"
              );
              return updatePodStatus(podId);
            }
          })
          .then((updatedPod) => {
            if (updatedPod) {
              console.log("Pod status updated: ", updatedPod);
            } else {
              console.log(
                "Pod status not updated, there might still be active users"
              );
            }
          })
          .catch((error) => console.error("Error leaving pod:", error));
      }
    };
  }, [podId, router]);

  if (podExists === null) {
    return (
      <div className="flex justify-center items-center h-screen">
        <div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-gray-900"></div>
      </div>
    );
  }

  return (
    <>
      {podExists ? <Pod appId={appId || ""} /> : null}
      <Snackbar
        message={snackbarMessage}
        isOpen={isSnackbarOpen}
        onClose={() => setIsSnackbarOpen(false)}
      />
    </>
  );
};
export default PodPage;

```

### src/app/history/[user-id]/page.tsx

```typescript
'use client';

import React from 'react';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import Header from '../../../components/Header';
import { User } from '@supabase/supabase-js';
import { supabase } from '../../../lib/supabase';
interface Pod {
  id: string;
  name: string; // Added name field
  owner_id: string;
  is_active: boolean;
  is_premium: boolean;
  is_public: boolean;
  created_at: string;
  ended_at: string | null;
}

interface PodSession {
  id: string;
  pod_id: string;
  user_id: string;
  joined_at: string;
  left_at: string | null;
  is_active: boolean;
  pod: Pod | null;
}

export default function HistoryPage() {
  const [user, setUser] = useState<User | null>(null);
  const [podSessions, setPodSessions] = useState<PodSession[]>([]);
  const router = useRouter();

  useEffect(() => {
    const getUser = async () => {
      const { data } = await supabase.auth.getUser();
      if (data.user) {
        setUser(data.user);
        fetchPodSessions(data.user);
      } else {
        router.push('/');
      }
    };
    getUser();
  }, [router]);

  const fetchPodSessions = async (currentUser: User) => {
    try {
      console.log('Fetching pod sessions for user:', currentUser.id);
  
      const { data: sessions, error: sessionsError } = await supabase
        .from('user_pod_session')
        .select(`
          *,
          pod (
            *
          )
        `)
        .eq('user_id', currentUser.id);
  
      if (sessionsError) {
        console.error('Error fetching pod sessions:', sessionsError.message);
        return;
      }
  
      console.log('Fetched sessions with pods:', sessions);
  
      if (sessions && sessions.length > 0) {
        setPodSessions(sessions as PodSession[]);
      } else {
        console.log('No sessions found for user');
        setPodSessions([]);
      }
    } catch (error) {
      console.error('Error fetching pod sessions:', error);
    }
  };
  

  const formatDate = (dateString: string) => {
    return new Date(dateString).toLocaleDateString('en-US', {
      year: 'numeric',
      month: 'long',
      day: 'numeric',
    });
  };

  const calculateDuration = (start: string, end: string | null) => {
    const startDate = new Date(start);
    const endDate = end ? new Date(end) : new Date();
    const durationMs = endDate.getTime() - startDate.getTime();
    const hours = Math.floor(durationMs / (1000 * 60 * 60));
    const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60));
    return `${hours}h ${minutes}m`;
  };

  const handleViewPod = (podId: string) => {
    router.push(`/pod/${podId}`);
  };

  if (!user) {
    return <div>Loading...</div>;
  }

  return (
    <div className="min-h-screen bg-[#323232] text-white">
      <Header user={user} textIsDisplayed={true} userIsDisplayed={true} />
      <main className="p-6">
        <h1 className="text-3xl font-bold mb-6">History</h1>
        {podSessions.length > 0 ? (
          <div className="space-y-4">
            {podSessions.map((session) => (
              <div
                key={session.id}
                className="bg-[#4A4A4A] rounded-lg p-4 flex justify-between items-center"
              >
                <div>
                  <h2 className="text-xl font-semibold">
                    {session.pod && session.pod.name ? session.pod.name : 'Unknown Pod'}
                  </h2>
                  <p className="text-sm text-gray-300">
                    Joined at: {formatDate(session.joined_at)}
                  </p>
                  {session.pod && (
                    <p className="text-sm text-gray-300">
                      Pod is {session.pod.is_active ? 'Active' : 'Inactive'}
                    </p>
                  )}
                </div>
                <div className="flex items-center space-x-4">
                  <span className="text-sm">
                    Duration: {calculateDuration(session.joined_at, session.left_at)}
                  </span>
                  <button
                    onClick={() => handleViewPod(session.pod_id)}
                    className="bg-blue-500 text-white px-4 py-2 rounded-full text-sm hover:bg-blue-600 transition-colors"
                  >
                    View
                  </button>
                </div>
              </div>
            ))}
          </div>
        ) : (
          <p>No pod sessions found.</p>
        )}
      </main>
    </div>
  );
}

```

### src/app/pod/new/page.tsx

```typescript
"use client";

import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { supabase } from "../../../lib/supabase";
import { User } from "@supabase/supabase-js";
import { MultiSelect } from "@/components/multi-select";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import Header from "../../../components/Header";
import ContextProvider from "../../../components/ContextProvider";
import { uploadEmbeddings } from "@/app/api/files/route";
import { TextContext } from "@/interfaces/types";

export default function NewPod() {
  const [user, setUser] = useState<User | null>(null);
  const router = useRouter();

  // Pod Variables
  const [podName, setPodName] = useState<string>("");
  const [podTags, setPodTags] = useState<string[]>([]);
  const [isCreatingPod, setIsCreatingPod] = useState<boolean>(false);
  const [contextList, setContextList] = useState<[TextContext | File][]>([]);
  const [isButtonHovered, setIsButtonHovered] = useState<boolean>(false);
  const [isCancelHovered, setIsCancelHovered] = useState<boolean>(false);
  const [tagsList, setTagsList] = useState<{ value: string; label: string }[]>(
    []
  ); // State for tags

  // const handleCreatePod = async () => {
  //   // INSERT POD INTO DATABASE, GET POD ID

  //   let finalContext = "";

  //   contextList.forEach(async (item) => {
  //     if (item[0] instanceof File) {
  //       const fileReader = new FileReader();
  //       fileReader.onload = function (fileLoadedEvent) {
  //         const textFromFileLoaded = fileLoadedEvent.target?.result;
  //         finalContext += textFromFileLoaded;
  //         finalContext += "\n";
  //       };
  //       fileReader.readAsText(item[0], "UTF-8");
  //     } else {
  //       finalContext += item[0].text;
  //       finalContext += "\n";
  //     }
  //   });

  //   // uploadEmbeddings({ podId: podName, context: finalContext });
  // };

  useEffect(() => {
    const { data: authListener } = supabase.auth.onAuthStateChange(
      (event, session) => {
        if (event === "SIGNED_IN") {
          setUser(session?.user ?? null);
        } else if (event === "SIGNED_OUT") {
          router.push("/");
        }
      }
    );

    supabase.auth.getSession().then(({ data: { session } }) => {
      setUser(session?.user ?? null);
    });

    return () => {
      authListener.subscription.unsubscribe();
    };
  }, [router]);

  // Fetch tags from supabase
  useEffect(() => {
    const fetchTags = async () => {
      try {
        const { data, error } = await supabase
          .from("pod_tag")
          .select("id, tag");

        if (error) throw error;

        const formattedTags = data.map((tag) => ({
          value: tag.tag,
          label: tag.tag
            ? tag.tag
                .toLowerCase()
                .replace(/\b\w/g, (c: string) => c.toUpperCase())
            : "",
        }));
        console.log(data);
        setTagsList(formattedTags);
      } catch (error) {
        console.error("Error fetching tags: ", error);
      }
    };

    fetchTags();
  }, []);

  const createPod = async () => {
    if (!user || !podName || podTags.length === 0) return;

    setIsCreatingPod(true);

    try {
      // Process context
      let finalContext = "";
      for (const item of contextList) {
        if (item[0] instanceof File) {
          const text = await readFileAsText(item[0]);
          finalContext += text + "\n";
        } else {
          finalContext += item[0].text + "\n";
        }
      }

      // Create pod
      const { data: podData, error: podError } = await supabase
        .from("pod")
        .insert({
          owner_id: user?.id,
          name: podName,
          is_active: true,
          is_premium: false,
          is_public: true,
        })
        .select()
        .single();

      if (podError) throw podError;

      // Insert pod topics
      if (podTags.length > 0) {
        const { error: topicError } = await supabase.from("pod_topic").insert(
          podTags.map((tag) => ({
            pod_id: podData.id,
            topic_name: tag.toUpperCase(),
          }))
        );

        if (topicError) throw topicError;
      }

      // Upload embeddings
      // let finalContext = "";

      // const processContextList = async () => {
      //   for (const item of contextList) {
      //     if (item[0] instanceof File) {
      //       const fileContent = await new Promise<string>((resolve) => {
      //         const fileReader = new FileReader();
      //         fileReader.onload = (event) => {
      //           resolve(event.target?.result as string);
      //         };
      //         fileReader.readAsText(item[0] as File);
      //       });
      //       finalContext += fileContent + "\n";
      //     } else {
      //       finalContext += (item[0] as TextContext).text + "\n";
      //     }
      //   }
      // };

      // await processContextList();

      await uploadEmbeddings({
        podId: podData.id,
        context: finalContext,
      });

      // Redirect to the new pod
      router.push(`/pod/${podData.id}`);
    } catch (error) {
      console.error("Error creating pod: ", error);
    } finally {
      setIsCreatingPod(false);
    }
  };

  // Helper function to read file as text
  const readFileAsText = (file: File): Promise<string> => {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = (event) => resolve(event.target?.result as string);
      reader.onerror = (error) => reject(error);
      reader.readAsText(file, "UTF-8");
    });
  };

  if (!user) {
    return <div>Loading...</div>;
  }

  return (
    <div className="h-screen bg-[#323232] flex flex-col">
      <Header user={user} textIsDisplayed={true} userIsDisplayed={true} />

      <div className="flex flex-col items-center justify-center">
        <h1 className="text-4xl text-gray-200 font-advers
[truncated — 2465 more characters]
```

### src/app/api/pods/history/route.ts

```typescript
import { NextResponse } from 'next/server';
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';

export async function GET() {
  console.log('API route called');
  const supabase = createRouteHandlerClient({ cookies });

  const {
    data: { session },
  } = await supabase.auth.getSession();

  console.log('Session in API route:', session);

  if (!session) {
    console.log('No session found in API route');
    return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
  }

  console.log('User ID:', session.user.id);

  const { data, error } = await supabase
    .from('user_pod_session')
    .select('*')
    .eq('user_id', session.user.id)
    .order('joined_at', { ascending: false });

  if (error) {
    console.error('Supabase query error:', error);
    return NextResponse.json({ error: error.message }, { status: 500 });
  }

  console.log('Query result:', data);

  return NextResponse.json(data);
}

```

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