import { useState, useEffect, useRef } from "react"; import axios from "axios"; import Webcam from "react-webcam"; import { jsPDF } from "jspdf"; import { Chart as ChartJS, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, PointElement, LineElement, RadialLinearScale, BarElement, Filler } from "chart.js"; import { Pie, Line, Radar, Bar } from "react-chartjs-2"; ChartJS.register( ArcElement, Tooltip, Legend, CategoryScale, LinearScale, PointElement, LineElement, RadialLinearScale, BarElement, Filler ); // ───────────────────────────────────────── // 🏆 UPGRADE 1: Badge system // ───────────────────────────────────────── function getBadge(score) { if (score >= 90) return { label: "Expert", emoji: "🏆", color: "text-yellow-400", bg: "bg-yellow-400/10 border-yellow-400/40" }; if (score >= 75) return { label: "Advanced", emoji: "💎", color: "text-blue-400", bg: "bg-blue-400/10 border-blue-400/40" }; if (score >= 60) return { label: "Intermediate", emoji: "⭐", color: "text-green-400", bg: "bg-green-400/10 border-green-400/40" }; if (score >= 40) return { label: "Beginner", emoji: "🌱", color: "text-orange-400", bg: "bg-orange-400/10 border-orange-400/40" }; return { label: "Novice", emoji: "🎯", color: "text-gray-400", bg: "bg-gray-400/10 border-gray-400/40" }; } function BadgeCard({ score }) { const badge = getBadge(score); return (
{badge.emoji}

{badge.label}

Performance Level

{score}% overall

); } // ───────────────────────────────────────── // 💬 UPGRADE 2: AI Coach with score memory // ───────────────────────────────────────── function AICoach({ lastScores }) { const [messages, setMessages] = useState([ { role: "ai", text: lastScores ? `Hi! I'm your AI Interview Coach 👋 I can see your latest scores — your final score was ${lastScores.final_score}%. Ask me anything and I'll give you personalised advice!` : "Hi! I'm your AI Interview Coach 👋 Ask me anything about interview preparation, tips, or how to improve your score." } ]); const [input, setInput] = useState(""); const [thinking, setThinking] = useState(false); const bottomRef = useRef(null); useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); const sendMessage = async () => { const userMsg = input.trim(); if (!userMsg) return; setMessages(prev => [...prev, { role: "user", text: userMsg }]); setInput(""); setThinking(true); try { const res = await axios.post("http://127.0.0.1:8000/coach-chat", { message: userMsg, last_scores: lastScores || null // ✅ sends scores for personalised advice }); setMessages(prev => [...prev, { role: "ai", text: res.data.reply }]); } catch { setMessages(prev => [...prev, { role: "ai", text: "Sorry, I couldn't connect to the AI right now. Please check your backend." }]); } setThinking(false); }; // Quick prompt chips const chips = lastScores ? ["How can I improve my score?", "Tips for eye contact", "Reduce filler words", "Body language advice"] : ["Tell me about STAR method", "How to handle nerves", "Salary negotiation tips", "Common mistakes to avoid"]; return (
{/* Score context banner */} {lastScores && (
📊 Last session: Final {lastScores.final_score}% · Grammar {lastScores.grammar_score}% · Eye Contact {lastScores.eye_contact_score}% · Emotion {lastScores.emotion_score}%
)} {/* Messages */}
{messages.map((m, i) => (
{m.text}
))} {thinking && (
Coach is thinking...
)}
{/* Quick chips */}
{chips.map((c, i) => ( ))}
{/* Input */}
setInput(e.target.value)} onKeyDown={e => e.key === "Enter" && sendMessage()} />
); } // ───────────────────────────────────────── // 🎯 UPGRADE 3: AI Practice Mode // ───────────────────────────────────────── function PracticeMode() { const [question, setQuestion] = useState(null); const [loadingQ, setLoadingQ] = useState(false); const [role, setRole] = useState("Software Engineer"); const [difficulty, setDifficulty] = useState("medium"); const [category, setCategory] = useState("behavioral"); const [timer, setTimer] = useState(null); const [timeLeft, setTimeLeft] = useState(0); const [recording, setRecording] = useState(false); const intervalRef = useRef(null); const webcamRef = useRef(null); const fetchQuestion = async () => { setLoadingQ(true); setQuestion(null); setTimer(null); clearInterval(intervalRef.current); try { const res = await axios.post("http://127.0.0.1:8000/generate-question", { role, difficulty, category }); setQuestion(res.data); } catch { setQuestion({ question: "Tell me about a challenging project you worked on and how you overcame obstacles.", tip: "Use the STAR method: Situation, Task, Action, Result.", time_limit: 90 }); } setLoadingQ(false); }; const startTimer = () => { if (!question) return; setTimeLeft(question.time_limit); setRecording(true); clearInterval(intervalRef.current); intervalRef.current = setInterval(() => { setTimeLeft(prev => { if (prev <= 1) { clearInterval(intervalRef.current); setRecording(false); return 0; } return prev - 1; }); }, 1000); }; const stopTimer = () => { clearInterval(intervalRef.current); setRecording(false); setTimeLeft(0); }; const timerColor = timeLeft > 30 ? "#22c55e" : timeLeft > 10 ? "#eab308" : "#ef4444"; return (

