digitalassistant / src /components /ICFAIChatbot.jsx
Chaitu2112's picture
Update src/components/ICFAIChatbot.jsx
e4c682c verified
import React, { useState, useEffect, useRef } from "react";
import { Send, Bot, User } from "lucide-react";
import { motion } from "framer-motion";
import { mbaFaqs } from "../data/mbaData";
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "";
export default function ICFAIChatbot() {
// Local program/semester/course data
const programs = [
{ id: "mba_online", name: "Online MBA" },
{ id: "bba_online", name: "Online BBA" },
];
const allSemesters = [
{
id: "sem1",
name: "Semester I",
courses: [
{ id: "mob", name: "Management and Organization Behavior" },
{ id: "ba", name: "Business Analytics" },
{ id: "faf", name: "Foundations of Accounting and Finance" },
{ id: "be", name: "Business Environment" },
{ id: "itm", name: "IT for Managers" },
],
},
{ id: "sem2", name: "Semester II", courses: [] },
{ id: "sem3", name: "Semester III", courses: [] },
{ id: "sem4", name: "Semester IV", courses: [] },
];
const faqs = mbaFaqs || [];
// Flow state: program -> semester -> course -> units
const [flowStep, setFlowStep] = useState("program");
const [selectedProgram, setSelectedProgram] = useState(null);
const [selectedSemester, setSelectedSemester] = useState(null);
const [selectedCourse, setSelectedCourse] = useState(null);
const [openUnit, setOpenUnit] = useState(null); // when a unit is selected, sidebar shows unit content
// Chat state
const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState("");
const [isTyping, setIsTyping] = useState(false);
const messagesEndRef = useRef(null);
useEffect(() => {
if (messages.length === 0) addBotMessage("Hello! 👋 Welcome — choose a program from the left.");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const addBotMessage = (text, delay = 300) => {
setIsTyping(true);
setTimeout(() => {
setMessages((m) => [...m, { type: "bot", text }]);
setIsTyping(false);
scrollToBottom();
}, delay);
};
const addUserMessage = (text) => {
setMessages((m) => [...m, { type: "user", text }]);
scrollToBottom();
};
const scrollToBottom = () => messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
// Flow handlers
const onSelectProgram = (p) => {
addUserMessage(p.name);
setSelectedProgram(p);
setSelectedSemester(null);
setSelectedCourse(null);
setOpenUnit(null);
setFlowStep("semester");
addBotMessage(`Great — ${p.name} selected. Choose a semester.`);
};
const onSelectSemester = (s) => {
addUserMessage(s.name);
setSelectedSemester(s);
setSelectedCourse(null);
setOpenUnit(null);
setFlowStep("course");
addBotMessage(`You selected ${s.name}. Pick a course.`);
};
const onSelectCourse = (c) => {
addUserMessage(c.name);
setSelectedCourse(c);
setOpenUnit(null);
if (c.id === "itm") {
setFlowStep("units");
addBotMessage(`You selected ${c.name}. Choose a unit from the left.`);
} else {
setFlowStep("chat");
addBotMessage(`You selected ${c.name}. Ask academic questions or open Units if available.`);
}
};
const onSelectUnit = (n) => setOpenUnit(n);
const onBack = () => {
if (openUnit) {
setOpenUnit(null);
return;
}
if (flowStep === "units") {
setFlowStep("course");
setSelectedCourse(null);
return;
}
if (flowStep === "course") {
setFlowStep("semester");
setSelectedSemester(null);
return;
}
if (flowStep === "semester") {
setFlowStep("program");
setSelectedProgram(null);
return;
}
};
const onFaqClick = (faq) => {
// show FAQ answer in chat (keeps sidebar focused on FAQs)
addUserMessage(faq.question);
addBotMessage(faq.answer, 400);
};
const handleSendMessage = async () => {
if (!inputValue.trim()) return;
const q = inputValue.trim();
addUserMessage(q);
setInputValue("");
setIsTyping(true);
try {
const fd = new FormData();
fd.append("user_message", q);
const res = await fetch(`${API_BASE_URL}/digital_icfai_chat`, { method: "POST", body: fd });
const data = await res.json().catch(() => ({}));
const answer = (data && (data.answer || data.response)) || "I couldn't find an exact answer in the FAQs.";
addBotMessage(answer, 200);
} catch (err) {
addBotMessage("Sorry, knowledge server unreachable. Try again later.");
} finally {
setIsTyping(false);
}
};
// Sidebar renderer — only one section visible at a time, and unit content expands inside sidebar
const SidebarContent = () => {
if (!flowStep || flowStep === "program") {
return (
<>
<h3 className="font-bold text-lg">Programs</h3>
<div className="mt-4 space-y-3">
{programs.map((p) => (
<button key={p.id} onClick={() => onSelectProgram(p)} className="w-full text-left px-4 py-3 rounded-lg bg-white border border-gray-100 hover:bg-blue-50">
{p.name}
</button>
))}
</div>
</>
);
}
if (flowStep === "semester") {
return (
<>
<div className="flex items-center justify-between">
<h3 className="font-bold text-lg">Semesters</h3>
<button onClick={onBack} className="text-sm text-gray-600">Back</button>
</div>
<div className="mt-4 space-y-3">
{allSemesters.map((s) => (
<button key={s.id} onClick={() => onSelectSemester(s)} className="w-full text-left px-4 py-3 rounded-lg bg-white border border-gray-100 hover:bg-blue-50">
{s.name}
</button>
))}
</div>
</>
);
}
if (flowStep === "course") {
return (
<>
<div className="flex items-center justify-between">
<h3 className="font-bold text-lg">Courses in {selectedSemester?.name}</h3>
<button onClick={onBack} className="text-sm text-gray-600">Back</button>
</div>
<div className="mt-4 space-y-3">
{(selectedSemester?.courses || []).length > 0 ? (
selectedSemester.courses.map((c) => (
<button key={c.id} onClick={() => onSelectCourse(c)} className="w-full text-left px-4 py-3 rounded-lg bg-white border border-gray-100 hover:bg-blue-50">
{c.name}
</button>
))
) : (
<div className="text-sm text-gray-500">No courses listed for this semester</div>
)}
</div>
</>
);
}
if (flowStep === "units") {
// If no unit selected, show unit buttons. If unit selected, show its FAQ(s) in the sidebar content area.
if (!openUnit) {
return (
<>
<div className="flex items-center justify-between">
<h3 className="font-bold text-lg">ITM Units</h3>
<button onClick={onBack} className="text-sm text-gray-600">Back</button>
</div>
<div className="mt-4 space-y-3">
{[1,2,3,4,5].map((n) => (
<button key={n} onClick={() => onSelectUnit(n)} className="w-full text-left px-4 py-3 rounded-lg bg-white border border-gray-100 hover:bg-blue-50">
Unit {n}
</button>
))}
</div>
</>
);
}
// openUnit: render the unit content inside the sidebar (fixed header + scrollable FAQ list)
return (
<>
<div className="flex items-center justify-between">
<h3 className="font-bold text-lg">Unit {openUnit}</h3>
<div className="space-x-2">
<button onClick={() => setOpenUnit(null)} className="text-sm text-gray-600">Back to Units</button>
<button onClick={onBack} className="text-sm text-gray-600">Back</button>
</div>
</div>
<div className="mt-4 flex-1 overflow-auto pr-2">
{openUnit === 1 ? (
<div className="space-y-2">
{faqs.map((q,i) => (
<button key={i} onClick={() => onFaqClick(q)} className="w-full text-left px-3 py-2 rounded-md bg-white border border-gray-100 hover:bg-gray-50">
{q.question}
</button>
))}
</div>
) : (
<div>
{faqs[openUnit - 2] ? (
<button onClick={() => onFaqClick(faqs[openUnit - 2])} className="w-full text-left px-3 py-2 rounded-md bg-white border border-gray-100 hover:bg-gray-50">
{faqs[openUnit - 2].question}
</button>
) : (
<div className="text-xs text-gray-400">No FAQ assigned</div>
)}
</div>
)}
</div>
</>
);
}
return null;
};
return (
<div className="flex w-full h-screen min-w-0 bg-gradient-to-br from-indigo-500 via-violet-500 to-pink-500">
{/* Sidebar: 600px, vertical layout, header fixed, content scrolls, footer fixed */}
<aside style={{ width: 600, minWidth: 600 }} className="flex-shrink-0 bg-white border border-gray-200 rounded-l-xl p-6 shadow-lg h-screen flex flex-col">
{/* Sidebar header */}
<div className="flex items-start justify-between mb-3">
<div>
<h2 className="text-xl font-bold">ICFAI Online Assistant</h2>
<p className="text-xs text-gray-500">Select program → semester → course → unit</p>
</div>
<div>
<button onClick={() => { setFlowStep("program"); setSelectedProgram(null); setSelectedSemester(null); setSelectedCourse(null); setOpenUnit(null); }} className="text-sm text-gray-600">Reset</button>
</div>
</div>
{/* Sidebar content area: this is the scrollable region */}
<div className="flex-1 overflow-auto">
<SidebarContent />
</div>
{/* Sidebar footer (fixed) */}
<div className="mt-4">
<button onClick={() => { setFlowStep("program"); setSelectedProgram(null); setSelectedSemester(null); setSelectedCourse(null); setOpenUnit(null); }} className="w-full bg-blue-600 text-white px-4 py-3 rounded-lg">Main Menu</button>
</div>
</aside>
{/* Right panel: always shows chat */}
<div className="flex-1 flex flex-col h-screen bg-white rounded-r-xl shadow-lg overflow-hidden min-w-0">
<motion.div className="bg-gradient-to-r from-blue-600 to-blue-700 text-white p-6 flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="bg-white p-3 rounded-full"><Bot size={22} className="text-blue-600" /></div>
<div>
<h3 className="font-bold text-lg">ICFAI Online Assistant</h3>
<p className="text-sm text-blue-100">Chat / Answers</p>
</div>
</div>
<div className="flex items-center gap-3">
<button onClick={onBack} className="text-sm bg-white/10 px-4 py-2 rounded-full">← Go Back</button>
<button onClick={() => { setFlowStep("program"); setSelectedProgram(null); setSelectedSemester(null); setSelectedCourse(null); setOpenUnit(null); }} className="text-sm bg-white/10 px-4 py-2 rounded-full">Main Menu</button>
</div>
</motion.div>
{/* Chat messages */}
<div className="flex-1 overflow-auto p-8 space-y-6" style={{ paddingBottom: 140 }}>
{messages.map((m, i) => (
<div key={i} className={`flex ${m.type === "user" ? "justify-end" : "justify-start"}`}>
{m.type !== "user" && <div className="bg-blue-600 p-3 rounded-full h-12 w-12 mr-3"><Bot size={18} className="text-white" /></div>}
<div className={`${m.type === "user" ? "bg-blue-600 text-white" : "bg-white text-gray-800"} p-4 rounded-2xl shadow-sm max-w-[85%]`}>
{m.text}
</div>
{m.type === "user" && <div className="bg-gray-300 p-2 rounded-full h-9 w-9 ml-3"><User size={16} className="text-gray-700" /></div>}
</div>
))}
{isTyping && (
<div className="flex gap-3">
<div className="bg-blue-600 p-3 rounded-full h-12 w-12"><Bot size={18} className="text-white" /></div>
<div className="bg-white p-3 rounded-2xl shadow-sm border border-gray-100">
<div className="flex gap-1">
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: "150ms" }} />
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: "300ms" }} />
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
{/* Input area */}
<div className="p-6 bg-gray-50 border-t border-gray-200" style={{ position: 'relative', zIndex: 2 }}>
<div className="flex gap-3 items-center">
<input value={inputValue} onChange={(e) => setInputValue(e.target.value)} placeholder="Ask academic questions about the selected course..." className="flex-1 p-4 border border-gray-300 rounded-2xl focus:outline-none focus:ring-2 focus:ring-blue-600 text-sm" />
<button onClick={handleSendMessage} disabled={!inputValue.trim()} className="bg-blue-600 text-white p-4 rounded-full disabled:opacity-50"><Send size={20} /></button>
</div>
<p className="text-xs text-gray-400 mt-3 text-center">Powered by ICFAI Online</p>
</div>
</div>
</div>
);
}