feat: complete pragativeer v2 frontend and backend, docker setup, and cbt engine

This commit is contained in:
2026-07-06 17:08:13 +05:30
parent ce01795aa9
commit 426b98ddab
66 changed files with 7949 additions and 363 deletions

View 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>
)
}