🎯 Practice Mode

Get AI-generated interview questions tailored to your role and practice with the webcam.

{/* Controls */}
setRole(e.target.value)} placeholder="e.g. Data Scientist" />
{/* Question card */}
{question ? (
{difficulty.toUpperCase()} {category.toUpperCase()}

❓ {question.question}

💡 Tip: {question.tip}
{/* Timer */}
{!recording ? ( ) : ( )} {(recording || timeLeft > 0) && (
{String(Math.floor(timeLeft / 60)).padStart(2, "0")}:{String(timeLeft % 60).padStart(2, "0")}
{recording && ( ● REC )}
)} {timeLeft === 0 && !recording && question && ( ✅ Time's up! )}
) : (

🎯

Click "Get Question" to generate an AI interview question

)}
{/* Webcam */}

📷 Live Preview

Practice your body language and eye contact

); } // ───────────────────────────────────────── // MAIN APP // ───────────────────────────────────────── export default function App() { const [page, setPage] = useState("analytics"); const [file, setFile] = useState(null); const [videoURL, setVideoURL] = useState(null); const [result, setResult] = useState(null); const [history, setHistory] = useState([]); const [loading, setLoading] = useState(false); const [llmFeedback, setLlmFeedback] = useState(null); const [llmLoading, setLlmLoading] = useState(false); // ✅ UPGRADE 2: last scores for AI Coach memory const [lastScores, setLastScores] = useState(null); useEffect(() => { const saved = localStorage.getItem("interviewHistory"); if (saved) setHistory(JSON.parse(saved)); const savedScores = localStorage.getItem("lastScores"); if (savedScores) setLastScores(JSON.parse(savedScores)); }, []); const saveHistory = (data) => { const entry = { ...data, timestamp: new Date().toISOString() }; const newHistory = [entry, ...history]; setHistory(newHistory); localStorage.setItem("interviewHistory", JSON.stringify(newHistory)); // ✅ save scores for AI Coach const scores = { final_score: data.final_score, grammar_score: data.grammar_score, emotion_score: data.emotion_score, eye_contact_score: data.eye_contact_score, keyword_score: data.keyword_score, speech_rate: data.speech_rate, filler_count: data.filler_count }; setLastScores(scores); localStorage.setItem("lastScores", JSON.stringify(scores)); }; const analyzeInterview = async () => { if (!file) { alert("Upload a video first"); return; } const formData = new FormData(); formData.append("file", file); try { setLoading(true); setLlmFeedback(null); const res = await axios.post("http://127.0.0.1:8000/analyze", formData); setResult(res.data); saveHistory(res.data); await fetchLLMFeedback(res.data); } catch { alert("Analysis failed. Make sure your backend is running."); } setLoading(false); }; const fetchLLMFeedback = async (data) => { try { setLlmLoading(true); const res = await axios.post("http://127.0.0.1:8000/llm-feedback", { final_score: data.final_score, grammar_score: data.grammar_score, emotion_score: data.emotion_score, eye_contact_score: data.eye_contact_score, keyword_score: data.keyword_score, speech_rate: data.speech_rate, filler_count: data.filler_count, text: data.text }); setLlmFeedback(res.data.llm_feedback); } catch { setLlmFeedback("Could not generate AI feedback. Check your Anthropic API key in api.py."); } setLlmLoading(false); }; const downloadPDF = () => { const doc = new jsPDF(); doc.setFontSize(18); doc.text("Interview Performance Report", 20, 20); doc.setFontSize(12); doc.text(`Final Score: ${result.final_score}`, 20, 40); doc.text(`Grammar Score: ${result.grammar_score}`, 20, 50); doc.text(`Emotion Score: ${result.emotion_score}`, 20, 60); doc.text(`Eye Contact Score: ${result.eye_contact_score}`, 20, 70); doc.text(`Keyword Score: ${result.keyword_score}`, 20, 80); doc.text(`Speech Rate: ${result.speech_rate}`, 20, 90); doc.setFontSize(13); doc.text("Transcript:", 20, 105); doc.setFontSize(10); doc.text(result.text || "N/A", 20, 113, { maxWidth: 160 }); const feedbackY = 113 + Math.ceil((result.text?.length || 0) / 90) * 6 + 10; doc.setFontSize(13); doc.text("AI Coach Feedback:", 20, feedbackY); doc.setFontSize(10); doc.text(llmFeedback || result.ai_feedback || "N/A", 20, feedbackY + 8, { maxWidth: 160 }); doc.save("interview_report.pdf"); }; const emotionChart = result && { labels: Object.keys(result.emotions), datasets: [{ data: Object.values(result.emotions), backgroundColor: ["#22c55e","#3b82f6","#eab308","#ef4444","#a855f7"] }] }; const timeline = result && { labels: result.emotion_timeline.map((_, i) => `Frame ${i}`), datasets: [{ label: "Emotion Timeline", data: result.emotion_timeline.map(e => e === "happy" ? 3 : e === "neutral" ? 2 : 1), borderColor: "#38bdf8", fill: true, backgroundColor: "rgba(56,189,248,0.1)" }] }; const radarData = result && { labels: ["Grammar", "Emotion", "Eye Contact", "Keywords", "Speech"], datasets: [{ data: [result.grammar_score, result.emotion_score, result.eye_contact_score, result.keyword_score, result.speech_rate], backgroundColor: "rgba(59,130,246,0.2)", borderColor: "#3b82f6" }] }; // ─── UPGRADE 4: History trend chart data ─── const trendData = history.length > 1 && { labels: history.map((_, i) => `Session ${history.length - i}`).reverse(), datasets: [ { label: "Final Score", data: [...history].reverse().map(h => h.final_score), borderColor: "#3b82f6", backgroundColor: "rgba(59,130,246,0.1)", fill: true, tension: 0.4 }, { label: "Grammar", data: [...history].reverse().map(h => h.grammar_score), borderColor: "#22c55e", backgroundColor: "transparent", tension: 0.4 }, { label: "Eye Contact", data: [...history].reverse().map(h => h.eye_contact_score), borderColor: "#a855f7", backgroundColor: "transparent", tension: 0.4 } ] }; return (
{/* SIDEBAR */}

