From 2ac28edecb6b1577cdbb121aaa7df10054e7c3a3 Mon Sep 17 00:00:00 2001 From: Youssef Hergal Date: Sun, 1 Dec 2024 20:59:33 +0100 Subject: [PATCH 1/4] =?UTF-8?q?Message=20d=C3=A9crivant=20les=20changement?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 40 + .idea/.gitignore | 5 + .idea/easy-study-web-app-main.iml | 12 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + README.md | 36 + app/(auth)/sign-in/[[...sign-in]]/page.jsx | 9 + app/(auth)/sign-up/[[...sign-up]]/page.jsx | 5 + app/_context/CourseCountContext.jsx | 3 + app/api/courses/route.js | 30 + app/api/create-user/route.js | 16 + app/api/generate-course-outline/route.js | 81 + app/api/generate/route.js | 78 + app/api/inngest/route.js | 16 + app/api/study-type-content/route.jsx | 64 + app/api/study-type/route.jsx | 68 + .../[courseId]/_components/ChapterList.jsx | 25 + .../_components/CourseIntroCard.jsx | 20 + .../[courseId]/_components/EndScreen.jsx | 18 + .../_components/MaterialCardItem.jsx | 60 + .../[courseId]/_components/StepProgress.jsx | 19 + .../_components/StudyMaterialSection.jsx | 76 + .../flashcards/_components/FlashcardItem.jsx | 28 + app/course/[courseId]/flashcards/page.jsx | 85 + app/course/[courseId]/notes/page.jsx | 58 + app/course/[courseId]/page.jsx | 43 + .../question_answer/_components/QAItem.jsx | 12 + .../[courseId]/question_answer/page.jsx | 60 + .../quiz/_components/QuizCardItem.jsx | 31 + app/course/[courseId]/quiz/page.jsx | 83 + app/course/layout.jsx | 16 + app/course/loading.jsx | 9 + app/create-file/_components/TopicFile.jsx | 0 app/create-file/page.jsx | 121 + app/create/_components/SelectOption.jsx | 74 + app/create/_components/TopicInput.jsx | 42 + app/create/page.jsx | 85 + app/dashboard/_components/CourseCardItem.jsx | 43 + app/dashboard/_components/CourseList.jsx | 71 + app/dashboard/_components/DashboardHeader.jsx | 34 + app/dashboard/_components/SideBar.jsx | 99 + app/dashboard/_components/WelcomeBanner.jsx | 19 + app/dashboard/layout.jsx | 26 + app/dashboard/page.jsx | 16 + app/dashboard/profile/page.jsx | 13 + app/dashboard/upgrade/page.jsx | 231 + app/favicon.ico | Bin 0 -> 25931 bytes app/featureCard.jsx | 54 + app/fonts/GeistMonoVF.woff | Bin 0 -> 67864 bytes app/fonts/GeistVF.woff | Bin 0 -> 66268 bytes app/globals.css | 93 + app/layout.js | 29 + app/page.js | 53 + app/provider.js | 45 + components.json | 21 + components/ui/button.jsx | 48 + components/ui/card.jsx | 50 + components/ui/carousel.jsx | 194 + components/ui/input.jsx | 19 + components/ui/progress.jsx | 23 + components/ui/select.jsx | 121 + components/ui/sonner.jsx | 29 + components/ui/textarea.jsx | 18 + configs/AiModel.js | 92 + configs/db.js | 3 + configs/schema.js | 45 + drizzle.config.js | 9 + inngest/client.js | 4 + inngest/functions.js | 129 + jsconfig.json | 7 + lib/utils.js | 6 + middleware.js | 16 + next.config.mjs | 4 + package-lock.json | 6103 +++++++++++++++++ package.json | 46 + postcss.config.mjs | 8 + public/Fsdm_it_club_logo.png | Bin 0 -> 114553 bytes public/code.png | Bin 0 -> 24062 bytes public/content.png | Bin 0 -> 16835 bytes public/cs.jpeg | Bin 0 -> 40072 bytes public/exam.png | Bin 0 -> 18493 bytes public/exam_1.png | Bin 0 -> 22854 bytes public/file.svg | 1 + public/fitness.png | Bin 0 -> 20020 bytes public/flashcard.png | Bin 0 -> 20749 bytes public/globe.svg | 1 + public/job.png | Bin 0 -> 14271 bytes public/knowledge.png | Bin 0 -> 33883 bytes public/laptop.png | Bin 0 -> 75114 bytes public/logo.svg | 1 + public/math.jpg | Bin 0 -> 249484 bytes public/next.svg | 1 + public/notes.png | Bin 0 -> 22113 bytes public/phisics.jpeg | Bin 0 -> 104378 bytes public/practice.png | Bin 0 -> 19338 bytes public/qa.png | Bin 0 -> 35388 bytes public/quiz.png | Bin 0 -> 22470 bytes public/vercel.svg | 1 + public/window.svg | 1 + tailwind.config.js | 61 + test.html | 11 + utils/fileParser.js | 20 + 102 files changed, 9361 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/easy-study-web-app-main.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 app/(auth)/sign-in/[[...sign-in]]/page.jsx create mode 100644 app/(auth)/sign-up/[[...sign-up]]/page.jsx create mode 100644 app/_context/CourseCountContext.jsx create mode 100644 app/api/courses/route.js create mode 100644 app/api/create-user/route.js create mode 100644 app/api/generate-course-outline/route.js create mode 100644 app/api/generate/route.js create mode 100644 app/api/inngest/route.js create mode 100644 app/api/study-type-content/route.jsx create mode 100644 app/api/study-type/route.jsx create mode 100644 app/course/[courseId]/_components/ChapterList.jsx create mode 100644 app/course/[courseId]/_components/CourseIntroCard.jsx create mode 100644 app/course/[courseId]/_components/EndScreen.jsx create mode 100644 app/course/[courseId]/_components/MaterialCardItem.jsx create mode 100644 app/course/[courseId]/_components/StepProgress.jsx create mode 100644 app/course/[courseId]/_components/StudyMaterialSection.jsx create mode 100644 app/course/[courseId]/flashcards/_components/FlashcardItem.jsx create mode 100644 app/course/[courseId]/flashcards/page.jsx create mode 100644 app/course/[courseId]/notes/page.jsx create mode 100644 app/course/[courseId]/page.jsx create mode 100644 app/course/[courseId]/question_answer/_components/QAItem.jsx create mode 100644 app/course/[courseId]/question_answer/page.jsx create mode 100644 app/course/[courseId]/quiz/_components/QuizCardItem.jsx create mode 100644 app/course/[courseId]/quiz/page.jsx create mode 100644 app/course/layout.jsx create mode 100644 app/course/loading.jsx create mode 100644 app/create-file/_components/TopicFile.jsx create mode 100644 app/create-file/page.jsx create mode 100644 app/create/_components/SelectOption.jsx create mode 100644 app/create/_components/TopicInput.jsx create mode 100644 app/create/page.jsx create mode 100644 app/dashboard/_components/CourseCardItem.jsx create mode 100644 app/dashboard/_components/CourseList.jsx create mode 100644 app/dashboard/_components/DashboardHeader.jsx create mode 100644 app/dashboard/_components/SideBar.jsx create mode 100644 app/dashboard/_components/WelcomeBanner.jsx create mode 100644 app/dashboard/layout.jsx create mode 100644 app/dashboard/page.jsx create mode 100644 app/dashboard/profile/page.jsx create mode 100644 app/dashboard/upgrade/page.jsx create mode 100644 app/favicon.ico create mode 100644 app/featureCard.jsx create mode 100644 app/fonts/GeistMonoVF.woff create mode 100644 app/fonts/GeistVF.woff create mode 100644 app/globals.css create mode 100644 app/layout.js create mode 100644 app/page.js create mode 100644 app/provider.js create mode 100644 components.json create mode 100644 components/ui/button.jsx create mode 100644 components/ui/card.jsx create mode 100644 components/ui/carousel.jsx create mode 100644 components/ui/input.jsx create mode 100644 components/ui/progress.jsx create mode 100644 components/ui/select.jsx create mode 100644 components/ui/sonner.jsx create mode 100644 components/ui/textarea.jsx create mode 100644 configs/AiModel.js create mode 100644 configs/db.js create mode 100644 configs/schema.js create mode 100644 drizzle.config.js create mode 100644 inngest/client.js create mode 100644 inngest/functions.js create mode 100644 jsconfig.json create mode 100644 lib/utils.js create mode 100644 middleware.js create mode 100644 next.config.mjs create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.mjs create mode 100644 public/Fsdm_it_club_logo.png create mode 100644 public/code.png create mode 100644 public/content.png create mode 100644 public/cs.jpeg create mode 100644 public/exam.png create mode 100644 public/exam_1.png create mode 100644 public/file.svg create mode 100644 public/fitness.png create mode 100644 public/flashcard.png create mode 100644 public/globe.svg create mode 100644 public/job.png create mode 100644 public/knowledge.png create mode 100644 public/laptop.png create mode 100644 public/logo.svg create mode 100644 public/math.jpg create mode 100644 public/next.svg create mode 100644 public/notes.png create mode 100644 public/phisics.jpeg create mode 100644 public/practice.png create mode 100644 public/qa.png create mode 100644 public/quiz.png create mode 100644 public/vercel.svg create mode 100644 public/window.svg create mode 100644 tailwind.config.js create mode 100644 test.html create mode 100644 utils/fileParser.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..d32cc78b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..b58b603fe --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/easy-study-web-app-main.iml b/.idea/easy-study-web-app-main.iml new file mode 100644 index 000000000..24643cc37 --- /dev/null +++ b/.idea/easy-study-web-app-main.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..e582be1a6 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..94a25f7f4 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..66bb426ff --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +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.js`. 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. diff --git a/app/(auth)/sign-in/[[...sign-in]]/page.jsx b/app/(auth)/sign-in/[[...sign-in]]/page.jsx new file mode 100644 index 000000000..9305682a7 --- /dev/null +++ b/app/(auth)/sign-in/[[...sign-in]]/page.jsx @@ -0,0 +1,9 @@ +import { SignIn } from '@clerk/nextjs' + +export default function Page() { + return ( +
+ +
+) +} \ No newline at end of file diff --git a/app/(auth)/sign-up/[[...sign-up]]/page.jsx b/app/(auth)/sign-up/[[...sign-up]]/page.jsx new file mode 100644 index 000000000..903a7d0f4 --- /dev/null +++ b/app/(auth)/sign-up/[[...sign-up]]/page.jsx @@ -0,0 +1,5 @@ +import { SignUp } from '@clerk/nextjs' + +export default function Page() { + return +} \ No newline at end of file diff --git a/app/_context/CourseCountContext.jsx b/app/_context/CourseCountContext.jsx new file mode 100644 index 000000000..58f9cdf30 --- /dev/null +++ b/app/_context/CourseCountContext.jsx @@ -0,0 +1,3 @@ +import { createContext } from "react"; + +export const CourseCountContext=createContext(); \ No newline at end of file diff --git a/app/api/courses/route.js b/app/api/courses/route.js new file mode 100644 index 000000000..49e85a708 --- /dev/null +++ b/app/api/courses/route.js @@ -0,0 +1,30 @@ +import { db } from "@/configs/db"; +import { STUDY_MATERIAL_TABLE } from "@/configs/schema"; +import { desc, eq } from "drizzle-orm"; +import { NextResponse } from "next/server"; + +export async function POST(req) { + + const {createdBy}=await req.json(); + + const result=await db.select().from(STUDY_MATERIAL_TABLE) + .where(eq(STUDY_MATERIAL_TABLE.createdBy,createdBy)) + .orderBy(desc(STUDY_MATERIAL_TABLE.id)) + + + return NextResponse.json({result:result}); + +} + +export async function GET(req) { + + const reqUrl=req.url; + const {searchParams}=new URL(reqUrl); + const courseId=searchParams?.get('courseId'); + + const course=await db.select().from(STUDY_MATERIAL_TABLE) + .where(eq(STUDY_MATERIAL_TABLE?.courseId,courseId)); + + return NextResponse.json({result:course[0]}) + +} \ No newline at end of file diff --git a/app/api/create-user/route.js b/app/api/create-user/route.js new file mode 100644 index 000000000..9614bf2f6 --- /dev/null +++ b/app/api/create-user/route.js @@ -0,0 +1,16 @@ +import { inngest } from "@/inngest/client"; +import { NextResponse } from "next/server"; + +export async function POST(req) { + + const {user}=await req.json(); + + const result=await inngest.send({ + name:'user.create', + data:{ + user:user + } + }) + + return NextResponse.json({result:result}) +} \ No newline at end of file diff --git a/app/api/generate-course-outline/route.js b/app/api/generate-course-outline/route.js new file mode 100644 index 000000000..ede5e1d00 --- /dev/null +++ b/app/api/generate-course-outline/route.js @@ -0,0 +1,81 @@ +import { courseOutlineAIModel } from "@/configs/AiModel"; +import { db } from "@/configs/db"; +import { STUDY_MATERIAL_TABLE } from "@/configs/schema"; +import { inngest } from "@/inngest/client"; +import { NextResponse } from "next/server"; + +export async function POST(req) { + + const {courseId,topic,courseType,difficultyLevel,createdBy}=await req.json(); + + const PROMPT = ` +Generate a comprehensive study material outline for the topic: "${topic}". +Ensure the material is suitable for a difficulty level of "${difficultyLevel}". + +The output should include the following: +1. **Course Title**: Provide a concise, engaging, and descriptive title for the course. +2. **Course Summary**: Write a brief summary of the course, highlighting its objectives, target audience, and key takeaways. +3. **List of Chapters** (Maximum of 3): + - Include a creative and descriptive title for each chapter. + - Provide a brief summary of each chapter (2-3 sentences). + - Suggest an appropriate emoji icon to represent each chapter. +4. **Topics List**: Include a list of topics covered in each chapter to give a clear structure to the material. + +**Important**: +- The output must be generated in the same language as the topic. For example, if the topic is in French, generate the content in French; if in Arabic, generate it in Arabic, and so on. Adjust the tone and phrasing to suit the language context appropriately. + +Ensure the output is returned in **valid JSON format**, structured as follows: +{ + "title": "Course Title", + "summary": "Course Summary", + "chapters": [ + { + "title": "Chapter 1 Title", + "summary": "Chapter 1 Summary", + "emoji": "📘", + "topics": ["Topic 1", "Topic 2", "Topic 3"] + }, + { + "title": "Chapter 2 Title", + "summary": "Chapter 2 Summary", + "emoji": "📙", + "topics": ["Topic 1", "Topic 2", "Topic 3"] + }, + { + "title": "Chapter 3 Title", + "summary": "Chapter 3 Summary", + "emoji": "📗", + "topics": ["Topic 1", "Topic 2", "Topic 3"] + } + ] +} + +Focus on creating a user-friendly and engaging output that makes the material easy to understand and visually appealing. +`; + + // Generate Course Layout using AI + const aiResp=await courseOutlineAIModel.sendMessage(PROMPT); + const aiResult= JSON.parse(aiResp.response.text()); + + // Save the result along with User Input + const dbResult=await db.insert(STUDY_MATERIAL_TABLE).values({ + courseId:courseId, + courseType:courseType, + createdBy:createdBy, + topic:topic, + courseLayout:aiResult + }).returning({resp:STUDY_MATERIAL_TABLE}) + + //Trigger the Inngest function to generate chapter notes + + inngest.send({ + name:'notes.generate', + data:{ + course:dbResult[0].resp + } + }); + // console.log(result); + + return NextResponse.json({result:dbResult[0]}) + +} \ No newline at end of file diff --git a/app/api/generate/route.js b/app/api/generate/route.js new file mode 100644 index 000000000..73c8a3229 --- /dev/null +++ b/app/api/generate/route.js @@ -0,0 +1,78 @@ +import { extractTextFromPdf } from "@/utils/fileParser"; +import { courseOutlineAIModel } from "@/configs/AiModel"; +import { v4 as uuidv4 } from "uuid"; +import formidable from "formidable"; +import fs from "fs/promises"; + + + +export async function POST(req) { + + + try { + const form = new formidable.IncomingForm(); + const { fields, files } = await new Promise((resolve, reject) => { + form.parse(req, (err, fields, files) => { + if (err) reject(err); + resolve({ fields, files }); + }); + }); + + const { createdBy } = fields; + const file = files.file; + + // Read file content + const fileBuffer = await fs.readFile(file.filepath); + const textFromPdf = await extractTextFromPdf(fileBuffer); + + + + // AI prompt + const PROMPT = ` + Generate a comprehensive study material outline for the topic: "${textFromPdf}". + Ensure the material is suitable for a difficulty level of easy. + + The output should include the following: + 1. **Course Title**: Provide a concise, engaging, and descriptive title for the course. + 2. **Course Summary**: Write a brief summary of the course, highlighting its objectives, target audience, and key takeaways. + 3. **List of Chapters** (Maximum of 3): + - Include a creative and descriptive title for each chapter. + - Provide a brief summary of each chapter (2-3 sentences). + - Suggest an appropriate emoji icon to represent each chapter. + 4. **Topics List**: Include a list of topics covered in each chapter to give a clear structure to the material. + + Ensure the output is returned in **valid JSON format**, structured as follows: + { + "title": "Course Title", + "summary": "Course Summary", + "chapters": [ + { + "title": "Chapter 1 Title", + "summary": "Chapter 1 Summary", + "emoji": "📘", + "topics": ["Topic 1", "Topic 2", "Topic 3"] + }, + ... + ] + } + `; + + const aiResp = await courseOutlineAIModel.sendMessage(PROMPT); + let aiResult; + aiResult = JSON.parse(aiResp.response.text()); + + + const courseData = { + courseId: uuidv4(), + courseType: "*", + topic: "Extracted from PDF", + difficultyLevel: "Easy", + courseLayout: aiResult, + createdBy, + status: "Generated", + }; + + } catch (error) { + console.error("Error processing PDF file:", error); + } +} diff --git a/app/api/inngest/route.js b/app/api/inngest/route.js new file mode 100644 index 000000000..e27e999bd --- /dev/null +++ b/app/api/inngest/route.js @@ -0,0 +1,16 @@ +import { serve } from "inngest/next"; +import { inngest } from "../../../inngest/client"; +import { CreateNewUser, GenerateNotes, GenerateStudyTypeContent, helloWorld } from "@/inngest/functions"; +export const runtime = 'edge'; +// Create an API that serves zero functions +export const { GET, POST, PUT } = serve({ + client: inngest, + streaming:'allow', + functions: [ + /* your functions will be passed here later! */ + helloWorld, + CreateNewUser, + GenerateNotes, + GenerateStudyTypeContent + ], +}); diff --git a/app/api/study-type-content/route.jsx b/app/api/study-type-content/route.jsx new file mode 100644 index 000000000..4038ad2f1 --- /dev/null +++ b/app/api/study-type-content/route.jsx @@ -0,0 +1,64 @@ +import { db } from "@/configs/db"; +import { STUDY_TYPE_CONTENT_TABLE } from "@/configs/schema"; +import { inngest } from "@/inngest/client"; +import { NextResponse } from "next/server"; + +// Function to get the prompt based on the study type +function getPromptByType(type, chapters) { + switch (type) { + case 'Flashcard': + return `Generate flashcards on the topic: ${chapters} in JSON format with "front" and "back" content. Limit to a maximum of 15 flashcards.`; + + case 'Question/Answer' : + return `Generate a Question and Answer list on the topic: ${chapters} in JSON format with the following structure: +{ + "questions": [ + { + "question": "What is machine learning?", + "answer": "Machine learning (ML) is a branch of artificial intelligence (AI) and computer science which focuses on the use of data and algorithms to imitate the way that humans learn, gradually improving its accuracy." + }, + ... + ] +} +Limit the number of qaPairs to a maximum of 10. Ensure each question is clear, and the answers are detailed and accurate.`; + + case 'Quiz': + return `Generate a quiz on the topic: ${chapters} in JSON format with questions, multiple-choice options, and the correct answer for each. Limit to a maximum of 10 questions.`; + + default: + throw new Error("Invalid type provided"); + } +} + +export async function POST(req) { + const { chapters, courseId, type } = await req.json(); + + try { + // Get the appropriate prompt using the helper function + const PROMPT = getPromptByType(type, chapters); + console.log(PROMPT) + + // Insert record into the database and set status to "Generating..." + const result = await db.insert(STUDY_TYPE_CONTENT_TABLE).values({ + courseId: courseId, + type: type, + }).returning({ id: STUDY_TYPE_CONTENT_TABLE.id }); + + // Trigger Inngest function with the generated prompt + await inngest.send({ + name: 'studyType.content', + data: { + studyType: type, + prompt: PROMPT, + courseId: courseId, + recordId: result[0].id, + }, + }); + + // Return the newly created record ID + return NextResponse.json(result[0].id); + } catch (error) { + // Handle invalid type or other errors + return NextResponse.json({ error: error.message }, { status: 400 }); + } +} diff --git a/app/api/study-type/route.jsx b/app/api/study-type/route.jsx new file mode 100644 index 000000000..bab01a688 --- /dev/null +++ b/app/api/study-type/route.jsx @@ -0,0 +1,68 @@ +import { db } from "@/configs/db"; +import { CHAPTER_NOTES_TABLE, STUDY_TYPE_CONTENT_TABLE } from "@/configs/schema"; +import { and, eq } from "drizzle-orm"; +import { NextResponse } from "next/server"; + +// Handler function for the POST request +export async function POST(req) { + try { + // Parse the incoming request payload + const { courseId, studyType } = await req.json(); + + // Validate the incoming data + if (!courseId || !studyType) { + return NextResponse.json({ error: "Missing courseId or studyType" }, { status: 400 }); + } + + // Fetch data based on the `studyType` + if (studyType === 'ALL') { + return await getAllStudyMaterials(courseId); + } else if (studyType === 'notes') { + return await getNotes(courseId); + } else { + return await getSpecificStudyMaterial(courseId, studyType); + } + } catch (error) { + console.error("Error handling POST request:", error); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); + } +} + +// Function to fetch all study materials (notes, flashcards, quizzes, QA) +async function getAllStudyMaterials(courseId) { + const notes = await db.select().from(CHAPTER_NOTES_TABLE) + .where(eq(CHAPTER_NOTES_TABLE.courseId, courseId)); + + const contentList = await db.select().from(STUDY_TYPE_CONTENT_TABLE) + .where(eq(STUDY_TYPE_CONTENT_TABLE.courseId, courseId)); + + const result = { + notes, + flashcard: contentList.filter(item => item.type === 'Flashcard'), + quiz: contentList.filter(item => item.type === 'Quiz'), + qa: contentList.filter(item => item.type === 'Question/Answer'), + }; + + return NextResponse.json(result); +} + +// Function to fetch notes +async function getNotes(courseId) { + const notes = await db.select().from(CHAPTER_NOTES_TABLE) + .where(eq(CHAPTER_NOTES_TABLE.courseId, courseId)); + + return NextResponse.json(notes); +} + +// Function to fetch specific study material by type +async function getSpecificStudyMaterial(courseId, studyType) { + const result = await db.select().from(STUDY_TYPE_CONTENT_TABLE) + .where( + and( + eq(STUDY_TYPE_CONTENT_TABLE.courseId, courseId), + eq(STUDY_TYPE_CONTENT_TABLE.type, studyType) + ) + ); + + return NextResponse.json(result[0] ?? []); +} diff --git a/app/course/[courseId]/_components/ChapterList.jsx b/app/course/[courseId]/_components/ChapterList.jsx new file mode 100644 index 000000000..77ba82a65 --- /dev/null +++ b/app/course/[courseId]/_components/ChapterList.jsx @@ -0,0 +1,25 @@ +import React from 'react' + +function ChapterList({course}) { + const CHAPTERS=course?.courseLayout?.chapters + return ( +
+

Chapters

+ +
+ {CHAPTERS?.map((chapter,index)=>( +
+

{chapter?.emoji}

+
+

{chapter?.chapter_title||chapter?.chapterTitle}

+

{chapter?.summary}

+
+
+ ))} +
+
+ ) +} + +export default ChapterList \ No newline at end of file diff --git a/app/course/[courseId]/_components/CourseIntroCard.jsx b/app/course/[courseId]/_components/CourseIntroCard.jsx new file mode 100644 index 000000000..36efe4d2c --- /dev/null +++ b/app/course/[courseId]/_components/CourseIntroCard.jsx @@ -0,0 +1,20 @@ +import { Progress } from '@/components/ui/progress' +import Image from 'next/image' +import React from 'react' + +function CourseIntroCard({course}) { + return ( +
+ other +
+

{course?.courseLayout?.title}

+

{course?.courseLayout?.summary}

+ + +

Total Chapter: {course?.courseLayout?.chapters?.length}

+
+
+ ) +} + +export default CourseIntroCard \ No newline at end of file diff --git a/app/course/[courseId]/_components/EndScreen.jsx b/app/course/[courseId]/_components/EndScreen.jsx new file mode 100644 index 000000000..c41b16167 --- /dev/null +++ b/app/course/[courseId]/_components/EndScreen.jsx @@ -0,0 +1,18 @@ +import { Button } from '@/components/ui/button' +import { useRouter } from 'next/navigation' +import React from 'react' + +function EndScreen({data,stepCount}) { + const route=useRouter(); + return ( +
+ {data?.length==stepCount&&
+ +

End of Notes

+ +
} +
+ ) +} + +export default EndScreen \ No newline at end of file diff --git a/app/course/[courseId]/_components/MaterialCardItem.jsx b/app/course/[courseId]/_components/MaterialCardItem.jsx new file mode 100644 index 000000000..17e264161 --- /dev/null +++ b/app/course/[courseId]/_components/MaterialCardItem.jsx @@ -0,0 +1,60 @@ +import { Button } from '@/components/ui/button' +import axios from 'axios' +import { RefreshCcw } from 'lucide-react'; +import Image from 'next/image' +import Link from 'next/link'; +import React, { useState } from 'react' +import { toast } from 'sonner'; + +function MaterialCardItem({item,studyTypeContent,course,refreshData}) { + + const [loading,setLoading]=useState(false); + const GenerateContent=async()=>{ + + toast(' Generating your content...') + setLoading(true) + // console.log(course) + let chapters=''; + course?.courseLayout.chapters.forEach(chapter=>{ + chapters=(chapter.title)+','+chapters + }); + + const result=await axios.post('/api/study-type-content',{ + courseId:course?.courseId, + type:item.name, + chapters:chapters + }); + + setLoading(false); + console.log(result); + refreshData(true); + toast('Your content is ready to view') + } + + return ( + +
+ {studyTypeContent?.[item.type]?.length==0? +

Generate

+ :

Ready

} + + {item.name} +

{item.name}

+

{item.desc}

+ + {studyTypeContent?.[item.type]?.length==0? + + : + + + } +
+ + ) +} + +export default MaterialCardItem \ No newline at end of file diff --git a/app/course/[courseId]/_components/StepProgress.jsx b/app/course/[courseId]/_components/StepProgress.jsx new file mode 100644 index 000000000..a04f9c034 --- /dev/null +++ b/app/course/[courseId]/_components/StepProgress.jsx @@ -0,0 +1,19 @@ +import { Button } from '@/components/ui/button' +import React from 'react' + +function StepProgress({stepCount,setStepCount,data}) { + return ( +
+ {stepCount!=0&& } + {data?.map((item,index)=>( +
+
+ ))} + + +
+ ) +} + +export default StepProgress \ No newline at end of file diff --git a/app/course/[courseId]/_components/StudyMaterialSection.jsx b/app/course/[courseId]/_components/StudyMaterialSection.jsx new file mode 100644 index 000000000..5e7fa69af --- /dev/null +++ b/app/course/[courseId]/_components/StudyMaterialSection.jsx @@ -0,0 +1,76 @@ +import React, { useEffect, useState } from 'react' +import MaterialCardItem from './MaterialCardItem' +import { db } from '@/configs/db' +import axios from 'axios' +import Link from 'next/link'; + +function StudyMaterialSection({courseId,course}) { + + const [studyTypeContent,setStudyTypeContent]=useState(); + const MaterialList=[ + { + name:'Notes/Chapters', + desc:'Read notes to prepare it', + icon:'/notes.png', + path:'/notes', + type:'notes' + }, + { + name:'Flashcard', + desc:'Flashcard to remember the concepts', + icon:'/flashcard.png', + path:'/flashcards', + type:'flashcard' + + }, + { + name:'Quiz', + desc:'Great way to test your knowledge', + icon:'/quiz.png', + path:'/quiz', + type:'quiz' + }, + { + name:'Question/Answer', + desc:'Help to pratice your learning', + icon:'/qa.png', + path:'/question_answer', + type:'qa' + } + ] + + useEffect(()=>{ + GetStudyMaterial(); + },[]) + + const GetStudyMaterial=async()=>{ + const result=await axios.post('/api/study-type',{ + courseId:courseId, + studyType:'ALL' + }) + + console.log(result?.data); + setStudyTypeContent(result.data) + } + + + return ( +
+

Study Plans

+ +
+ {MaterialList.map((item,index)=>( + + + + ))} +
+
+ ) +} + +export default StudyMaterialSection \ No newline at end of file diff --git a/app/course/[courseId]/flashcards/_components/FlashcardItem.jsx b/app/course/[courseId]/flashcards/_components/FlashcardItem.jsx new file mode 100644 index 000000000..95d2695cb --- /dev/null +++ b/app/course/[courseId]/flashcards/_components/FlashcardItem.jsx @@ -0,0 +1,28 @@ +import React from 'react' +import ReactCardFlip from 'react-card-flip' + +function FlashcardItem({isFlipped,handleClick,flashcard}) { + return ( +
+ +
+

{flashcard?.front}

+
+ +
+

{flashcard?.back}

+
+
+
+ ) +} + +export default FlashcardItem \ No newline at end of file diff --git a/app/course/[courseId]/flashcards/page.jsx b/app/course/[courseId]/flashcards/page.jsx new file mode 100644 index 000000000..309b8e48f --- /dev/null +++ b/app/course/[courseId]/flashcards/page.jsx @@ -0,0 +1,85 @@ +"use client" +import axios from 'axios'; +import { useParams } from 'next/navigation' +import React, { useEffect, useState } from 'react' + +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, + } from "@/components/ui/carousel" +import StepProgress from '../_components/StepProgress'; +import FlashcardItem from './_components/FlashcardItem'; + +function Flashcards() { + + const {courseId}=useParams(); + const [flashCards,setFlashCards]=useState([]); + const [isFlipped,setIsFlipped]=useState(); + const [api,setApi]=useState(); + // const [stepCount,setStepCount]=useState(0) + + useEffect(()=>{ + GetFlashCards(); + },[]) + + useEffect(()=>{ + if(!api) + { + return ; + } + api.on('select',()=>{ + setIsFlipped(false); + }) + },[api]) + + const GetFlashCards=async()=>{ + const result=await axios.post('/api/study-type',{ + courseId:courseId, + studyType:'Flashcard' + + }); + + setFlashCards(result?.data); + + } + + const handleClick=(index)=>{ + setIsFlipped(!isFlipped) + } + + return ( +
+

Flashcards

+

Flashcards: The Ultimate Tool to Lock in Concepts!

+ +
+ {/* setStepCount(v)} + stepCount={stepCount} /> */} + + + + {flashCards?.content&&flashCards.content?.map((flashcard,index)=>( + + + + ))} + + + + + + +
+ + +
+ ) +} + +export default Flashcards \ No newline at end of file diff --git a/app/course/[courseId]/notes/page.jsx b/app/course/[courseId]/notes/page.jsx new file mode 100644 index 000000000..457ed8706 --- /dev/null +++ b/app/course/[courseId]/notes/page.jsx @@ -0,0 +1,58 @@ +"use client" +import { Button } from '@/components/ui/button'; +import axios from 'axios' +import { useParams, useRouter } from 'next/navigation' +import React, { useEffect, useState } from 'react' +import StepProgress from '../_components/StepProgress'; +import EndScreen from '../_components/EndScreen'; + +function ViewNotes() { + + const {courseId}=useParams(); + const [notes,setNotes]=useState(); + const [stepCount,setStepCount]=useState(0) + const route=useRouter(); + useEffect(()=>{ + GetNotes(); + },[]) + + const GetNotes=async()=>{ + const result=await axios.post('/api/study-type',{ + courseId:courseId, + studyType:'notes' + }); + + console.log(result?.data); + setNotes(result?.data); + } + + return notes&&( +
+
+ {stepCount!=0&& } + {notes?.map((item,index)=>( +
+
+ ))} + + +
+ + +
+
+ + {notes?.length==stepCount&&
+ +

End of Notes

+ +
} + +
+ +
+ ) +} + +export default ViewNotes \ No newline at end of file diff --git a/app/course/[courseId]/page.jsx b/app/course/[courseId]/page.jsx new file mode 100644 index 000000000..43e1d0b4a --- /dev/null +++ b/app/course/[courseId]/page.jsx @@ -0,0 +1,43 @@ +"use client" +import DashboardHeader from '@/app/dashboard/_components/DashboardHeader'; +import axios from 'axios'; +import { useParams } from 'next/navigation' +import React, { useEffect, useState } from 'react' +import CourseIntroCard from './_components/CourseIntroCard'; +import StudyMaterialSection from './_components/StudyMaterialSection'; +import ChapterList from './_components/ChapterList'; + +function Course() { + const {courseId}=useParams(); + const [course,setCourse]=useState(); + useEffect(()=>{ + GetCourse(); + },[]) + const GetCourse=async()=>{ + const result=await axios.get('/api/courses?courseId='+courseId); + console.log(result); + setCourse(result.data.result); + } + + if(!course) + { + return ; + } + + return ( +
+ +
+ {/* Course Intro */} + + {/* Study Materials Options */} + + {/* Chapter List */} + {/* */} +
+ +
+ ) +} + +export default Course \ No newline at end of file diff --git a/app/course/[courseId]/question_answer/_components/QAItem.jsx b/app/course/[courseId]/question_answer/_components/QAItem.jsx new file mode 100644 index 000000000..9e754a6ad --- /dev/null +++ b/app/course/[courseId]/question_answer/_components/QAItem.jsx @@ -0,0 +1,12 @@ +import React from "react"; + +function QAItem({ questionData }) { + return questionData ? ( +
+

{questionData.question}

+

{questionData.answer}

+
+ ) : null; +} + +export default QAItem; diff --git a/app/course/[courseId]/question_answer/page.jsx b/app/course/[courseId]/question_answer/page.jsx new file mode 100644 index 000000000..4e34b56cb --- /dev/null +++ b/app/course/[courseId]/question_answer/page.jsx @@ -0,0 +1,60 @@ +"use client"; + +import axios from "axios"; +import { useParams } from "next/navigation"; +import React, { useEffect, useState } from "react"; +import StepProgress from "../_components/StepProgress"; +import QAItem from "./_components/QAItem"; +import EndScreen from "../_components/EndScreen"; + +function QAPage() { + const { courseId } = useParams(); + const [qaData, setQAData] = useState({ content: { questions: [] } }); // Default structure + const [stepCount, setStepCount] = useState(0); + + useEffect(() => { + fetchQAs(); + }, [courseId]); + + const fetchQAs = async () => { + console.log(courseId); // Debug courseId + try { + const result = await axios.post("/api/study-type", { + courseId: courseId, + studyType: "Question/Answer", + }); + + console.log("QA Data:", result.data); // Debug response + setQAData(result.data); + } catch (error) { + console.error("Error fetching Q&A data:", error); + setQAData({ content: { questions: [] } }); // Fallback in case of error + } + }; + + return ( +
+

Q&A

+ + setStepCount(value)} + /> + +
+ {qaData?.content?.questions?.length > 0 ? ( + qaData.content.questions[stepCount] && ( + + ) + ) : ( +

Loading questions or no data available.

+ )} +
+ + +
+ ); +} + +export default QAPage; diff --git a/app/course/[courseId]/quiz/_components/QuizCardItem.jsx b/app/course/[courseId]/quiz/_components/QuizCardItem.jsx new file mode 100644 index 000000000..675ba5a15 --- /dev/null +++ b/app/course/[courseId]/quiz/_components/QuizCardItem.jsx @@ -0,0 +1,31 @@ +import { Button } from '@/components/ui/button' +import React, { useEffect, useState } from 'react' + +function QuizCardItem({quiz,userSelectedOption}) { + + const [selectedOption,setSelectedOption]=useState(); + + return quiz&&( +
+

{quiz?.question}

+ +
+ {quiz?.options.map((option,index)=>( +

{setSelectedOption(option); + userSelectedOption(option) + }} + key={index} + variant="outline" + className={`w-full border rounded-full p-3 px-4 text-center + text-lg hover:bg-gray-200 cursor-pointer + ${selectedOption==option&&'bg-primary text-white hover:bg-primary'}`} + >{option}

+ ))} + +
+
+ ) +} + +export default QuizCardItem \ No newline at end of file diff --git a/app/course/[courseId]/quiz/page.jsx b/app/course/[courseId]/quiz/page.jsx new file mode 100644 index 000000000..84b4dc0b8 --- /dev/null +++ b/app/course/[courseId]/quiz/page.jsx @@ -0,0 +1,83 @@ +"use client" +import axios from 'axios' +import { useParams } from 'next/navigation' +import React, { useEffect, useState } from 'react' +import StepProgress from '../_components/StepProgress'; +import QuizCardItem from './_components/QuizCardItem'; +import EndScreen from '../_components/EndScreen'; + +function Quiz() { + const {courseId}=useParams(); + const [quizData,setQuizData]=useState(); + const [stepCount,setStepCount]=useState(0); + const [isCorrectAns,setIsCorrectAnswer]=useState(null); + const [quiz,setQuiz]=useState([]); + const [correctAns,setCorrectAns]=useState(); + useEffect(()=>{ + GetQuiz() + },[courseId]) + + const GetQuiz=async()=>{ + console.log(courseId) + const result=await axios.post('/api/study-type',{ + courseId:courseId, + studyType:'Quiz' + // 'Quiz' + }); + + setQuizData(result.data); + setQuiz(result.data?.content?.questions) + } + + const checkAnswer=(userAnswer,currentQuestion)=>{ + + console.log(currentQuestion?.answer,userAnswer); + if(userAnswer==currentQuestion?.answer) + { + setIsCorrectAnswer(true); + setCorrectAns(currentQuestion?.answer) + return ; + } + setIsCorrectAnswer(false); + } + + useEffect(()=>{ + setCorrectAns(null); + setIsCorrectAnswer(null); + },[stepCount]) + + return ( +
+

Quiz

+ + setStepCount(value)} /> + +
+ {/* {quiz&&quiz.map((item,index)=>( */} + checkAnswer(v,quiz[stepCount])} + /> + {/* ))} */} +
+ + {isCorrectAns==false&&
+
+

Incorrect

+

Correct Answer is : {correctAns}

+
+
} + + {isCorrectAns==true&&
+
+

Correct

+

Yout answer is Correct

+
+
} + + + +
+ ) +} + +export default Quiz \ No newline at end of file diff --git a/app/course/layout.jsx b/app/course/layout.jsx new file mode 100644 index 000000000..99cf64573 --- /dev/null +++ b/app/course/layout.jsx @@ -0,0 +1,16 @@ +import React from 'react' +import DashboardHeader from '../dashboard/_components/DashboardHeader' +import SideBar from "@/app/dashboard/_components/SideBar"; + +function CourseViewLayout({children}) { + return ( +
+ +
+ {children} +
+
+ ) +} + +export default CourseViewLayout \ No newline at end of file diff --git a/app/course/loading.jsx b/app/course/loading.jsx new file mode 100644 index 000000000..e8b6acd8d --- /dev/null +++ b/app/course/loading.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +function loading() { + return ( +
loading...
+ ) +} + +export default loading \ No newline at end of file diff --git a/app/create-file/_components/TopicFile.jsx b/app/create-file/_components/TopicFile.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/app/create-file/page.jsx b/app/create-file/page.jsx new file mode 100644 index 000000000..e31bea52a --- /dev/null +++ b/app/create-file/page.jsx @@ -0,0 +1,121 @@ +"use client"; + +import React, { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Loader2 } from "lucide-react"; + +const FileUpload = () => { + const [uploading, setUploading] = useState(false); + const [feedbackMessage, setFeedbackMessage] = useState(""); + const [extractedText, setExtractedText] = useState(""); + + const handleFileChange = (e) => { + const file = e.target.files[0]; + if (file) { + setFeedbackMessage("File selected: " + file.name); + } + }; + + const handleUpload = async () => { + const fileInput = document.getElementById("file"); + const selectedFile = fileInput.files[0]; + + if (!selectedFile) { + setFeedbackMessage("Please select a file to upload."); + return; + } + + const formData = new FormData(); + formData.append("file", selectedFile); + + setUploading(true); + + try { + const response = await fetch("https://api.pdf.co/v1/pdf/convert/to/text", { + method: "POST", + headers: { + "x-api-key":process.env.CO_PDF_API_KEY , + }, + body: formData, + }); + + if (!response.ok) { + throw new Error("Failed to extract text from the file."); + } + + const result = await response.json(); + + if (result.body) { + setExtractedText(result.body); // API returns extracted text in the 'body' field + setFeedbackMessage("File processed successfully!"); + } else { + setFeedbackMessage("File processed but no text extracted."); + } + } catch (error) { + console.error("Error uploading file:", error); + setFeedbackMessage("Failed to process the file."); + } finally { + setUploading(false); // Hide the loader + } + }; + + return ( + <> +
+

+ Create Your Personalized Study Plans +

+

+ Provide your file to generate study material tailored to your needs. +

+
+
+
+
+

+ Upload Study Plan File +

+ + {feedbackMessage && ( +

+ {feedbackMessage} +

+ )} + + {extractedText && ( +
+

Extracted Text:

+

+ {extractedText} +

+
+ )} +
+
+
+ + ); +}; + +export default FileUpload; diff --git a/app/create/_components/SelectOption.jsx b/app/create/_components/SelectOption.jsx new file mode 100644 index 000000000..12e8507b4 --- /dev/null +++ b/app/create/_components/SelectOption.jsx @@ -0,0 +1,74 @@ +import React, { useState } from 'react'; +import { Book, Dna ,TestTube ,FlaskRound, BarChart, Brain, DollarSign, Globe, Heart, PlusCircle, Monitor } from 'lucide-react'; // Import des icônes nécessaires + +function SelectOption({ selectedStudyType }) { + const Options = [ + { + name: 'Physics', + icon: // Icon "Flask" for Physics + }, + { + name: 'Mathematics', + icon: // Icon "BarChart" for Mathematics + }, + { + name: 'Chemistry', + icon: // Icon "Flask" for Chemistry + }, + { + name: 'Biology', + icon: // Icon "DNA" for Biology + }, + { + name: 'Psychology', + icon: // Icon "Brain" for Psychology + }, + { + name: 'Economics', + icon: // Icon "DollarSign" for Economics + }, + { + name: 'History', + icon: // Icon "Globe" for History + }, + { + name: 'Medicine', + icon: // Icon "Heart" for Medicine + }, + { + name: 'Computer Science', + icon: // Icon "Monitor" for Computer Science + }, + { + name: 'Other', + icon: // Generic icon for "Other" + } + + ]; + + const [selectedOption, setSelectedOption] = useState(); + + return ( +
+
+ {Options.map((option, index) => ( +
{ + setSelectedOption(option.name); + selectedStudyType(option.name); + }} + > +
{option.icon}
{/* Icône affichée ici */} +

{option.name}

+
+ ))} +
+
+ ); +} + +export default SelectOption; diff --git a/app/create/_components/TopicInput.jsx b/app/create/_components/TopicInput.jsx new file mode 100644 index 000000000..2afbf966e --- /dev/null +++ b/app/create/_components/TopicInput.jsx @@ -0,0 +1,42 @@ +import { Textarea } from '@/components/ui/textarea' +import React from 'react' +import { + Select, + Input, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + } from "@/components/ui/select" +import SelectOption from "@/app/create/_components/SelectOption"; + + +function TopicInput({setTopic,setDifficultyLevel}) { + return ( +
+ + +
+

Enter topic or paster the content for which you want to generate study material plan

+ +