# Project export: Ta.ai

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 2024
- Tagline: An end-to-end platform for lecturers to facilitate mastery learning
- Devpost: https://devpost.com/software/ta-ai
- GitHub: https://github.com/zanechristiansabbagh/Treehacks24.git
- Video: https://www.youtube.com/embed/M-_NBbEdoGQ?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — shumannxu (28 commits), mwirtz112 (16 commits), christenx (14 commits), zcsabbagh (9 commits)

## Devpost submission (written by the team)

### Overview

Everything in this project was planned & completed during TreeHacks.

### Inspiration

Every student knows the week in a course after which nothing makes much sense. When enough "knowledge gaps" form in a course that builds upon itself, it becomes impossible to learn, and easy to stop paying attention, doubt yourself, or even switch majors. A top-tier TED talk is Sal Khan on mastery learning. His core idea is that if we teach for mastery rather than passing, we can remedy the problems in learning caused by knowledge gaps, which grow exponentially over time. There's lots of talk about using AI to catch cheating, to assist in grading and translation, but less about facilitating mastery learning. We want to build a tool that helps educators facilitate this seamlessly.

### What it does

Educators join Ta.ai and get a QR code students can scan to join their class. Before lecture, educators upload their slides, and as students leave lecture, they receive a few short/long-answer questions based on the slides via text, allowing them to answer at their convenience. Each answer gets a quantitative score depending on how thoroughly the student answered the question. On the flip side, educators are able to track which students are yet to complete the questions, the cumulative % of correctness in each lecture, and the individual-level responses to each question sent out. This helps educators understand what topics need to be better communicated, and what students are failing to grasp the material. As the class progresses, students have access to a search that provides chatGPT-style answers to their questions about course content, powered by the content of the slide decks.

### How we built it

We thought about our pain points at students, and then asked educators about theirs. We learned educators needed better ways to track student knowledge gaps. We knew that we needed an easier, more engaging way to demonstrate knowledge: text-messaging is a non-utilized and highly-convenient medium, which made it a good one to try. The product emerged from this intersection. We used Convex as our backend, Flask to deploy functions, and Next.js as our front-end. We used Chroma, Langchain and OpenAI to generate multi-modal embeddings for the slides and conduct Retrieval-Augmented Detection (RAG.) We used Twillio to send text messages to students.

### Challenges we ran into

