import streamlit as st import json import base64 import os import time import uuid from backend import VirtualInterviewer import pandas as pd # Set page configuration st.set_page_config( page_title="Virtual Interviewer", page_icon="🎯", layout="wide", initial_sidebar_state="expanded" ) # Default job description and key topics for Solution Architect DEFAULT_JOB_DESCRIPTION = """ Job Title: Enterprise Solution Architect Job Description: We are seeking an experienced Enterprise Solution Architect to design and implement innovative technology solutions that address complex business challenges. The ideal candidate will have a strong background in cloud architecture, enterprise integration, and modern application development. Responsibilities: - Design scalable, secure, and resilient enterprise solutions using cloud-native technologies - Create architectural blueprints and technical roadmaps aligned with business objectives - Evaluate and recommend appropriate technologies and frameworks for various business needs - Lead technical discussions with stakeholders and development teams - Ensure solutions adhere to architectural standards, best practices, and compliance requirements - Mentor junior architects and developers on architectural principles and patterns Requirements: - 8+ years of experience in IT with at least 5 years in solution architecture - Strong knowledge of Azure cloud services and architecture patterns - Experience with Java enterprise applications and microservices architecture - Familiarity with GraphQL API design and implementation - Experience integrating with Salesforce and other enterprise systems - Knowledge of Generative AI technologies and their practical applications - Excellent communication and presentation skills - Ability to translate business requirements into technical solutions """ DEFAULT_KEY_TOPICS = "Azure, GraphQL, Java, Salesforce, Generative AI, Cloud Architecture, Microservices, API Design" # Custom CSS for styling st.markdown(""" """, unsafe_allow_html=True) # Function to create an HTML audio player def get_audio_player_html(audio_path, autoplay=True, player_id=None): if not audio_path or not os.path.exists(audio_path): return "" # Generate a unique ID for this audio player if not provided if player_id is None: player_id = str(uuid.uuid4()) # Read the audio file with open(audio_path, 'rb') as f: audio_bytes = f.read() audio_base64 = base64.b64encode(audio_bytes).decode() autoplay_attr = "autoplay" if autoplay else "" # Create HTML with JavaScript to ensure autoplay works html = f"""
""" return html # Initialize session state variables if they don't exist if 'interviewer' not in st.session_state: st.session_state.interviewer = None if 'current_question_index' not in st.session_state: st.session_state.current_question_index = 0 if 'current_question' not in st.session_state: st.session_state.current_question = "" if 'conversation_history' not in st.session_state: st.session_state.conversation_history = [] if 'interview_started' not in st.session_state: st.session_state.interview_started = False if 'interview_setup_done' not in st.session_state: st.session_state.interview_setup_done = False if 'questions_generated' not in st.session_state: st.session_state.questions_generated = False if 'interview_completed' not in st.session_state: st.session_state.interview_completed = False if 'interview_scored' not in st.session_state: st.session_state.interview_scored = False if 'score_results' not in st.session_state: st.session_state.score_results = None if 'answer_submitted' not in st.session_state: st.session_state.answer_submitted = False if 'current_answer' not in st.session_state: st.session_state.current_answer = "" if 'generate_ideal_answers' not in st.session_state: st.session_state.generate_ideal_answers = True if 'voice_type' not in st.session_state: st.session_state.voice_type = "female_casual" if 'use_tts' not in st.session_state: st.session_state.use_tts = False if 'current_audio_path' not in st.session_state: st.session_state.current_audio_path = "" if 'audio_key' not in st.session_state: st.session_state.audio_key = str(uuid.uuid4()) if 'should_play_audio' not in st.session_state: st.session_state.should_play_audio = True # Define callback functions def reset_answer_input(): st.session_state.answer_submitted = False st.session_state.current_answer = "" # We don't modify st.session_state.user_answer directly # Function to generate audio for a question def ensure_audio_for_question(question, voice_type): """Ensure audio exists for the given question and return the path.""" if not question: return "" # Check if we already have audio for this question audio_path = st.session_state.interviewer.get_question_audio_path(question) # If no audio exists, generate it if not audio_path: with st.spinner("Generating audio..."): audio_path = st.session_state.interviewer.generate_question_audio(question, voice_type) # Update the current audio path in session state st.session_state.current_audio_path = audio_path # Generate a new audio key to force refresh st.session_state.audio_key = str(uuid.uuid4()) return audio_path # Function to handle replay button click def replay_audio(): st.session_state.should_play_audio = True st.session_state.audio_key = str(uuid.uuid4()) # Title and description st.markdown("

🎯 Virtual Interviewer

", unsafe_allow_html=True) st.markdown("

An AI-powered interview simulator to help you prepare for your next job interview.

", unsafe_allow_html=True) # Create a two-column layout left_col, right_col = st.columns([1, 1]) with left_col: # Interview setup section if not st.session_state.interview_setup_done: st.markdown("

📋 Interview Setup

", unsafe_allow_html=True) # 1a. Job requirement text box job_description = st.text_area( "💼 Job Description", value=DEFAULT_JOB_DESCRIPTION, height=200 ) # 1b. OpenAI API Key api_key = st.text_input( "🔑 OpenAI API Key", type="password", placeholder="Enter your OpenAI API key" ) # 2. Interview type dropdown interview_type = st.selectbox( "🎭 Interview Type", options=["Technical", "Non-technical"] ) # 3. Difficulty level dropdown difficulty_level = st.selectbox( "📊 Difficulty Level", options=["Easy", "Medium", "Hard"] ) # 4. Key topics text box key_topics = st.text_input( "🔍 Key Topics", value=DEFAULT_KEY_TOPICS ) # 5. Scoring option enable_scoring = st.radio( "📝 Enable Scoring?", options=["Yes", "No"], horizontal=True ) # New option for generating ideal answers generate_ideal_answers = st.radio( "💡 Generate Ideal Answers?", options=["Yes", "No"], horizontal=True ) # 6. Number of questions num_questions = st.number_input( "❓ Number of Questions", min_value=1, max_value=10, value=5 ) # Text-to-Speech options st.markdown("

🔊 Text-to-Speech Options

", unsafe_allow_html=True) use_tts = st.checkbox("Enable Text-to-Speech for questions", value=True) if use_tts: # Voice type selection with a single radio button for all voices st.markdown("

Select a voice for the interviewer:

", unsafe_allow_html=True) # Create a single radio button with all voice options voice_options = [ "👨 Male - Casual (Guy)", "👨 Male - Formal (Christopher)", "👨 Male - British (Ryan)", "👩 Female - Casual (Jenny)", "👩 Female - Formal (Aria)", "👩 Female - British (Sonia)" ] selected_voice = st.radio( "Voice Selection", options=voice_options, index=3, # Default to Female Casual label_visibility="collapsed" # Hide the label since we already have a header ) # Map the selected voice to the backend voice type voice_mapping = { "👨 Male - Casual (Guy)": "male_casual", "👨 Male - Formal (Christopher)": "male_formal", "👨 Male - British (Ryan)": "male_british", "👩 Female - Casual (Jenny)": "female_casual", "👩 Female - Formal (Aria)": "female_formal", "👩 Female - British (Sonia)": "female_british" } voice_type = voice_mapping.get(selected_voice, "female_casual") else: voice_type = "female_casual" # Start interview button if st.button("🚀 Start Interview"): if not api_key: st.error("⚠️ Please enter your OpenAI API key.") elif not job_description: st.error("⚠️ Please enter a job description.") else: with st.spinner("Setting up your interview..."): try: # Initialize the interviewer st.session_state.interviewer = VirtualInterviewer(api_key) # Store whether to generate ideal answers should_generate_ideal_answers = (generate_ideal_answers == "Yes") st.session_state.generate_ideal_answers = should_generate_ideal_answers # Store TTS settings st.session_state.use_tts = use_tts st.session_state.voice_type = voice_type # Generate questions questions = st.session_state.interviewer.generate_interview_questions( job_description=job_description, interview_type=interview_type, difficulty_level=difficulty_level, key_topics=key_topics, num_questions=int(num_questions), generate_ideal_answers=should_generate_ideal_answers ) # Generate audio for the first question if TTS is enabled if use_tts and questions: with st.spinner("Generating audio for first question..."): audio_path = st.session_state.interviewer.generate_question_audio(questions[0], voice_type) st.session_state.current_audio_path = audio_path st.session_state.audio_key = str(uuid.uuid4()) st.session_state.should_play_audio = True # Store interview parameters for scoring later st.session_state.job_description = job_description st.session_state.interview_type = interview_type st.session_state.difficulty_level = difficulty_level st.session_state.enable_scoring = (enable_scoring == "Yes") st.session_state.num_questions = int(num_questions) # Set the first question st.session_state.current_question = questions[0] st.session_state.questions_generated = True st.session_state.interview_setup_done = True st.session_state.interview_started = True st.session_state.answer_submitted = False st.session_state.current_answer = "" # Rerun to update the UI st.rerun() except Exception as e: st.error(f"⚠️ Error setting up the interview: {str(e)}") # Interview in progress section elif st.session_state.interview_started and not st.session_state.interview_completed: st.markdown("

🎙️ Interview in Progress

", unsafe_allow_html=True) # Display current question number st.markdown(f"

Question {st.session_state.current_question_index + 1} of {len(st.session_state.interviewer.questions_asked)}

", unsafe_allow_html=True) # Display conversation history if st.session_state.conversation_history: st.markdown("

📜 Previous Questions and Answers

", unsafe_allow_html=True) for i, qa in enumerate(st.session_state.conversation_history): if i % 2 == 0: # Question (even index) st.markdown(f"

Q{i//2 + 1}: {qa}

", unsafe_allow_html=True) else: # Answer (odd index) st.markdown(f"

A: {qa}

", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) # Interview completed section elif st.session_state.interview_completed: st.markdown("

🏁 Interview Completed

", unsafe_allow_html=True) # Display conversation history if st.session_state.conversation_history: st.markdown("

📋 Interview Summary

", unsafe_allow_html=True) for i, qa in enumerate(st.session_state.conversation_history): if i % 2 == 0: # Question (even index) st.markdown(f"

Q{i//2 + 1}: {qa}

", unsafe_allow_html=True) else: # Answer (odd index) st.markdown(f"

A: {qa}

", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) # Option to restart if st.button("🔄 Start New Interview"): # Reset all session state variables for key in list(st.session_state.keys()): del st.session_state[key] st.rerun() # Right column for displaying the current question and answer input with right_col: if st.session_state.interview_started and not st.session_state.interview_completed: st.markdown("

❓ Current Question

", unsafe_allow_html=True) # Display the current question st.markdown(f"

{st.session_state.current_question}

", unsafe_allow_html=True) # Display audio player if TTS is enabled if st.session_state.use_tts: # Ensure we have audio for the current question current_question = st.session_state.current_question audio_path = ensure_audio_for_question(current_question, st.session_state.voice_type) # Display audio player with unique key to force refresh if audio_path and os.path.exists(audio_path): # Create a container for the audio player audio_container = st.empty() # Display the audio player with the current audio key audio_container.markdown( get_audio_player_html( audio_path, autoplay=st.session_state.should_play_audio, player_id=st.session_state.audio_key ), unsafe_allow_html=True ) # Reset the should_play_audio flag after displaying if st.session_state.should_play_audio: st.session_state.should_play_audio = False # Add a button to replay the audio if st.button("🔊 Replay Question Audio"): # Set flag to play audio and generate new key replay_audio() st.rerun() # Text area for the user's answer user_answer = st.text_area( "Your Answer", key="user_answer", height=300, placeholder="Type your answer here..." ) # Submit button for the answer submit_button_col1, submit_button_col2, submit_button_col3 = st.columns([1, 1, 1]) with submit_button_col2: if st.button("✅ Submit Answer", key="submit_answer"): if user_answer: # Use the local variable, not session_state st.session_state.answer_submitted = True st.session_state.current_answer = user_answer # Store the answer in a different session state variable st.markdown("

✅ Answer submitted! Click 'Next Question' to continue.

", unsafe_allow_html=True) else: st.markdown("

⚠️ Please provide an answer before submitting.

", unsafe_allow_html=True) # Display score results if interview is scored elif st.session_state.interview_scored and st.session_state.score_results: st.markdown("

📊 Interview Results

", unsafe_allow_html=True) score_results = st.session_state.score_results # Display overall score with a visual indicator if "overall_score" in score_results: overall_score = score_results['overall_score'] # Create a visual score indicator score_color = "#4CAF50" if overall_score >= 4 else "#FF9800" if overall_score >= 3 else "#F44336" st.markdown(f"""