Interview AI

{["analytics","practice","history","coach"].map(p => ( ))}
{/* MAIN */}
{/* ── ANALYTICS ── */} {page === "analytics" && ( <>

Smart Interview Analyzer

{ setFile(e.target.files[0]); setVideoURL(URL.createObjectURL(e.target.files[0])); }} />
{videoURL && (

Interview Recording

)} {loading && (
⏳ Analyzing your interview... please wait
)} {result && ( <> {/* ✅ UPGRADE 1: Badge + Score Cards row */}
{/* Charts */}

Emotion Distribution

Emotion Timeline

Performance Radar

{/* Transcript */}

📝 Transcript

{result.text || "No transcript available."}

{/* Claude AI Feedback */}

🤖 AI Coach Feedback Powered by Claude AI

{llmLoading && (
✨ Claude is analyzing your performance...
)} {llmFeedback && !llmLoading && (
{llmFeedback}
)}
{/* Quick Feedback + PDF */}

Quick Feedback

    {Array.isArray(result.feedback) ? result.feedback.map((f, i) =>
  • {f}
  • ) :
  • {result.ai_feedback}
  • }
)} )} {/* ── PRACTICE — UPGRADE 3 ── */} {page === "practice" && } {/* ── HISTORY — UPGRADE 4 ── */} {page === "history" && (

🕒 Interview History

{/* Score trend chart */} {trendData ? (

📈 Score Trend Across Sessions

) : history.length === 1 ? (
Complete at least 2 sessions to see your score trend chart.
) : null} {/* Session cards */} {history.length === 0 &&

No interviews analyzed yet.

}
{history.map((h, i) => { const badge = getBadge(h.final_score); const date = h.timestamp ? new Date(h.timestamp).toLocaleDateString("en-IN", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }) : "—"; return (

Session #{history.length - i}

{badge.emoji} {badge.label}

{date}

Final: {h.final_score}% Grammar: {h.grammar_score}% Eye Contact: {h.eye_contact_score}% Emotion: {h.emotion_score}%
); })}
)} {/* ── AI COACH — UPGRADE 2 ── */} {page === "coach" && (

🤖 AI Interview Coach

Ask anything — interview tips, how to improve your score, body language advice, and more. {lastScores && Your last session scores are loaded for personalised advice.}

)}
); } function Card({ title, value }) { return (

{title}

{value}

); }