From 77c2eb49f593ace1a4e777055d4bbd753c1df214 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 10 Jul 2025 20:07:44 +0800 Subject: [PATCH 1/8] Add module chatbot interface --- .../[courseId]/modules/[moduleId]/page.tsx | 6 +- components/chatbot/ModuleChatBot.tsx | 103 ++++++++++++++++++ 2 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 components/chatbot/ModuleChatBot.tsx diff --git a/app/courses/[courseId]/modules/[moduleId]/page.tsx b/app/courses/[courseId]/modules/[moduleId]/page.tsx index c4844b4..f3a9958 100644 --- a/app/courses/[courseId]/modules/[moduleId]/page.tsx +++ b/app/courses/[courseId]/modules/[moduleId]/page.tsx @@ -1,4 +1,5 @@ import ModuleDetail from "@/components/organisation/courses/ModuleDetail"; +import ModuleChatBot from "@/components/chatbot/ModuleChatBot"; import { getAuthUser } from "@/lib/auth"; export default async function ModulePage({ @@ -8,11 +9,12 @@ export default async function ModulePage({ }) { const user = await getAuthUser(); const isAdmin = user?.organisation?.role === "admin"; - const { moduleId } = await params; + const { courseId, moduleId } = await params; return ( -
+
+
); } diff --git a/components/chatbot/ModuleChatBot.tsx b/components/chatbot/ModuleChatBot.tsx new file mode 100644 index 0000000..75353d5 --- /dev/null +++ b/components/chatbot/ModuleChatBot.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { useState } from "react"; + +interface ModuleChatbotProps { + courseId: string; + moduleId: string; +} + +export default function ModuleChatbot({ + courseId, + moduleId, +}: ModuleChatbotProps) { + const [question, setQuestion] = useState(""); + const [chat, setChat] = useState< + { type: "user" | "assistant"; content: string }[] + >([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const sendQuestion = async (e: React.FormEvent) => { + e.preventDefault(); + if (!question.trim()) return; + setError(null); + setLoading(true); + + // Add user question to chat + setChat((prev) => [...prev, { type: "user", content: question }]); + + try { + const res = await fetch("/api/chatbot/ask", { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "include", // for cookies/session + body: JSON.stringify({ courseId, moduleId, question }), + }); + const data = await res.json(); + + if (data?.success && data.answer) { + setChat((prev) => [ + ...prev, + { type: "assistant", content: data.answer }, + ]); + } else { + setError(data.message || "Something went wrong."); + } + } catch (err: any) { + setError("Failed to get response. Please try again."); + } + setLoading(false); + setQuestion(""); + }; + + return ( +
+

Module Assistant

+
+ {chat.length === 0 && ( +
+ Ask a question about this module! +
+ )} + {chat.map((msg, i) => ( +
+ + {msg.content} + +
+ ))} + {loading && ( +
Assistant is typing…
+ )} +
+ {error &&
{error}
} +
+ setQuestion(e.target.value)} + disabled={loading} + /> + +
+
+ ); +} From bfe8f60598dabd0dfa0a6c7cc95f4c46b187123a Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 10 Jul 2025 20:11:01 +0800 Subject: [PATCH 2/8] Show chat history --- components/chatbot/ModuleChatBot.tsx | 29 ++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/components/chatbot/ModuleChatBot.tsx b/components/chatbot/ModuleChatBot.tsx index 75353d5..751aefb 100644 --- a/components/chatbot/ModuleChatBot.tsx +++ b/components/chatbot/ModuleChatBot.tsx @@ -1,12 +1,14 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; interface ModuleChatbotProps { courseId: string; moduleId: string; } +type ChatMessage = { type: "user" | "assistant"; content: string }; + export default function ModuleChatbot({ courseId, moduleId, @@ -18,13 +20,36 @@ export default function ModuleChatbot({ const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + useEffect(() => { + const fetchLogs = async () => { + try { + const res = await fetch("/api/chatbot/logs", { + credentials: "include", + }); + const data = await res.json(); + if (data.success && Array.isArray(data.logs)) { + const logMessages: ChatMessage[] = []; + data.logs + .reverse() + .forEach((log: { question: string; answer: string }) => { + logMessages.push({ type: "user", content: log.question }); + logMessages.push({ type: "assistant", content: log.answer }); + }); + setChat(logMessages); + } + } catch (err) { + // Ignore errors in loading logs + } + }; + fetchLogs(); + }, []); + const sendQuestion = async (e: React.FormEvent) => { e.preventDefault(); if (!question.trim()) return; setError(null); setLoading(true); - // Add user question to chat setChat((prev) => [...prev, { type: "user", content: question }]); try { From 8167c8d08a5195daba83aa8621d3ec68810a1d89 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 10 Jul 2025 20:18:09 +0800 Subject: [PATCH 3/8] Show chat history only for that module --- components/chatbot/ModuleChatBot.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/chatbot/ModuleChatBot.tsx b/components/chatbot/ModuleChatBot.tsx index 751aefb..9b8f4e5 100644 --- a/components/chatbot/ModuleChatBot.tsx +++ b/components/chatbot/ModuleChatBot.tsx @@ -24,7 +24,10 @@ export default function ModuleChatbot({ const fetchLogs = async () => { try { const res = await fetch("/api/chatbot/logs", { + method: "POST", credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ courseId, moduleId }), }); const data = await res.json(); if (data.success && Array.isArray(data.logs)) { From fbf9b68a9f3f3255459f0d9cf70cb5beb25a240d Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 10 Jul 2025 20:24:09 +0800 Subject: [PATCH 4/8] update module to show chat only if not quiz --- .../[courseId]/modules/[moduleId]/page.tsx | 5 +- .../organisation/courses/ModuleDetail.tsx | 139 +++++++++--------- 2 files changed, 74 insertions(+), 70 deletions(-) diff --git a/app/courses/[courseId]/modules/[moduleId]/page.tsx b/app/courses/[courseId]/modules/[moduleId]/page.tsx index f3a9958..5c7337c 100644 --- a/app/courses/[courseId]/modules/[moduleId]/page.tsx +++ b/app/courses/[courseId]/modules/[moduleId]/page.tsx @@ -12,9 +12,8 @@ export default async function ModulePage({ const { courseId, moduleId } = await params; return ( -
- - +
+
); } diff --git a/components/organisation/courses/ModuleDetail.tsx b/components/organisation/courses/ModuleDetail.tsx index 21b4bd6..09260b2 100644 --- a/components/organisation/courses/ModuleDetail.tsx +++ b/components/organisation/courses/ModuleDetail.tsx @@ -1,8 +1,8 @@ "use client"; import React, { useEffect } from "react"; -import { useParams } from "next/navigation"; import { useState } from "react"; +import ModuleChatBot from "@/components/chatbot/ModuleChatBot"; export interface QuizOption { id: number; @@ -31,14 +31,14 @@ export interface ModuleDetailData { } interface Props { + courseId: string; moduleId: string; isAdmin: boolean; } -export default function ModuleDetail({ moduleId, isAdmin }: Props) { +export default function ModuleDetail({ courseId, moduleId, isAdmin }: Props) { const [data, setData] = React.useState(null); const [enrolled, setEnrolled] = React.useState(false); - const { courseId } = useParams() as { courseId: string }; const [answers, setAnswers] = useState>({}); const [moduleStatus, setModuleStatus] = useState(null); const [courseCompleted, setCourseCompleted] = useState(false); @@ -175,75 +175,80 @@ export default function ModuleDetail({ moduleId, isAdmin }: Props) { if (data.module_type !== "quiz") { const finalUrl = `http://localhost:4000${data.file_url}`; return ( -
-
- {moduleStatus === "not_started" && !courseCompleted && ( - - )} +
+
+
+ {moduleStatus === "not_started" && !courseCompleted && ( + + )} + + {moduleStatus === "in_progress" && !courseCompleted && ( + + )} - {moduleStatus === "in_progress" && !courseCompleted && ( - + {moduleStatus === "completed" && !courseCompleted && ( + + )} +
+

{data.title}

+

{data.description}

+ + {data.module_type === "video" && data.file_url && ( +