feat: complete pragativeer v2 frontend and backend, docker setup, and cbt engine
This commit is contained in:
170
src/app/dashboard/ai-coach/page.tsx
Normal file
170
src/app/dashboard/ai-coach/page.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
||||
import { StudyPlanCard } from "@/components/AI/StudyPlanCard"
|
||||
import { ReadinessGauge } from "@/components/AI/ReadinessGauge"
|
||||
import { CollegePredictorCard } from "@/components/AI/CollegePredictorCard"
|
||||
import { TutorChat } from "@/components/AI/TutorChat"
|
||||
import { ReferralCard } from "@/components/AI/ReferralCard"
|
||||
import { ProgressShare } from "@/components/AI/ProgressShare"
|
||||
import { MistakeReviewCard, MistakePayload } from "@/components/AI/MistakeReviewCard"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { BrainCircuit, BookOpen, Calculator, Target, RefreshCw, CheckCircle2 } from "lucide-react"
|
||||
|
||||
// Hardcode mock UUID for demo fetching
|
||||
const USER_ID = "b0e17378-864d-439e-8806-708dc48a891b"
|
||||
|
||||
export default function AICoachDashboard() {
|
||||
const [mistakes, setMistakes] = useState<MistakePayload[]>([])
|
||||
const [currentIndex, setCurrentIndex] = useState(0)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const fetchMistakes = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await fetch(`http://localhost:8000/api/mistakes/today?user_id=${USER_ID}`)
|
||||
if (!res.ok) throw new Error("Failed to fetch")
|
||||
const data = await res.json()
|
||||
setMistakes(data)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
// If backend fails, fallback to empty array
|
||||
setMistakes([])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchMistakes()
|
||||
}, [])
|
||||
|
||||
const submitReview = async (mistakeId: string, selectedAnswer: string) => {
|
||||
const res = await fetch("http://localhost:8000/api/mistakes/review", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ mistake_id: mistakeId, selected_answer: selectedAnswer })
|
||||
})
|
||||
|
||||
if (!res.ok) throw new Error("Failed to submit review")
|
||||
return await res.json()
|
||||
}
|
||||
|
||||
const handleNext = () => {
|
||||
if (currentIndex < mistakes.length - 1) {
|
||||
setCurrentIndex(prev => prev + 1)
|
||||
} else {
|
||||
// Session finished
|
||||
setCurrentIndex(mistakes.length) // Push out of bounds to trigger complete state
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-8 max-w-6xl mx-auto space-y-10 animate-in fade-in duration-500">
|
||||
<header className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight flex items-center gap-3">
|
||||
<BrainCircuit className="text-primary" size={32}/> AI Coach
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-1 font-medium">Your personalized SM-2 spaced repetition and analytics engine.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Summary Metrics */}
|
||||
<section className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Card className="border-border shadow-sm">
|
||||
<CardContent className="p-5 flex items-center gap-4">
|
||||
<div className="p-3 bg-secondary rounded-xl text-primary"><Calculator size={20}/></div>
|
||||
<div>
|
||||
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">Physics Errors</p>
|
||||
<h3 className="text-2xl font-bold">{mistakes.filter(m => m.question.subject === 'Physics').length}</h3>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-border shadow-sm">
|
||||
<CardContent className="p-5 flex items-center gap-4">
|
||||
<div className="p-3 bg-secondary rounded-xl text-emerald-500"><BookOpen size={20}/></div>
|
||||
<div>
|
||||
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">Chemistry Errors</p>
|
||||
<h3 className="text-2xl font-bold">{mistakes.filter(m => m.question.subject === 'Chemistry').length}</h3>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-border shadow-sm">
|
||||
<CardContent className="p-5 flex items-center gap-4">
|
||||
<div className="p-3 bg-secondary rounded-xl text-blue-500"><Target size={20}/></div>
|
||||
<div>
|
||||
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">Maths Errors</p>
|
||||
<h3 className="text-2xl font-bold">{mistakes.filter(m => m.question.subject === 'Maths').length}</h3>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
{/* Predictive Intelligence */}
|
||||
<section className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
||||
<ReadinessGauge />
|
||||
<CollegePredictorCard />
|
||||
</section>
|
||||
|
||||
{/* Mistake Review Workspace */}
|
||||
<section>
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<h2 className="text-xl font-semibold tracking-tight">Daily Mistake Review Queue</h2>
|
||||
<span className="text-sm font-bold text-muted-foreground bg-secondary px-3 py-1 rounded-full">
|
||||
{currentIndex < mistakes.length ? `${currentIndex + 1} / ${mistakes.length}` : "Finished"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<Card className="p-12 flex items-center justify-center border-dashed">
|
||||
<RefreshCw className="animate-spin text-muted-foreground mr-3" /> Loading queue...
|
||||
</Card>
|
||||
) : mistakes.length === 0 ? (
|
||||
<Card className="p-12 text-center border-dashed bg-secondary/20">
|
||||
<CheckCircle2 size={48} className="mx-auto text-green-500 mb-4 opacity-50" />
|
||||
<h3 className="text-xl font-bold mb-2">You're all caught up!</h3>
|
||||
<p className="text-muted-foreground">No mistakes scheduled for review today. Go take a test!</p>
|
||||
</Card>
|
||||
) : currentIndex < mistakes.length ? (
|
||||
<MistakeReviewCard
|
||||
data={mistakes[currentIndex]}
|
||||
onNext={handleNext}
|
||||
onSubmitReview={submitReview}
|
||||
/>
|
||||
) : (
|
||||
<Card className="p-12 text-center border-dashed bg-primary/5 border-primary/20">
|
||||
<BrainCircuit size={48} className="mx-auto text-primary mb-4 opacity-80" />
|
||||
<h3 className="text-xl font-bold mb-2 text-primary">Session Complete</h3>
|
||||
<p className="text-muted-foreground mb-6">Great job clearing your error queue! The SM-2 engine has scheduled your next reviews.</p>
|
||||
<Button onClick={() => window.location.reload()} variant="outline" className="rounded-xl">Check for New Errors</Button>
|
||||
</Card>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Heatmap & Timeline */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8 pt-8 border-t border-border">
|
||||
<section>
|
||||
<StudyPlanCard />
|
||||
</section>
|
||||
<section>
|
||||
<MistakeHeatmap />
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Tutor Chat Workspace */}
|
||||
<section className="pt-8 border-t border-border">
|
||||
<TutorChat />
|
||||
</section>
|
||||
|
||||
{/* Growth & Sharing Hub */}
|
||||
<section className="grid grid-cols-1 lg:grid-cols-2 gap-8 pt-8 border-t border-border mt-8">
|
||||
<ReferralCard />
|
||||
<ProgressShare />
|
||||
</section>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user