diff --git a/lecture-pulse/src/components/AISummaryCard.jsx b/lecture-pulse/src/components/AISummaryCard.jsx new file mode 100644 index 0000000..09a9ded --- /dev/null +++ b/lecture-pulse/src/components/AISummaryCard.jsx @@ -0,0 +1,136 @@ +import { useState } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Sparkles, ThumbsUp, AlertTriangle, BookOpen, Loader2, RefreshCw } from "lucide-react"; +import { generateAISummary } from "@/utils/aiSummary"; + +const sentimentColor = { + positive: "text-emerald-600 bg-emerald-50 border-emerald-200", + neutral: "text-blue-600 bg-blue-50 border-blue-200", + negative: "text-red-600 bg-red-50 border-red-200", +}; + +const AISummaryCard = ({ lecture, analytics, feedback }) => { + const [summary, setSummary] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const handleGenerate = async () => { + setLoading(true); + setError(null); + try { + const result = await generateAISummary(lecture, analytics, feedback); + setSummary(result); + } catch (e) { + setError("Failed to generate summary. Check your API key or try again."); + } finally { + setLoading(false); + } + }; + + return ( + + + +
+ + AI Feedback Summary +
+ {summary && ( + + {summary.sentiment} + + )} +
+
+ + {!summary && !loading && ( +
+ +

+ Generate an AI-powered summary of this lecture's feedback +

+ +
+ )} + + {loading && ( +
+ +

Analyzing feedback with Llama 3.3...

+
+ )} + + {error && ( +
+ {error} +
+ )} + + {summary && ( +
+

{summary.overall}

+ + {summary.positives?.length > 0 && ( +
+
+ + Positives +
+
    + {summary.positives.map((p, i) => ( +
  • + {p} +
  • + ))} +
+
+ )} + + {summary.concerns?.length > 0 && ( +
+
+ + Concerns +
+
    + {summary.concerns.map((c, i) => ( +
  • + {c} +
  • + ))} +
+
+ )} + + {summary.followUp?.length > 0 && ( +
+
+ + Follow-up +
+
    + {summary.followUp.map((f, i) => ( +
  • + {f} +
  • + ))} +
+
+ )} + + +
+ )} +
+
+ ); +}; + +export default AISummaryCard; \ No newline at end of file diff --git a/lecture-pulse/src/pages/Analytics.jsx b/lecture-pulse/src/pages/Analytics.jsx index ed30730..b0b50bf 100644 --- a/lecture-pulse/src/pages/Analytics.jsx +++ b/lecture-pulse/src/pages/Analytics.jsx @@ -9,6 +9,7 @@ import UnderstandingChart from '@/components/charts/UnderstandingChart'; import AttentionChart from '@/components/charts/AttentionChart'; import ConfusionChart from '@/components/charts/ConfusionChart'; import FeedbackTimeline from '@/components/charts/FeedbackTimeline'; +import AISummaryCard from '@/components/AISummaryCard'; import { generateLecturePDF } from "@/utils/pdfReport"; import { useRef } from "react"; import html2canvas from "html2canvas"; @@ -34,6 +35,7 @@ const Analytics = () => { return canvas.toDataURL('image/png'); }; + const [feedback, setFeedback] = useState([]); const loadData = useCallback(async () => { if (!sessionId) return; @@ -60,7 +62,7 @@ const Analytics = () => { // Assume empty for now. Real app would wait for students. // Uncomment below to force mock data for specific tests or add a "Generate Mock Data" button } - + setFeedback(feedback); const calculatedAnalytics = calculateAnalytics(feedback); setAnalytics(calculatedAnalytics); }, [sessionId, navigate]); @@ -290,6 +292,7 @@ const Analytics = () => { + )} diff --git a/lecture-pulse/src/utils/aiSummary.js b/lecture-pulse/src/utils/aiSummary.js new file mode 100644 index 0000000..86cd2b4 --- /dev/null +++ b/lecture-pulse/src/utils/aiSummary.js @@ -0,0 +1,60 @@ +export const generateAISummary = async (lecture, analytics, feedback) => { + const apiKey = import.meta.env.VITE_GROQ_API_KEY; + console.log("KEY:", apiKey); + + const feedbackSample = feedback.slice(0, 30).map(f => ({ + understanding: f.understanding, + attention: f.attention, + confusionTime: f.confusionTime, + comment: f.comment || null, + })); + + const prompt = ` +You are an educational analytics assistant. Analyze this lecture feedback and provide a concise summary. + +Lecture: "${lecture.topic}" (${lecture.subject}, ${lecture.duration} mins) + +Metrics: +- Total responses: ${analytics.totalResponses} +- Understanding score: ${analytics.understandingScore}% +- Attention score: ${analytics.attentionScore}% +- Overall effectiveness: ${Math.round((analytics.understandingScore + analytics.attentionScore) / 2)}% + +Feedback sample: +${JSON.stringify(feedbackSample, null, 2)} + +Provide a JSON response with exactly this structure: +{ + "overall": "2-3 sentence overall summary of the lecture session", + "positives": ["up to 3 positive themes from feedback"], + "concerns": ["up to 3 areas of confusion or concern"], + "followUp": ["up to 3 suggested follow-up topics or actions"], + "sentiment": "positive" +} + +Return only valid JSON, no markdown, no explanation. +`; + + const response = await fetch("https://api.groq.com/openai/v1/chat/completions", { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + model: "llama-3.3-70b-versatile", + messages: [{ role: "user", content: prompt }], + temperature: 0.4, + max_tokens: 600, + }), + }); + + if (!response.ok) { + const err = await response.text(); + throw new Error(err); + } + + const data = await response.json(); + const text = data.choices[0].message.content.trim(); + return JSON.parse(text); +}; \ No newline at end of file diff --git a/lecture-pulse/vite.config.js b/lecture-pulse/vite.config.js index 2844729..88de904 100644 --- a/lecture-pulse/vite.config.js +++ b/lecture-pulse/vite.config.js @@ -1,13 +1,18 @@ -import { defineConfig } from 'vite' +import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' -// https://vite.dev/config/ -export default defineConfig({ - plugins: [react(), tailwindcss()], - resolve: { - alias: { - '@': '/src', +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), '') + return { + plugins: [react(), tailwindcss()], + resolve: { + alias: { + '@': '/src', + }, }, - }, -}) + define: { + 'import.meta.env.VITE_GROQ_API_KEY': JSON.stringify(env.VITE_GROQ_API_KEY), + }, + } +}) \ No newline at end of file