diff --git a/.gitignore b/.gitignore index bfa36b2..467becb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ node_modules/ .env*.local .DS_Store .vercel +*.tsbuildinfo diff --git a/app/(dashboard)/essay-grader/[id]/page.tsx b/app/(dashboard)/essay-grader/[id]/page.tsx new file mode 100644 index 0000000..eb6091c --- /dev/null +++ b/app/(dashboard)/essay-grader/[id]/page.tsx @@ -0,0 +1,132 @@ +import { createServerClient } from "@supabase/ssr" +import { cookies } from "next/headers" +import { notFound } from "next/navigation" +import Link from "next/link" +import { ArrowLeft, CheckCircle2, XCircle, Lightbulb } from "lucide-react" +import { Button } from "@/components/ui/button" +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card" + +export default async function EssayEvaluationPage({ + params +}: { + params: { id: string } +}) { + const cookieStore = await cookies() + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name) { return cookieStore.get(name)?.value }, + }, + } + ) + + const { data: { user } } = await supabase.auth.getUser() + + if (!user) { + return notFound() + } + + const { data: evaluation, error } = await supabase + .from('essay_evaluations') + .select('*') + .eq('id', params.id) + .eq('user_id', user.id) + .single() + + if (error || !evaluation) { + return notFound() + } + + const feedback = evaluation.feedback + + return ( +
+
+ + + +
+

Evaluation Results

+

+ Graded on {new Date(evaluation.created_at).toLocaleDateString()} +

+
+
+ +
+ + + Estimated Grade + + +
{feedback.grade}
+
+
+ + + + Strengths & Weaknesses + + +
+

+ Pros +

+
    + {feedback.pros.map((pro: string, i: number) => ( +
  • {pro}
  • + ))} +
+
+
+

+ Cons +

+
    + {feedback.cons.map((con: string, i: number) => ( +
  • {con}
  • + ))} +
+
+
+
+
+ + + + + Actionable Suggestions + + Specific feedback to improve your essay. + + + {feedback.suggestions.map((suggestion: any, i: number) => ( +
+
+ "{suggestion.quote}" +
+

+ {suggestion.comment} +

+
+ ))} +
+
+ + + + Original Essay + + +
+ {evaluation.content} +
+
+
+
+ ) +} diff --git a/app/(dashboard)/essay-grader/page.tsx b/app/(dashboard)/essay-grader/page.tsx new file mode 100644 index 0000000..cbf418f --- /dev/null +++ b/app/(dashboard)/essay-grader/page.tsx @@ -0,0 +1,166 @@ +"use client" + +import { useState, useEffect } from "react" +import { useRouter } from "next/navigation" +import { createBrowserClient } from "@supabase/ssr" +import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { Textarea } from "@/components/ui/textarea" +import { Loader2, FileSignature, CheckCircle } from "lucide-react" + +export default function EssayGraderPage() { + const router = useRouter() + const [content, setContent] = useState("") + const [rubric, setRubric] = useState("") + const [loading, setLoading] = useState(false) + const [evaluations, setEvaluations] = useState([]) + const [fetching, setFetching] = useState(true) + + const supabase = createBrowserClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ) + + useEffect(() => { + fetchEvaluations() + }, []) + + async function fetchEvaluations() { + setFetching(true) + const { data: { user } } = await supabase.auth.getUser() + if (user) { + const { data, error } = await supabase + .from('essay_evaluations') + .select('id, grade, created_at, content') + .eq('user_id', user.id) + .order('created_at', { ascending: false }) + + if (!error && data) { + setEvaluations(data) + } + } + setFetching(false) + } + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + if (!content.trim() || loading) return + + setLoading(true) + + try { + const res = await fetch("/api/essays/grade", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ content, rubric }), + }) + + if (res.ok) { + const data = await res.json() + if (data.id) { + router.push(`/essay-grader/${data.id}`) + } + } else { + const errorData = await res.json() + alert(errorData.error || "Failed to evaluate essay.") + } + } catch (err) { + console.error(err) + alert("Connection error.") + } finally { + setLoading(false) + } + } + + return ( +
+
+

AI Essay Grader

+

+ Paste your essay and an optional rubric to receive an instant grade, strengths, weaknesses, and actionable feedback. +

+
+ +
+
+ + + New Evaluation + Submit a new essay for AI grading. + +
+ +
+ +