- {isAttempted ?
:
}
+ {isAttempted ?
:
}
@@ -200,28 +214,34 @@ export default function AIDailyPracticePage() {
) : isExpired && !isAttempted && !dpp.pdf_url ? (
) : dpp.pdf_url ? (
-
+
View PDF}>
+
+
) : isAttempted ? (
<>
{!isExpired && (
-
-
-
+
Retake}>
+
+
+
+
)}
>
) : (
-
-
-
+
Launch Practice Test}>
+
+
+
+
)}
@@ -335,3 +355,4 @@ export default function AIDailyPracticePage() {
)
}
+
diff --git a/src/components/AI/TutorChat.tsx b/src/components/AI/TutorChat.tsx
index 30e2ecf..e7a3d66 100644
--- a/src/components/AI/TutorChat.tsx
+++ b/src/components/AI/TutorChat.tsx
@@ -3,7 +3,7 @@
import { fetchWithAuth } from "@/lib/apiClient";
import { useState, useRef, useEffect } from "react"
import { Card, CardContent } from "@/components/ui/card"
-import { Send, Loader2, BrainCircuit, Sparkles } from "lucide-react"
+import { Send, Loader2, BrainCircuit, Sparkles, PlusCircle, MessageSquare } from "lucide-react"
import ReactMarkdown from "react-markdown"
import remarkMath from "remark-math"
import rehypeKatex from "rehype-katex"
@@ -16,6 +16,13 @@ interface Message {
content: string
}
+interface ChatSession {
+ id: string;
+ title: string;
+ timestamp: number;
+ messages: Message[];
+}
+
const QUICK_PROMPTS = [
"Explain Newton's laws with an example",
"What is the difference between speed and velocity?",
@@ -23,10 +30,11 @@ const QUICK_PROMPTS = [
"Solve: Integration of x² from 0 to 3",
]
+const INITIAL_MSG: Message = { role: "assistant", content: "Hello! I'm your PrepPilot AI. Ask me anything — from concept explanations to step-by-step problem solving for JEE & NEET topics." }
+
export function TutorChat() {
- const [messages, setMessages] = useState
([
- { role: "assistant", content: "Hello! I'm your PrepPilot AI. Ask me anything — from concept explanations to step-by-step problem solving for JEE & NEET topics." }
- ])
+ const [sessions, setSessions] = useState([])
+ const [activeSessionId, setActiveSessionId] = useState("")
const [input, setInput] = useState("")
const [streaming, setStreaming] = useState(false)
const messagesEndRef = useRef(null)
@@ -36,21 +44,50 @@ export function TutorChat() {
// Load from localStorage on mount
useEffect(() => {
if (userId) {
+ const savedV2 = localStorage.getItem("ai_tutor_chats_v2_" + userId)
+ if (savedV2) {
+ try {
+ const parsed = JSON.parse(savedV2)
+ if (parsed && parsed.length > 0) {
+ setSessions(parsed)
+ setActiveSessionId(parsed[0].id)
+ return
+ }
+ } catch (e) {}
+ }
+
+ // Fallback to legacy
const saved = localStorage.getItem("ai_tutor_chat_" + userId)
if (saved) {
try {
- setMessages(JSON.parse(saved))
+ const parsedMsgs = JSON.parse(saved)
+ if (parsedMsgs && parsedMsgs.length > 1) {
+ const legacySession = {
+ id: Date.now().toString(),
+ title: "Previous Chat",
+ timestamp: Date.now(),
+ messages: parsedMsgs
+ }
+ setSessions([legacySession])
+ setActiveSessionId(legacySession.id)
+ return
+ }
} catch (e) {}
}
+
+ // Default empty session
+ handleNewChat()
}
}, [userId])
- // Save to localStorage whenever messages change
+ // Save to localStorage whenever sessions change
useEffect(() => {
- if (userId && messages.length > 1) { // Only save if there's actual history beyond initial message
- localStorage.setItem("ai_tutor_chat_" + userId, JSON.stringify(messages))
+ if (userId && sessions.length > 0) {
+ localStorage.setItem("ai_tutor_chats_v2_" + userId, JSON.stringify(sessions))
}
- }, [messages, userId])
+ }, [sessions, userId])
+
+ const activeMessages = sessions.find(s => s.id === activeSessionId)?.messages || [INITIAL_MSG]
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
@@ -58,16 +95,33 @@ export function TutorChat() {
useEffect(() => {
scrollToBottom()
- }, [messages])
+ }, [activeMessages])
+
+ const handleNewChat = () => {
+ const newId = Date.now().toString()
+ setSessions(prev => [{
+ id: newId,
+ title: "New Chat",
+ timestamp: Date.now(),
+ messages: [INITIAL_MSG]
+ }, ...prev])
+ setActiveSessionId(newId)
+ }
const sendMessage = async (text: string) => {
if (!text.trim() || streaming || userLoading || !userId) return
setInput("")
- setMessages(prev => [...prev, { role: "user", content: text.trim() }])
- setMessages(prev => [...prev, { role: "assistant", content: "" }])
setStreaming(true)
+ setSessions(prev => prev.map(s => {
+ if (s.id === activeSessionId) {
+ const title = s.messages.length === 1 ? text.trim().substring(0, 30) + "..." : s.title;
+ return { ...s, title, messages: [...s.messages, { role: "user", content: text.trim() }, { role: "assistant", content: "" }] }
+ }
+ return s
+ }))
+
try {
const res = await fetchWithAuth("/api/ai-coach/chat/stream", {
method: "POST",
@@ -84,23 +138,29 @@ export function TutorChat() {
const { done, value } = await reader.read()
if (done) break
const chunk = decoder.decode(value, { stream: true })
- setMessages(prev => {
- const msgs = [...prev]
- msgs[msgs.length - 1].content += chunk
- return msgs
- })
+
+ setSessions(prev => prev.map(s => {
+ if (s.id === activeSessionId) {
+ const msgs = [...s.messages]
+ msgs[msgs.length - 1].content += chunk
+ return { ...s, messages: msgs }
+ }
+ return s
+ }))
}
} catch {
- // Graceful fallback — don't expose internal errors
const fallback = "I'm having trouble connecting right now. Please try again in a moment. In the meantime, you can browse your **Study Plan** or review your **Mistake Queue** for offline practice."
const words = fallback.split(" ")
for (let i = 0; i < words.length; i++) {
await new Promise(r => setTimeout(r, 40))
- setMessages(prev => {
- const msgs = [...prev]
- msgs[msgs.length - 1].content += (i === 0 ? "" : " ") + words[i]
- return msgs
- })
+ setSessions(prev => prev.map(s => {
+ if (s.id === activeSessionId) {
+ const msgs = [...s.messages]
+ msgs[msgs.length - 1].content += (i === 0 ? "" : " ") + words[i]
+ return { ...s, messages: msgs }
+ }
+ return s
+ }))
}
} finally {
setStreaming(false)
@@ -113,8 +173,32 @@ export function TutorChat() {
}
return (
-
-
+
+ {/* Sidebar for chat history */}
+
+
+
+ Past Chats
+ {sessions.map(s => (
+
+ ))}
+
+
+
+ {/* Main Chat Area */}
+
{/* Chat Header */}
@@ -126,11 +210,18 @@ export function TutorChat() {
Online · JEE & NEET specialist
+
{/* Messages */}
- {messages.map((msg, idx) => (
+ {activeMessages.map((msg, idx) => (
{/* Quick Prompts — shown only when only 1 message (initial) */}
- {messages.length === 1 && (
+ {activeMessages.length === 1 && (
{QUICK_PROMPTS.map(p => (