Overall Performance

{overall_score}/5

{score_results['overall_feedback']}

""", unsafe_allow_html=True) # Display score table if "individual_scores" in score_results: st.markdown("

📈 Score Summary

", unsafe_allow_html=True) # Extract data for the table questions = [] scores = [] for i, score_item in enumerate(score_results["individual_scores"]): # Truncate long questions for better display question_text = score_item['question'] if len(question_text) > 80: question_text = question_text[:77] + "..." questions.append(f"Q{i+1}: {question_text}") scores.append(score_item['score']) # Calculate average score if scores: avg_score = sum(scores) / len(scores) questions.append("**Average Score**") scores.append(f"**{avg_score:.2f}**") # Create DataFrame df = pd.DataFrame({ "Question": questions, "Score (out of 5)": scores }) # Convert DataFrame to HTML and display it table_html = df.to_html(classes='score-table', escape=False, index=False) st.markdown(f"""
{table_html}
""", unsafe_allow_html=True) # Display individual scores with ideal answers if "individual_scores" in score_results: st.markdown("

📝 Detailed Feedback

", unsafe_allow_html=True) st.markdown("""

Click on each question below to see detailed feedback and ideal answers.

""", unsafe_allow_html=True) for i, score_item in enumerate(score_results["individual_scores"]): # Determine score color score_value = score_item['score'] score_color = "#4CAF50" if score_value >= 4 else "#FF9800" if score_value >= 3 else "#F44336" with st.expander(f"Question {i+1}: {score_item['question']}"): st.markdown(f"""
{score_value}/5

