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 index 92ec67790..f83b4ff1a 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,55 @@ -# About The Hackathon -The MoroccoAI InnovAI Hackathon is a unique opportunity for AI enthusiasts, professionals, and innovators to collaborate and create transformative AI-based solutions addressing real-life challenges in Morocco and across Africa. As part of the annual MoroccoAI Annual Conference, this hackathon is set under the theme “Driving the Future of Innovation Through AI”, inspiring participants to harness AI’s capabilities to make a meaningful societal impact. Participants will join teams to develop Proof of Concepts (PoCs) using applications or APIs that address challenges in various domains. Education, Healthcare, Environment, Finance or Customer Services . - -In line with MoroccoAI’s mission, this hackathon centers around “Driving the Future of Innovation Through AI”. AI has the power to redefine industries, address community needs, and propel sustainable growth. Through this event, participants will dive into AI’s potential by developing impactful solutions that address challenges unique to Morocco and Africa in fields such as agriculture, education, health, and finance, fostering innovation in response to real-world needs. - -# The Challenge -Connect with the MoroccoAI community, join teams and brainstorm ideas then come up with a project that leverages AI in 5 areas of focus: -* Innovation -* Healthcare -* Environment -* Finance -* CustomerServices - -# Mentorship -Join the Hackathon server on discord and meet the mentors to learn more about their proposed projects. - -# Why should you participate in this Hackathon? -* Hands-on experience in AI project development that targets relevant issues in Morocco and Africa. -* Mentorship and networking opportunities with experts and peers in the AI community. -* Showcase their solutions to a jury of AI specialists at the awards ceremony, creating visibility and opportunities for further development. -* Win great prizes offered by MoroccoAI's sponsors -* Obtain your MoroccoAI certificate of recognition - -# For more information -https://morocco.ai/events/conferences/MoroccoAI-Conference-2024/pages/hackathon.html + + +# Easy Study Web App + +This project is a web application designed to enhance student learning by generating quizzes, flashcards, courses , questions answers based on the provided topics. + +--- + +## Abstract + +### Background and Problem Statement +In today's fast-paced education environment, students often struggle to find efficient ways to revise and consolidate their knowledge. Manually creating study materials such as quizzes or flashcards can be time-consuming and ineffective. Additionally, locating relevant supplemental resources like guided courses is a fragmented process. + +### Impact and Proposed Solution +The **AI Study web app** addresses these issues by automating the creation of personalized study aids. Using advanced AI techniques and efficient database management, the app generates topic-specific quizzes, flashcards, q&a and curates educational courses recommendations, saving students valuable time and enhancing their learning experience. + +### Project Outcomes and Deliverables +- **Quizzes and Flashcards**: Automatically generated based on user-submitted topics or keywords. +- **Educational Course Recommendations**: AI-curated relevant courses for deeper understanding. +- **Interactive User Interface**: A responsive and intuitive interface for students to access study aids effortlessly. +- **Integration with Next.js, Dizzle ORM, and Ingest**: Ensures scalability, efficiency, and real-time data handling. + + + +## Technologies Used + +- **Next.js**: React framework for server-side rendering and static site generation. +- **Dizzle ORM**: Object-relational mapping (ORM) for database management. +- **Ingest**: For data ingestion and streaming. +- **Gemini API**: For retrieving personalized educational content and resources. +- **OpenAI**: For generating quizzes, flashcards, and natural language processing tasks. +- Other tools: Node.js, npm. + +--- + +## Getting Started + +Follow the steps below to set up and run the project locally. + +### Prerequisites + +Ensure you have the following installed on your system: + +- [Node.js](https://nodejs.org/) (v16 or later) +- [npm](https://www.npmjs.com/) +- A database supported by Dizzle ORM (e.g., PostgreSQL, MySQL, etc.) + +--- + +## Setup and Installation + +1. **Clone the Repository** + ```bash + git clone https://github.com/youssefhergal/2024-InnovAI-Hackathon.git + cd easy-study-web-app-main 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..e69de29bb 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

+ +