Refusing to compromise front-end quality Coming from a startup background, we knew that the devil is in the details for product usage, and wanted to build something that was shippable and usable for real-world usage. This came with a lot of extra time spent on interaction design, animation, and hosting. Picking up Convex Had to pick up a new backend stack and then play musical chairs switching ownership and membership as only 2 people were allowed in the free tier. Stuck with it because we loved the ease of using Convex in our stack otherwise :) Deploying with Twillio Spent hours debugging a niche error which was turned out to be the need to run it locally on a specific port :(

### Accomplishments we're proud of

First ever hackathon for all of us! Shipped a lot, fast to bring the vision to life Lots of laughing with each other 20 hours of sleep between the 4 of us

### What we learned

Don't shoot for the moon We originally planned to be a lot more ambitious, hoping to make embeddings for lecture recordings, and integrate the search with Canvas and Ed. Picking up new stacks isn't easy Two of us were completely new to web development 36 hours ago. Tools like v0 and Cursor make it a lot easier to learn, but the learning curve is still there!

### What's next

We're going to expand search functionality and data availability, by allowing search across Canvas and EdStem too. We've already sent a few feeler emails to run a pilot program with Stanford CS classes this Spring quarter. We'll be cold-emailing 100 more CS professors across quarter-system schools.

## README (from the GitHub repository)

This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/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/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## 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/deployment) for more details.


## Detected evidence (automated analysis)

Indexed codebase: 47 recognized source files, 114 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
- Flask (technology) — claimed on Devpost, not found in the code
- LangChain (technology) — claimed on Devpost, not found in the code
- OpenAI (technology) — claimed on Devpost, not found in the code
- Python (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (56 of 56)

```
.env.local
.env.production
.eslintrc.json
.gitignore
app/api/auth/[auth0]/route.js
app/breakdown/[lectureId]/page.tsx
app/ConvexClientProvider.tsx
app/dashboard/page.tsx
app/globals.css
app/joinClass/[parsedEmail]/page.tsx
app/layout.tsx
app/page.tsx
app/qr/page.tsx
app/search/[classId]/page.tsx
app/studentList/page.tsx
components.json
components/class-search.tsx
components/component1.tsx
components/home-auth.tsx
components/home-unauth.tsx
components/join-class.tsx
components/qrcode.tsx
components/student.tsx
components/ui/button.tsx
components/ui/card.tsx
components/ui/input.tsx
components/ui/loginButton.tsx
components/ui/modal.tsx
components/ui/table.tsx
components/ui/wave.tsx
convex/_generated/api.d.ts
convex/_generated/api.js
convex/_generated/dataModel.d.ts
convex/_generated/server.d.ts
convex/_generated/server.js
convex/auth.config.js
convex/classes.ts
convex/embed.ts
convex/files.ts
convex/http.ts
convex/myHttpActions.ts
convex/problems.ts
convex/problemSets.ts
convex/README.md
convex/studentResponses.ts
convex/students.ts
convex/tasks.js
convex/tsconfig.json
convex/twilio.ts
lib/utils.ts
next.config.mjs
package.json
postcss.config.js
README.md
tailwind.config.ts
tsconfig.json
```

### Dependencies

- package.json: @auth0/nextjs-auth0@^3.5.0, @nivo/line@^0.84.0, @radix-ui/react-icons@^1.3.0, @radix-ui/react-slot@^1.0.2, @react-pdf/renderer@^3.3.8, @types/node@^20, @types/react@^18, @types/react-dom@^18, @xixixao/uploadstuff@^0.0.5, autoprefixer@^10.0.1, class-variance-authority@^0.7.0, clsx@^2.1.0, convex@^1.9.0, eslint@^8, eslint-config-next@14.1.0, lucide-react@^0.331.0, next@14.1.0, postcss@^8, react@^18, react-dom@^18, tailwind-merge@^2.2.1, tailwindcss@^3.3.0, tailwindcss-animate@^1.0.7, typescript@^5

### Recent commits (newest first)

- REdoyp error
- get redploy
- get redploy
- fix prerender
- sdfslfdjs
- removed dummy data from studentList
- Dark Mode
- Merge pull request #11 from zanechristiansabbagh/Zane.UpdatedSearchNavigation
- new:
- main
- pass in classid for lecutre sllide generation
- Merge pull request #10 from zanechristiansabbagh/convexfunctions
- Merge branch 'main' into convexfunctions
- typoe
- psuh
- Lectures bigger
- x on add slides
- stornger border
- search button and qr code page improvement
- Merge pull request #9 from zanechristiansabbagh/convexfunctions

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

### package.json

```
{
  "name": "treehacks",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@auth0/nextjs-auth0": "^3.5.0",
    "@nivo/line": "^0.84.0",
    "@radix-ui/react-icons": "^1.3.0",
    "@radix-ui/react-slot": "^1.0.2",
    "@react-pdf/renderer": "^3.3.8",
    "@xixixao/uploadstuff": "^0.0.5",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.0",
    "convex": "^1.9.0",
    "lucide-react": "^0.331.0",
    "next": "14.1.0",
    "react": "^18",
    "react-dom": "^18",
    "tailwind-merge": "^2.2.1",
    "tailwindcss-animate": "^1.0.7"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "eslint": "^8",
    "eslint-config-next": "14.1.0",
    "postcss": "^8",
    "tailwindcss": "^3.3.0",
    "typescript": "^5"
  }
}

```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { UserProvider } from "@auth0/nextjs-auth0/client";
import ConvexClientProvider from "./ConvexClientProvider";

const inter = Inter({ 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">
      <UserProvider>
        <body className={inter.className}>
          <ConvexClientProvider>{children}</ConvexClientProvider>
        </body>
      </UserProvider>
    </html>
  );
}

```

### app/page.tsx

```typescript
"use client";
import React, { useEffect, useState } from "react";
import { get } from "../convex/tasks.js";
import { api } from "../convex/_generated/api";
import { useQuery } from "convex/react";
import { useUser } from "@auth0/nextjs-auth0/client";
import { HomeUnauth } from "@/components/home-unauth";
import HomeAuth from "@/components/home-auth";


export default function Component() {
  const { user, error, isLoading } = useUser();
  const tasksQueryResult = useQuery(api.tasks.get);
  const tasksId = tasksQueryResult ? tasksQueryResult[0]?.id : "";
  // const [profID, setProfID] = useState('');

  // useEffect(() => {
  //   const id = get(); // Assuming getTasks returns profID directly
  //   setProfID(id);
  // }, []);

  return user ? (
    <HomeAuth />
  ) : (
    // <a href="/api/auth/login">login</a>
    <HomeUnauth />
  );
}

```

### app/qr/page.tsx

```typescript
import React from "react";
import { QRCode } from "@/components/qrcode"; // Assuming the path is correct based on the provided context

export default function QRPage() {
  return (
    <div className="flex justify-center items-center h-screen">
      <QRCode />
    </div>
  );
}

```

### app/studentList/page.tsx

```typescript
"use client";
import React from "react";
import {
  TableHead,
  TableRow,
  TableHeader,
  TableCell,
  TableBody,
  Table,
} from "@/components/ui/table";
import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";
import { useUser } from "@auth0/nextjs-auth0/client";

export default function StudentTable() {
  const { user } = useUser();
  const data = useQuery(api.classes.getStudentInfo, { email: user.email });

  return (
    <>
      {data && (
        <div className="flex justify-between items-center mb-4 w-full">
          <h1 className="text-3xl font-bold tracking-tighter">
            Students <span style={{ color: "gray" }}>{`(${data.length})`}</span>
          </h1>
        </div>
      )}
      <div className="flex flex-col items-center justify-center">
        <div className="w-full flex justify-start items-center bg-red"></div>
        <div className="flex justify-center items-center w-full ">
          <div className="bg-gray-800 rounded-lg shadow p-4 w-full">
            <Table className="mx-auto text-lg">
              <TableHeader>
                <TableRow>
                  <TableHead className="w-[50px]" />
                  <TableHead className="text-left">Name</TableHead>
                  <TableHead className="text-right">
                    Cumulative questions completed
                  </TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {data &&
                  data.map((student, index) => (
                    <TableRow key={index}>
                      <TableCell>
                        <img
                          alt="Avatar"
                          className="rounded-full"
                          height="32"
                          src={"/placeholder.svg"}
                          style={{
                            aspectRatio: "32/32",
                            objectFit: "cover",
                          }}
                          width="32"
                        />
                      </TableCell>
                      <TableCell className="font-medium">
                        {student.name}
                      </TableCell>
                      <TableCell className="text-right flex items-center justify-end">
                        <div className="relative mr-2 w-full max-w-[100px] h-4 bg-gray-200 rounded-full overflow-hidden">
                          <div
                            className="absolute top-0 left-0 h-full bg-green-600 rounded-full"
                            style={{
                              width: `${student.cumulativeQuestions * 100}%`,
                              backgroundColor: `hsl(${
                                student.cumulativeQuestions * 100 + 100
                              }, 70%, 40%)`,
                            }}
                          ></div>
                        </div>
                        {Math.round(student.cumulativeQuestions * 100)}%
                      </TableCell>
                    </TableRow>
                  ))}
              </TableBody>
            </Table>
          </div>
        </div>
      </div>
    </>
  );
}

```

### convex/_generated/server.js

```javascript
/* eslint-disable */
/**
 * Generated utilities for implementing server-side Convex query and mutation functions.
 *
 * THIS CODE IS AUTOMATICALLY GENERATED.
 *
 * Generated by convex@1.9.0.
 * To regenerate, run `npx convex dev`.
 * @module
 */

import {
  actionGeneric,
  httpActionGeneric,
  queryGeneric,
  mutationGeneric,
  internalActionGeneric,
  internalMutationGeneric,
  internalQueryGeneric,
} from "convex/server";

/**
 * Define a query in this Convex app's public API.
 *
 * This function will be allowed to read your Convex database and will be accessible from the client.
 *
 * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
 * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
 */
export const query = queryGeneric;

/**
 * Define a query that is only accessible from other Convex functions (but not from the client).
 *
 * This function will be allowed to read from your Convex database. It will not be accessible from the client.
 *
 * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
 * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
 */
export const internalQuery = internalQueryGeneric;

/**
 * Define a mutation in this Convex app's public API.
 *
 * This function will be allowed to modify your Convex database and will be accessible from the client.
 *
 * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
 * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
 */
export const mutation = mutationGeneric;

/**
 * Define a mutation that is only accessible from other Convex functions (but not from the client).
 *
 * This function will be allowed to modify your Convex database. It will not be accessible from the client.
 *
 * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
 * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
 */
export const internalMutation = internalMutationGeneric;

/**
 * Define an action in this Convex app's public API.
 *
 * An action is a function which can execute any JavaScript code, including non-deterministic
 * code and code with side-effects, like calling third-party services.
 * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
 * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
 *
 * @param func - The action. It receives an {@link ActionCtx} as its first argument.
 * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
 */
export const action = actionGeneric;

/**
 * Define an action that is only accessible from other Convex functions (but not from the client).
 *
 * @param func - The function. It receives an {@link ActionCtx} as its first argument.
 * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
 */
export const internalAction = internalActionGeneric;

/**
 * Define a Convex HTTP action.
 *
 * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object
 * as its second.
 * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
 */
export const httpAction = httpActionGeneric;

```

### app/dashboard/page.tsx

```typescript
"use client";
/**
 * This code was generated by v0 by Vercel.
 * @see https://v0.dev/t/mw0ZX5yE2dS
 */
import {
  CardTitle,
  CardDescription,
  CardHeader,
  CardContent,
  Card,
} from "@/components/ui/card";
import { api } from "@/convex/_generated/api";
import { useUser } from "@auth0/nextjs-auth0/client";
import { ResponsiveLine } from "@nivo/line";
import { useQuery } from "convex/react";

export default function Page() {
  return (
    <div className="flex flex-col gap-4 w-full">
      <Card>
        <CardHeader className="pb-0 text-center">
          <CardTitle className="text-3xl">
            Question answering over time
          </CardTitle>
          <CardDescription className="text-xl">
            Percentage of questions answered correctly over time
          </CardDescription>
        </CardHeader>
        <CardContent>
          <div className="flex flex-col gap-4">
            <LineChart className="w-full aspect-[2/1]" />
          </div>
        </CardContent>
      </Card>
    </div>
  );
}

function LineChart(props: React.HTMLAttributes<HTMLDivElement>) {
  const { user } = useUser();

  const getClass = useQuery(api.classes.get, { userId: user?.email });
  const lectureIds =
    getClass?.map((currentClass, index) => {
      const lectureId = currentClass.lectureId;
      return lectureId;
    }) ?? [];

  const data = useQuery(api.studentResponses.getAverageScore, {
    lectureIds: lectureIds,
  });

  return (
    <div {...props}>
      {data && (
        <ResponsiveLine
          data={[
            {
              id: "Sample Data",
              data: data,
              // data: [
              //   { x: 1, y: 5 },
              //   { x: 2, y: 10 },
              //   { x: 3, y: 15 },
              //   { x: 4, y: 20 },
              //   { x: 5, y: 25 },
              //   { x: 6, y: 30 },
              //   { x: 7, y: 35 },
              //   { x: 8, y: 40 },
              //   { x: 9, y: 15 },
              //   { x: 10, y: 50 },
              //   { x: 11, y: 55 },
              //   { x: 12, y: 60 },
              //   { x: 13, y: 25 },
              //   { x: 14, y: 70 },
              //   { x: 15, y: 75 },
              //   { x: 16, y: 40 },
              //   { x: 17, y: 85 },
              //   { x: 18, y: 90 },
              //   { x: 19, y: 95 },
              // ],
            },
          ]}
          margin={{ top: 10, right: 10, bottom: 80, left: 80 }}
          xScale={{
            type: "linear",
            min: 0,
            max: 20,
          }}
          yScale={{
            type: "linear",
            min: 0,
            max: 100,
          }}
          axisTop={null}
          axisRight={null}
          axisBottom={{
            tickSize: 5,
            tickPadding: 16,
            tickValues: 5,
            legend: "Lecture",
            legendOffset: 70,
            legendPosition: "middle",
          }}
          axisLeft={{
            tickSize: 5,
            tickValues: [0, 25, 50, 75, 100],
            tickPadding: 16,
            legend: "% Correct",
            legendOffset: -70,
            legendPosition: "middle",
          }}
          colors={["#90ee90"]}
          pointSize={10} // Increased size of each data point
          lineWidth={4}
          useMesh={true}
          enableGridX={false}
          enableGridY={true}
          gridYValues={[0, 25, 50, 75, 100]}
          gridXValues={[0, 5, 10, 15, 20]}
          theme={{
            grid: {
              line: {
                stroke: "#e0e0e0", // Lighter gray for grid background
                strokeWidth: 1,
              },
            },
            axis: {
              ticks: {
                line: {
                  stroke: "#e0e0e0", // Adjusted to match grid for consistency
                  strokeWidth: 2,
                },
                text: {
                  fill: "#e0e0e0", // Adjusted to match grid for consistency
                  fontSize: 16, // Larger font size for ticks
                },
              },
              legend: {
                text: {
                  fill: "#e0e0e0", // Adjusted to match grid for consistency
                  fontSize: 18, // Larger font size for axis titles
                  fontWeight: "bold",
                },
              },
            },
            tooltip: {
              chip: {
                borderRadius: "9999px",
              },
              container: {
                fontSize: "12px",
                textTransform: "capitalize",
                borderRadius: "6px",
                background: "#fff", // Ensuring tooltip background is solid for readability
                boxShadow: "0px 3px 6px rgba(0, 0, 0, 0.1)", // Adding subtle shadow for better visibility
                color: "#333", // Changing text color for better contrast
              },
            },
          }}
          enablePointLabel={false} // Disabling point labels by default
          pointLabel="y" // Specifying the label to display (y value)
          pointLabelYOffset={-12} // Adjusting label position for better visibility
          role="application"
        />
      )}
    </div>
  );
}

```

### app/joinClass/[parsedEmail]/page.tsx

```typescript
import React from "react";
import { JoinClass } from "@/components/join-class"; // Assuming the path is correct based on the provided context

export default function Page({params}) {
  return (
    <div className="flex justify-center items-center h-screen">
      <JoinClass parsedEmail={params.parsedEmail }/>
    </div>
  );
}

```

### app/breakdown/[lectureId]/page.tsx

```typescript
"use client";
import { TableCell, TableRow, TableBody, Table } from "@/components/ui/table";
import { Card } from "@/components/ui/card";
import { useState } from "react";
import { useUser } from "@auth0/nextjs-auth0/client";
import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";
import { useRouter } from "next/router";

export default function QuestionBreakdown({ params }) {
  const { user } = useUser();
  // const router = useRouter();
  const data = useQuery(api.studentResponses.getResponseScore, {
    lectureId: params.lectureId,
  });
  console.log(data);

  // const data = [
  //   {
  //     question:
  //       "Lorem ipsum dolor sit amet, consectetur and the winner of the award is ",
  //     percentage: "%",
  //   },
  //   { question: "adipiscing elit, sed do eiusmod tempor", percentage: "60%" },
  //   {
  //     question: "incididunt ut labore et dolore magna aliqua",
  //     percentage: "90%",
  //   },
  //   { question: "Ut enim ad minim veniam, quis nostrud", percentage: "75%" },
  //   { question: "exercitation ullamco laboris nisi ut", percentage: "85%" },
  //   { question: "aliquip ex ea commodo consequat", percentage: "70%" },
  // ];

  // Initialize visibility states for n cards
  const initialVisibilityStates = Array(data?.length ?? 0).fill(true);
  const [cardVisibilities, setCardVisibilities] = useState(
    initialVisibilityStates
  );

  const toggleCardVisibility = (index: number) => {
    // Added type annotation for index
    const newVisibilities = [...cardVisibilities];
    newVisibilities[index] = !newVisibilities[index];
    setCardVisibilities(newVisibilities);
  };

  return (
    <div style={{ marginLeft: "5%" }}>
      {" "}
      {/* Added left margin to the entire page */}
      <div style={{ marginTop: "10%", marginBottom: "20%" }}>
        {" "}
        {/* Added vertical margin before the first question */}
        <h1
          style={{
            fontSize: "2em",
            marginLeft: "20%",
            marginBottom: "0.5em",
            fontWeight: "bold",
          }}
        >
          Questions from Lecture 1
        </h1>{" "}
        {/* Made title larger, bold, and horizontally in line with Questions */}
        {Array.from({ length: data?.length ?? 0 }, (_, index) => (
          <div
            key={index}
            className="flex flex-col items-start"
            style={{ width: "60%", margin: "0 auto" }}
          >
            {" "}
            {/* Adjusted width to 60% and centered */}
            <div
              className="cursor-pointer p-4 text-xl font-semibold flex items-center"
              onClick={() => toggleCardVisibility(index)}
            >
              {cardVisibilities[index] ? (
                <span style={{ fontSize: "0.75em" }}>&#9660;</span>
              ) : (
                <span style={{ fontSize: "0.75em" }}>&#9654;</span>
              )}{" "}
              {/* Toggle arrow direction and reduced size */}
              {data && (
                <span className="ml-2">
                  Question {index + 1}: {data[index][0].questionText}
                </span>
              )}
            </div>
            {cardVisibilities[index] && data?.[index] && (
              <Card>
                <div className="flex items-center justify-start">
                  <div className="w-full flex justify-start">
                    <div className="w-full">
                      {" "}
                      {/* Adjusted width to full to utilize Card's width */}
                      <Table className="w-full mx-auto">
                        {" "}
                        {/* Adjusted Table width to full to match the new Card width */}
                        <TableBody className="divide-y">
                          {data[index].map((item, itemIndex) => (
                            <TableRow key={itemIndex} className="text-sm">
                              <TableCell className="w-3/4">
                                {item.responseText}
                              </TableCell>
                              <TableCell>{item.score}</TableCell>
                            </TableRow>
                          ))}
                        </TableBody>
                      </Table>
                    </div>
                  </div>
                </div>
              </Card>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}

```

### app/search/[classId]/page.tsx

```typescript
"use client";
/**
 * This code was generated by v0 by Vercel.
 * @see https://v0.dev/t/3nf3Hoi26BU
 */
import { Button } from "@/components/ui/button";
import { api } from "@/convex/_generated/api";
import { useUser } from "@auth0/nextjs-auth0/client";
import { useQuery } from "convex/react";
import { useRef, useState, useEffect } from "react"; // Import useRef hook
import styled, { keyframes } from "styled-components";
import { useRouter } from 'next/router';

export default function ClassSearch({ params }) {
  // Sample data for Sources


  const [sources, setSources] = useState([
    { name: "Lecture 1", id: 1 },
    { name: "Lecture 2", id: 2 },
    { name: "Lecture 4", id: 3 },
  ]);

  // Sample data for Related
  const [related, setRelated] = useState([
    { query: "what is a short answer" },
    { query: "how to create a short answer" },
    { query: "why short answers are effective" },
  ]);
  const { user } = useUser();
  const [textareaValue, setTextareaValue] = useState("");
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const [inputStyle, setInputStyle] = useState("rounded-full bg-black-500");
  const [enterPressed, setEnterPressed] = useState(false);
  const [reSearch, setReSearch] = useState(false);
  const [titleText, setTitleText] = useState("Give me a short answer to this"); // Added state for dynamic title text
  

  
  const classId = params.classId
  console.log("Class Id: ", classId)
  const handleInput = (e: React.FormEvent<HTMLTextAreaElement>) => {
    const target = e.currentTarget;
    target.style.height = "auto";
    target.style.height = `${target.scrollHeight}px`;
    setInputStyle(
      target.scrollHeight > 35
        ? "rounded-md bg-black-500"
        : "rounded-full bg-black-500"
    );
  };
  const handleKeyPress = async (
    e?: React.KeyboardEvent<HTMLTextAreaElement>
  ) => {
    if ((e?.key === "Enter" && !e.shiftKey) || reSearch === true) {
      console.log(reSearch);
      e?.preventDefault();
      // Clear UI by resetting states that control the display of content
      setTypedAnswer(""); // Clear the typed answer
      setSources([]); // Clear sources
      setRelated([]); // Clear related questions
      setTitleText(""); // Clear title text
      setEnterPressed(false); // Reset enterPressed to hide content sections

      if (textareaRef.current && textareaRef.current.value.trim() !== "") {
        console.log("should generate related questions");

        // Update the UI with new content after clearing
        setEnterPressed(true); // Show content sections
        setTitleText(textareaRef.current.value); // Set the new title text
        const newRelatedQuestions = await generateRelatedQuestions(
          textareaRef.current.value
        );
        const answerText = await generateAnswer(
          textareaRef.current.value,
          classId
        );
        setRelated(
          newRelatedQuestions.map((query, index) => ({ query, id: index }))
        );
        setSources([
          { name: "Lecture 1", id: 4 },
          { name: "Lecture 2", id: 5 },
          { name: "Lecture 3", id: 6 },
        ]);
        setTypedAnswer(""); // Prepare to set the generated answer text
        setTextareaValue(""); // Clear the textarea value
        setFullAnswerText(answerText); // Set the fullAnswerText to the generated answer
      }
    }
  };

  const [typedAnswer, setTypedAnswer] = useState(""); // State to hold the currently typed text
  const [fullAnswerText, setFullAnswerText] = useState(""); // State to hold the full answer text

  useEffect(() => {
    let index = 0;
    const intervalId = setInterval(() => {
      if (index <= fullAnswerText.length) {
        setTypedAnswer((prev) => prev + fullAnswerText.charAt(index));
        index++;
      } else {
        clearInterval(intervalId);
      }
    }, 12.5); // Adjust the interval (12.5ms) to control the speed of typing, speeding up by 4x

    return () => clearInterval(intervalId); // Cleanup interval on component unmount
  }, [fullAnswerText]);

  return (
    <div className="max-w-2xl mx-auto my-8">
      <h1 className="text-4xl font-bold text-center text-orange-500">Ta.ai</h1>
      {enterPressed && (
        <div>
          <h1 className="text-4xl font-bold mb-6">{titleText}</h1>{" "}
          {/* Use dynamic title text */}
          <div className="mb-6">
            <h2 className="text-2xl font-semibold mb-3 flex items-center">
              <SourcesIcon className="mr-2" />
              Sources
            </h2>
            <div className="flex flex-wrap gap-4">
              {sources.map((source, index) => (
                <div key={index} className="flex items-center space-x-2">
                  <FileIcon className="text-gray-600 fadeInTopToBottom" />
                  <span className="text-sm fadeInTopToBottom">
                    {source.name}
                  </span>
                  <span className="text-xs text-gray-500 fadeInTopToBottom">
                    . {source.id}
                  </span>
                </div>
              ))}
            </div>
          </div>
          <div
            className="mb-6 fadeInTopToBottom"
            style={{ "--animation-delay": "1.5s" }}
          >
            <h2 className="text-2xl font-semibold mb-3 flex items-center">
              <AnswerIcon className="mr-2" />
              Answer
            </h2>
            <p className="mb-4">{typedAnswer}</p>
            <div className="flex items-center space-x-2">
              <ShareIcon
                className="text-gray-600 cursor-pointer"
                onClick={() => navigator.clipboard.writeText(typedAnswer)}
              />
              <Button
                className="text-sm pl-1"
                variant="ghost"
                onClick={() => navigator.clipboard.writeText(typedAnswer)}
              >
                Share
              </Button>
              <WriteIcon className="text-gray-600" />
              <Button
        
[truncated — 9889 more characters]
```

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