{score_item['feedback']}

""", unsafe_allow_html=True) st.markdown("

🧑‍💼 Your Answer:

", unsafe_allow_html=True) st.markdown(f"{score_item['answer']}") # Only show ideal answers if they were generated if st.session_state.generate_ideal_answers: st.markdown("

💡 Ideal Answer:

", unsafe_allow_html=True) if "ideal_answer" in score_item: st.markdown(f"{score_item['ideal_answer']}") else: st.markdown("No ideal answer available.") # Display a welcome message if interview hasn't started elif not st.session_state.interview_started: st.markdown("

👋 Welcome to Virtual Interviewer

", unsafe_allow_html=True) st.markdown("""

This tool will help you practice for your upcoming interviews by:

  1. Generating relevant interview questions based on a job description
  2. Simulating a real interview experience
  3. Providing feedback on your answers

To get started, fill out the interview setup form on the left and click "Start Interview".

""", unsafe_allow_html=True) # Bottom section for control buttons if st.session_state.interview_started and not st.session_state.interview_completed: st.markdown("
", unsafe_allow_html=True) # Create a container for the buttons at the bottom button_container = st.container() with button_container: col1, col2, col3 = st.columns([1, 1, 1]) with col1: pass # Empty column for spacing with col2: next_button_disabled = not st.session_state.answer_submitted if st.button("⏭️ Next Question", disabled=next_button_disabled, use_container_width=True): # Store the current question and answer if st.session_state.current_answer: # Use current_answer instead of user_answer # Add to conversation history st.session_state.conversation_history.append(st.session_state.current_question) st.session_state.conversation_history.append(st.session_state.current_answer) # Store in the interviewer object st.session_state.interviewer.store_user_answer( st.session_state.current_question, st.session_state.current_answer ) # Move to the next question st.session_state.current_question_index += 1 # Check if we've reached the end of the questions if st.session_state.current_question_index >= len(st.session_state.interviewer.questions_asked): st.session_state.interview_completed = True # Automatically score the interview if scoring is enabled if st.session_state.enable_scoring: with st.spinner("Scoring your interview..."): try: # Score the interview score_results = st.session_state.interviewer.score_interview( job_description=st.session_state.job_description, interview_type=st.session_state.interview_type, difficulty_level=st.session_state.difficulty_level ) st.session_state.score_results = score_results st.session_state.interview_scored = True except Exception as e: st.error(f"⚠️ Error scoring the interview: {str(e)}") st.rerun() else: # Get the next question next_question = st.session_state.interviewer.get_next_question( st.session_state.current_question_index ) st.session_state.current_question = next_question # Reset submission status and current answer reset_answer_input() # Set flag to play audio for the new question st.session_state.should_play_audio = True st.session_state.audio_key = str(uuid.uuid4()) st.rerun() else: st.warning("⚠️ Please provide an answer before moving to the next question.") with col3: if st.session_state.enable_scoring: if st.button("📊 Score Interview", use_container_width=True, key="score_button"): if len(st.session_state.interviewer.user_answers) > 0: with st.spinner("Scoring your interview..."): try: # Score the interview score_results = st.session_state.interviewer.score_interview( job_description=st.session_state.job_description, interview_type=st.session_state.interview_type, difficulty_level=st.session_state.difficulty_level ) st.session_state.score_results = score_results st.session_state.interview_scored = True st.session_state.interview_completed = True st.rerun() except Exception as e: st.error(f"⚠️ Error scoring the interview: {str(e)}") else: st.warning("⚠️ Please answer at least one question before scoring.") # Footer st.markdown("
", unsafe_allow_html=True) st.markdown("", unsafe_allow_html=True)