+
+
+
+
+ Code Editor
+
+
+ {LANGUAGES.map(lang => (
+ {lang}
+ ))}
+
-
-
-
-
+
+
+
+ {/* Code validation warning */}
+ {!codeValidation.isValid && (
+
+
+
{codeValidation.message}
-
Performance Analytics
-
Track progress and identify areas for improvement
+ )}
+
+
+
+
- {/* Footer */}
-
-
© 2024 InterXAI - Master Every Interview with AI Coaching
-
+ {/* Final Submit Section */}
+
+
+
+
Interview Progress
+
+ {submittedQuestions.length} of {questions.length} questions completed •
+ Score: {score}/{questions.length * 10}
+
+
+
+ {submittedQuestions.length > 0 && (
+
+ Final Submit
+
+ )}
+
+
+
diff --git a/src/pages/InterviewSession.jsx b/src/pages/InterviewSession.jsx
index abf833127..a7a7a6fad 100644
--- a/src/pages/InterviewSession.jsx
+++ b/src/pages/InterviewSession.jsx
@@ -336,37 +336,17 @@
//
// );
// };
-
// export default InterviewSession;
-import React, { useEffect, useState, useRef, useCallback } from "react";
+import React, { useEffect, useState, useRef } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { getAuthToken, fetchWithToken } from "../utils/handleToken";
-import {
- Loader2,
- ArrowLeft,
- Send,
- MessageCircle,
- User,
- Mic,
- MicOff,
- Volume2,
- VolumeX,
- Phone,
- PhoneOff,
- Settings,
- Timer,
- Pause,
- Play,
- SkipForward,
- AlertTriangle
-} from "lucide-react";
+import { Loader2, Send, MessageCircle, User, Mic, MicOff, Volume2, VolumeX, Play, Sparkles } from "lucide-react";
const InterviewSession = () => {
const { interviewId } = useParams();
const navigate = useNavigate();
const token = getAuthToken();
- // Original state from your code
const [loading, setLoading] = useState(true);
const [sessionId, setSessionId] = useState(null);
const [currentQuestion, setCurrentQuestion] = useState(null);
@@ -378,137 +358,116 @@ const InterviewSession = () => {
const [endTime, setEndTime] = useState(null);
const [interviewActive, setInterviewActive] = useState(false);
- // New interactive features state
- const [isRecording, setIsRecording] = useState(false);
+ // Voice states
const [isListening, setIsListening] = useState(false);
const [speechEnabled, setSpeechEnabled] = useState(true);
- const [autoSpeak, setAutoSpeak] = useState(true);
- const [voice, setVoice] = useState(null);
- const [availableVoices, setAvailableVoices] = useState([]);
- const [speechRate, setSpeechRate] = useState(1.0);
- const [volume, setVolume] = useState(1.0);
- const [interviewTimer, setInterviewTimer] = useState(0);
- const [isPaused, setIsPaused] = useState(false);
- const [showSettings, setShowSettings] = useState(false);
- const [confidence, setConfidence] = useState(0);
- const [transcriptHistory, setTranscriptHistory] = useState([]);
- const [questionLoading, setQuestionLoading] = useState(false);
+ const [isSpeaking, setIsSpeaking] = useState(false);
- // Refs
const hasInitialized = useRef(false);
const chatEndRef = useRef(null);
+ const [questionLoading, setQuestionLoading] = useState(false);
const recognitionRef = useRef(null);
- const synthesisRef = useRef(null);
- const timerRef = useRef(null);
- const interviewStartTime = useRef(null);
-
const API_URL = import.meta.env.VITE_API_URL;
- // Initialize speech synthesis
- useEffect(() => {
- if ('speechSynthesis' in window) {
- const synth = window.speechSynthesis;
- synthesisRef.current = synth;
-
- const updateVoices = () => {
- const voices = synth.getVoices();
- setAvailableVoices(voices);
- if (!voice && voices.length > 0) {
- // Prefer female voice for interview
- const preferredVoice = voices.find(v =>
- v.name.includes('Female') || v.name.includes('Samantha') || v.name.includes('Karen')
- ) || voices[0];
- setVoice(preferredVoice);
- }
- };
-
- updateVoices();
- synth.onvoiceschanged = updateVoices;
- }
- }, [voice]);
-
// Initialize speech recognition
useEffect(() => {
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
- const recognition = new SpeechRecognition();
-
- recognition.continuous = true;
- recognition.interimResults = true;
- recognition.maxAlternatives = 1;
- recognition.lang = 'en-US';
+ recognitionRef.current = new SpeechRecognition();
+ recognitionRef.current.continuous = true;
+ recognitionRef.current.interimResults = true;
+ recognitionRef.current.lang = 'en-US';
- recognition.onstart = () => {
- setIsListening(true);
- };
-
- recognition.onresult = (event) => {
+ recognitionRef.current.onresult = (event) => {
let finalTranscript = '';
let interimTranscript = '';
- let maxConfidence = 0;
for (let i = event.resultIndex; i < event.results.length; i++) {
- const result = event.results[i];
- const transcript = result[0].transcript;
- const confidence = result[0].confidence || 0;
-
- if (result.isFinal) {
+ const transcript = event.results[i][0].transcript;
+ if (event.results[i].isFinal) {
finalTranscript += transcript;
- maxConfidence = Math.max(maxConfidence, confidence);
} else {
interimTranscript += transcript;
}
}
- if (finalTranscript) {
- setAnswer(prev => prev + finalTranscript);
- setConfidence(maxConfidence);
- setTranscriptHistory(prev => [...prev, { text: finalTranscript, confidence: maxConfidence, timestamp: Date.now() }]);
- }
+ setAnswer(prev => {
+ const withoutInterim = prev.replace(/\[.*?\]/g, '');
+ return finalTranscript ? withoutInterim + finalTranscript : withoutInterim + `[${interimTranscript}]`;
+ });
};
- recognition.onerror = (event) => {
- console.error('Speech recognition error:', event.error);
+ recognitionRef.current.onend = () => {
setIsListening(false);
- setIsRecording(false);
+ setAnswer(prev => prev.replace(/\[.*?\]/g, ''));
};
- recognition.onend = () => {
+ recognitionRef.current.onerror = (event) => {
+ console.error('Speech recognition error:', event.error);
setIsListening(false);
- if (isRecording) {
- recognition.start(); // Restart if still recording
- }
};
-
- recognitionRef.current = recognition;
}
- }, [isRecording]);
+ }, []);
- // Timer effect
+ // Text-to-speech function
+ const speakText = (text) => {
+ if (!speechEnabled || !text) return;
+
+ window.speechSynthesis.cancel();
+
+ const utterance = new SpeechSynthesisUtterance(text);
+ utterance.rate = 0.9;
+ utterance.pitch = 1;
+ utterance.volume = 0.8;
+
+ utterance.onstart = () => setIsSpeaking(true);
+ utterance.onend = () => setIsSpeaking(false);
+ utterance.onerror = () => setIsSpeaking(false);
+
+ window.speechSynthesis.speak(utterance);
+ };
+
+ // Auto-speak new questions
useEffect(() => {
- if (!interviewStartTime.current) {
- interviewStartTime.current = Date.now();
+ if (currentQuestion && !questionLoading && speechEnabled) {
+ setTimeout(() => speakText(currentQuestion), 500);
}
+ }, [currentQuestion, questionLoading, speechEnabled]);
+
+ // Toggle speech recognition
+ const toggleListening = () => {
+ if (!recognitionRef.current) return;
- if (!isPaused) {
- timerRef.current = setInterval(() => {
- setInterviewTimer(Math.floor((Date.now() - interviewStartTime.current) / 1000));
- }, 1000);
+ if (isListening) {
+ recognitionRef.current.stop();
+ } else {
+ recognitionRef.current.start();
+ setIsListening(true);
}
+ };
- return () => {
- if (timerRef.current) {
- clearInterval(timerRef.current);
- }
- };
- }, [isPaused]);
+ // Toggle text-to-speech
+ const toggleSpeech = () => {
+ setSpeechEnabled(!speechEnabled);
+ if (speechEnabled) {
+ window.speechSynthesis.cancel();
+ setIsSpeaking(false);
+ }
+ };
- // Auto-scroll to bottom (from your original code)
+ // Replay current question
+ const replayQuestion = () => {
+ if (currentQuestion) {
+ speakText(currentQuestion);
+ }
+ };
+
+ // ✅ Auto-scroll to bottom
useEffect(() => {
chatEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [chatHistory]);
- // Original fetch interview details & initialize (from your code)
+ // ✅ Fetch interview details & initialize (but don't start session yet)
useEffect(() => {
if (!token) {
navigate("/login");
@@ -518,7 +477,7 @@ const InterviewSession = () => {
if (hasInitialized.current) return;
hasInitialized.current = true;
- const initSession = async () => {
+ const initInterviewDetails = async () => {
try {
const interviewData = await fetchWithToken(
`${API_URL}/interview/get-all-interviews/`,
@@ -551,31 +510,19 @@ const InterviewSession = () => {
}
setInterviewActive(true);
+ setLoading(false);
- const data = await fetchWithToken(
- `${API_URL}/interview/interview-session-initializer/${interviewId}/`,
- token,
- null,
- "POST"
- );
-
- if (!data || !data.session_id) {
- setError(data?.error || "Failed to start interview.");
- } else {
- setSessionId(data.session_id);
- setCurrentQuestion(data.question);
- }
+ // Don't initialize session yet - wait for user to click start
} catch (err) {
- setError("Error initializing session.");
- } finally {
+ setError("Error loading interview details.");
setLoading(false);
}
};
- initSession();
- }, [interviewId, token, navigate, API_URL]);
+ initInterviewDetails();
+ }, [interviewId, token, navigate]);
- // Auto-end interview when endTime reached (from your original code)
+ // ✅ Auto-end interview when endTime reached
useEffect(() => {
if (!endTime) return;
const timer = setInterval(() => {
@@ -588,75 +535,20 @@ const InterviewSession = () => {
return () => clearInterval(timer);
}, [endTime]);
- // Redirect to DSA platform after completion (from your original code)
+ // ✅ Redirect to DasInterViewPlatform after completion
useEffect(() => {
if (completed && sessionId) {
navigate(`/dsa-interview-platform/${sessionId}`);
}
- }, [completed, sessionId, navigate]);
-
- // Text-to-speech function
- const speak = useCallback((text) => {
- if (!speechEnabled || !synthesisRef.current || !voice) return;
-
- // Cancel any ongoing speech
- synthesisRef.current.cancel();
-
- const utterance = new SpeechSynthesisUtterance(text);
- utterance.voice = voice;
- utterance.rate = speechRate;
- utterance.volume = volume;
- utterance.pitch = 1.0;
-
- utterance.onstart = () => {
- console.log('Speech started');
- };
-
- utterance.onend = () => {
- console.log('Speech ended');
- };
-
- synthesisRef.current.speak(utterance);
- }, [speechEnabled, voice, speechRate, volume]);
-
- // Auto-speak new questions
- useEffect(() => {
- if (currentQuestion && autoSpeak && !questionLoading && !showWelcome) {
- const questionText = `Question ${chatHistory.length + 1}: ${currentQuestion}`;
- setTimeout(() => speak(questionText), 500);
- }
- }, [currentQuestion, autoSpeak, speak, chatHistory.length, questionLoading, showWelcome]);
+ }, [completed, sessionId]);
- // Speech recognition controls
- const startRecording = () => {
- if (recognitionRef.current && !isRecording) {
- setIsRecording(true);
- recognitionRef.current.start();
- }
- };
-
- const stopRecording = () => {
- if (recognitionRef.current && isRecording) {
- setIsRecording(false);
- recognitionRef.current.stop();
- }
- };
-
- const toggleRecording = () => {
- if (isRecording) {
- stopRecording();
- } else {
- startRecording();
- }
- };
-
- // Original submit answer function (from your code) with voice enhancements
+ // ✅ Submit answer
const handleNext = async () => {
if (!answer.trim()) return;
- // Stop any ongoing speech
- if (synthesisRef.current) {
- synthesisRef.current.cancel();
+ // Stop listening if active
+ if (isListening && recognitionRef.current) {
+ recognitionRef.current.stop();
}
setChatHistory((prev) => [...prev, { question: currentQuestion, answer }]);
@@ -688,264 +580,240 @@ const InterviewSession = () => {
}
};
- const handleEndInterview = () => {
- if (window.confirm("Are you sure you want to end the interview? This action cannot be undone.")) {
- if (synthesisRef.current) {
- synthesisRef.current.cancel();
- }
- setCompleted(true);
- }
- };
-
- const handleStartInterview = () => {
- setShowWelcome(false);
- interviewStartTime.current = Date.now();
- };
+ const handleStartInterview = async () => {
+ setLoading(true);
+
+ try {
+ // Initialize the actual interview session when user clicks start
+ const data = await fetchWithToken(
+ `${API_URL}/interview/interview-session-initializer/${interviewId}/`,
+ token,
+ null,
+ "POST"
+ );
- const formatTime = (seconds) => {
- const mins = Math.floor(seconds / 60);
- const secs = seconds % 60;
- return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
- };
+ if (!data || !data.session_id) {
+ setError(data?.error || "Failed to start interview.");
+ setLoading(false);
+ return;
+ }
- const togglePause = () => {
- setIsPaused(!isPaused);
- if (synthesisRef.current) {
- if (isPaused) {
- synthesisRef.current.resume();
- } else {
- synthesisRef.current.pause();
+ setSessionId(data.session_id);
+ setCurrentQuestion(data.question);
+ setShowWelcome(false);
+
+ // Welcome message after session starts
+ if (speechEnabled) {
+ setTimeout(() => speakText("Welcome to your interview. Let's begin with the first question."), 1000);
}
+ } catch (err) {
+ setError("Error starting interview session.");
+ } finally {
+ setLoading(false);
}
};
- // Loading screen (from your original code)
+ // ✅ Loading
if (loading) {
return (
-
-
-
-
Initializing interview session...
+
+
+
+
Preparing your interview...
);
}
- // Error screen (from your original code)
+ // ✅ Error screen
if (error) {
return (
-
-
-
{error}
-
navigate(-1)}
- className="bg-purple-600 px-6 py-3 rounded-lg hover:bg-purple-700 transition-all duration-200"
- >
- Go Back
-
-
- );
- }
-
- // Welcome screen (enhanced version of your original)
- if (showWelcome) {
- return (
-
-
-
-
-
-
- Welcome to the Interview
-
-
- You're about to begin your interview session. Take your time with each question and answer thoughtfully.
-
-
-
Enhanced Features:
-
- • 🎙️ Voice-to-text transcription
- • 🔊 Text-to-speech for questions
- • ⏱️ Real-time interview timer
- • 📝 Complete conversation history
- • ⚙️ Customizable voice settings
-
+
+
+
+
+
{error}
navigate(-1)}
+ className="bg-gradient-to-r from-purple-600 to-purple-700 text-white px-8 py-3 rounded-2xl hover:from-purple-700 hover:to-purple-800 transition-all duration-300 shadow-lg hover:shadow-xl transform hover:scale-105"
>
- Start Interview
+ Go Back
);
}
- // Completion screen (from your original code)
- if (completed) {
+ // ✅ Welcome Screen
+ if (showWelcome) {
return (
-
-
-
-
-
-
- Thank you for attending the interview!
-
-
- Total time: {formatTime(interviewTimer)}
-
-
- Redirecting to dashboard in a moment...
-
-
-
+
+
+ {/* Decorative elements */}
+
+
+
+
+
+
+
+
+ AI Voice Interview
+
+
+ Experience a natural conversation with our AI interviewer. Questions will be read aloud and you can respond using voice input for a seamless interview experience.
+
+
+
+
+ Voice Questions
+
+
+
+ Voice Answers
+
+
+
+ Begin Interview
+
);
}
- // Main interview UI (enhanced version of your original)
- return (
-
- {/* Enhanced Header */}
-
-
-
-
-
-
{formatTime(interviewTimer)}
+ // ✅ Completion Screen
+ if (completed) {
+ return (
+
+
+
+
+
+
-
-
-
- {isRecording ? 'Recording...' : 'Ready'}
-
+
+ Interview Complete!
+
+
+ Thank you for your time. We'll be in touch soon with the results.
+
+
+
+ Redirecting to dashboard...
-
-
-
- {isPaused ? : }
-
-
setShowSettings(!showSettings)}
- className="p-2 rounded-lg bg-slate-700 hover:bg-slate-600 transition-colors"
- >
-
-
-
-
- End Interview
-
-
+
+ );
+ }
- {/* Settings Panel */}
- {showSettings && (
-
-
-
-
Voice
-
setVoice(availableVoices.find(v => v.name === e.target.value))}
- className="w-full bg-slate-800 border border-slate-600 rounded px-3 py-1"
- >
- {availableVoices.map((v) => (
- {v.name}
- ))}
-
+ // ✅ Main Interview UI
+ return (
+
+ {/* Modern Header with Voice Controls */}
+
+
+
+
+
+
- Speed: {speechRate}
- setSpeechRate(parseFloat(e.target.value))}
- className="w-full"
- />
-
-
-
- setSpeechEnabled(e.target.checked)}
- />
- Enable TTS
-
-
- setAutoSpeak(e.target.checked)}
- />
- Auto-speak
-
+
+ AI Voice Interview
+
+ {isSpeaking && (
+
+
+ AI is speaking...
+
+ )}
+
+
+
+ {speechEnabled ? : }
+
+
+ {currentQuestion && (
+
+
+
+ )}
+
- )}
+
- {/* Chat History (enhanced version of your original) */}
-
-
-
-
- Chat History ({chatHistory.length})
+ {/* Chat History Sidebar */}
+
+
-
+
{chatHistory.length === 0 ? (
-
-
-
No messages yet
-
Your conversation will appear here
+
+
+
+
+
No messages yet
+
Your conversation will appear here as you progress
) : (
chatHistory.map((item, index) => (
-
-
-
+
+ {/* Question */}
+
+
-
-
-
Question {index + 1}
-
speak(item.question)}
- className="text-purple-400 hover:text-purple-300 transition-colors"
- >
-
-
-
-
{item.question}
+
+
+ Question {index + 1}
+
+
{item.question}
-
-
+ {/* Answer */}
+
+
-
-
Your Answer
-
{item.answer}
+
+
+ Your Response
+
+
{item.answer}
@@ -956,104 +824,96 @@ const InterviewSession = () => {
- {/* Question Section (enhanced version of your original) */}
+ {/* Main Question Area */}
-
+
{questionLoading ? (
-
-
-
Evaluating your response...
-
Preparing next question
+
+
+
Analyzing your response...
+
Please wait while we prepare the next question
) : currentQuestion && (
-
-
-
-
+
+ {/* Decorative background */}
+
+
+
+ {/* Question Header - Fixed Height */}
+
+
-
- Question {chatHistory.length + 1}
-
-
-
- speak(currentQuestion)}
- className="p-2 rounded-lg bg-purple-600 hover:bg-purple-700 transition-colors"
- title="Speak question"
- >
- {speechEnabled ? : }
-
-
-
-
-
-
- {currentQuestion}
-
-
-
-
-
- {/* Confidence indicator */}
- {confidence > 0 && (
-
-
-
- Speech confidence: {Math.round(confidence * 100)}%
-
+ {/* Question Box - Fixed Height */}
+
- {/* Controls */}
-
-
-
{answer.length} characters
- {isListening && (
-
- )}
-
-
-
+ {/* Answer Section - Fixed Layout */}
+
+
+
+
+ {/* Controls Row - Fixed Height */}
+
+
+ {isListening && (
+
+
+
Recording your voice... Click mic to stop
+
+ )}
+
+
- Submit Answer
+
+ Submit Answer
diff --git a/src/routes/ProtectorRouteWrapper.jsx b/src/routes/ProtectorRouteWrapper.jsx
index af164ac03..422e5e527 100644
--- a/src/routes/ProtectorRouteWrapper.jsx
+++ b/src/routes/ProtectorRouteWrapper.jsx
@@ -485,6 +485,25 @@
// EnhancedProctoredRouteWrapper.jsx
// ProctoredRouteWrapper.jsx
// ProctoredRouteWrapper.jsx
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
import React, { useState, useEffect, useRef } from 'react';
import { useParams, useLocation } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
@@ -1145,8 +1164,8 @@ const ProctoredRouteWrapper = ({ children }) => {
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [isMonitoring]);
- const navigate=useNavigate()
-useEffect(()=>{if(violations>10){navigate('/')}},[violations])
+
+
// Error state
if (error) {
return (
diff --git a/vite.config.js b/vite.config.js
index ba7c50301..00b2a301f 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,12 +1,31 @@
+// import { defineConfig } from 'vite'
+// import react from '@vitejs/plugin-react'
+// import tailwindcss from '@tailwindcss/vite'
+
+// // https://vite.dev/config/
+// export default defineConfig({
+// plugins: [
+
+// tailwindcss(),
+// react()
+// ],
+// })
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
+import path from 'path'
+import { fileURLToPath } from 'url'
+
+// ✅ ESM-friendly __dirname workaround
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = path.dirname(__filename)
-// https://vite.dev/config/
export default defineConfig({
- plugins: [
-
- tailwindcss(),
- react()
- ],
+ plugins: [tailwindcss(), react()],
+ resolve: {
+ alias: {
+ // ✅ Make sure the path points to your .jsx mock file
+ 'next/image': path.resolve(__dirname, 'src/mocks/next-image.jsx'),
+ },
+ },
})