Spaces:
Sleeping
Sleeping
| 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(""" | |
| <style> | |
| .main-header { | |
| font-size: 2.5rem; | |
| color: #4527A0; | |
| font-weight: 700; | |
| margin-bottom: 0; | |
| } | |
| .sub-header { | |
| font-size: 1.1rem; | |
| color: #5E35B1; | |
| font-style: italic; | |
| margin-top: 0; | |
| } | |
| .section-header { | |
| font-size: 1.5rem; | |
| color: #3949AB; | |
| font-weight: 600; | |
| border-bottom: 2px solid #3949AB; | |
| padding-bottom: 0.3rem; | |
| margin-top: 1rem; | |
| } | |
| .question-header { | |
| font-size: 1.3rem; | |
| color: #1E88E5; | |
| font-weight: 600; | |
| } | |
| .question-number { | |
| font-size: 1rem; | |
| color: #0D47A1; | |
| font-weight: 500; | |
| } | |
| .success-text { | |
| color: #2E7D32; | |
| font-weight: 500; | |
| } | |
| .warning-text { | |
| color: #FF6F00; | |
| font-weight: 500; | |
| } | |
| .score-header { | |
| font-size: 1.4rem; | |
| color: #00897B; | |
| font-weight: 600; | |
| } | |
| .score-value { | |
| font-size: 1.8rem; | |
| color: #00695C; | |
| font-weight: 700; | |
| } | |
| .feedback-text { | |
| color: #455A64; | |
| font-style: italic; | |
| } | |
| .ideal-answer-header { | |
| color: #7B1FA2; | |
| font-weight: 600; | |
| } | |
| .user-answer-header { | |
| color: #0277BD; | |
| font-weight: 600; | |
| } | |
| .footer { | |
| text-align: center; | |
| color: #78909C; | |
| font-size: 0.8rem; | |
| margin-top: 2rem; | |
| } | |
| .stButton>button { | |
| background-color: #3949AB; | |
| color: white; | |
| font-weight: 500; | |
| } | |
| .stButton>button:hover { | |
| background-color: #303F9F; | |
| color: white; | |
| } | |
| .submit-button>button { | |
| background-color: #00897B; | |
| color: white; | |
| } | |
| .submit-button>button:hover { | |
| background-color: #00796B; | |
| } | |
| .score-button>button { | |
| background-color: #7B1FA2; | |
| color: white; | |
| } | |
| .score-button>button:hover { | |
| background-color: #6A1B9A; | |
| } | |
| .stExpander { | |
| border: 1px solid #E0E0E0; | |
| border-radius: 5px; | |
| margin-bottom: 1rem; | |
| } | |
| /* Voice selection styling */ | |
| div[data-testid="stRadio"] > div { | |
| background-color: #E8EAF6; | |
| padding: 1rem; | |
| border-radius: 5px; | |
| margin-bottom: 1rem; | |
| } | |
| div[data-testid="stRadio"] label { | |
| font-weight: 500; | |
| color: #3949AB; | |
| } | |
| .audio-player { | |
| margin-top: 0.5rem; | |
| margin-bottom: 1rem; | |
| } | |
| /* Score table styling */ | |
| .score-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin-bottom: 2rem; | |
| font-size: 1.1rem; | |
| box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
| border-radius: 5px; | |
| overflow: hidden; | |
| } | |
| .score-table th { | |
| background-color: #3949AB; | |
| color: white; | |
| font-weight: 600; | |
| text-align: left; | |
| padding: 0.75rem 1rem; | |
| border: none; | |
| } | |
| .score-table td { | |
| padding: 0.75rem 1rem; | |
| border-bottom: 1px solid #E0E0E0; | |
| vertical-align: middle; | |
| } | |
| .score-table tr:last-child { | |
| font-weight: bold; | |
| background-color: #E8EAF6; | |
| } | |
| .score-table tr:last-child td { | |
| border-top: 2px solid #3949AB; | |
| border-bottom: none; | |
| color: #3949AB; | |
| } | |
| .score-table tr:nth-child(even):not(:last-child) { | |
| background-color: #F5F5F5; | |
| } | |
| .score-table tr:hover:not(:last-child) { | |
| background-color: #EDE7F6; | |
| } | |
| /* Make the table responsive */ | |
| @media screen and (max-width: 600px) { | |
| .score-table { | |
| font-size: 0.9rem; | |
| } | |
| .score-table th, .score-table td { | |
| padding: 0.5rem; | |
| } | |
| } | |
| </style> | |
| """, 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""" | |
| <div class="audio-player" id="audio-container-{player_id}"> | |
| <audio id="audio-{player_id}" controls {autoplay_attr}> | |
| <source src="data:audio/mp3;base64,{audio_base64}" type="audio/mp3"> | |
| Your browser does not support the audio element. | |
| </audio> | |
| </div> | |
| <script> | |
| // Force play the audio after a short delay | |
| setTimeout(function() {{ | |
| const audioElement = document.getElementById('audio-{player_id}'); | |
| if (audioElement) {{ | |
| audioElement.play().catch(e => console.log('Auto-play failed:', e)); | |
| }} | |
| }}, 500); | |
| </script> | |
| """ | |
| 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("<h1 class='main-header'>🎯 Virtual Interviewer</h1>", unsafe_allow_html=True) | |
| st.markdown("<p class='sub-header'>An AI-powered interview simulator to help you prepare for your next job interview.</p>", 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("<h2 class='section-header'>📋 Interview Setup</h2>", 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("<h3 class='section-header'>🔊 Text-to-Speech Options</h3>", 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("<p style='margin-top: 1rem; font-weight: 500;'>Select a voice for the interviewer:</p>", 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("<h2 class='section-header'>🎙️ Interview in Progress</h2>", unsafe_allow_html=True) | |
| # Display current question number | |
| st.markdown(f"<p class='question-number'>Question {st.session_state.current_question_index + 1} of {len(st.session_state.interviewer.questions_asked)}</p>", unsafe_allow_html=True) | |
| # Display conversation history | |
| if st.session_state.conversation_history: | |
| st.markdown("<h3 class='section-header'>📜 Previous Questions and Answers</h3>", unsafe_allow_html=True) | |
| for i, qa in enumerate(st.session_state.conversation_history): | |
| if i % 2 == 0: # Question (even index) | |
| st.markdown(f"<p><strong style='color: #3949AB;'>Q{i//2 + 1}:</strong> {qa}</p>", unsafe_allow_html=True) | |
| else: # Answer (odd index) | |
| st.markdown(f"<p><em style='color: #455A64;'>A: {qa}</em></p>", unsafe_allow_html=True) | |
| st.markdown("<hr style='margin: 0.5rem 0; border-color: #E0E0E0;'>", unsafe_allow_html=True) | |
| # Interview completed section | |
| elif st.session_state.interview_completed: | |
| st.markdown("<h2 class='section-header'>🏁 Interview Completed</h2>", unsafe_allow_html=True) | |
| # Display conversation history | |
| if st.session_state.conversation_history: | |
| st.markdown("<h3 class='section-header'>📋 Interview Summary</h3>", unsafe_allow_html=True) | |
| for i, qa in enumerate(st.session_state.conversation_history): | |
| if i % 2 == 0: # Question (even index) | |
| st.markdown(f"<p><strong style='color: #3949AB;'>Q{i//2 + 1}:</strong> {qa}</p>", unsafe_allow_html=True) | |
| else: # Answer (odd index) | |
| st.markdown(f"<p><em style='color: #455A64;'>A: {qa}</em></p>", unsafe_allow_html=True) | |
| st.markdown("<hr style='margin: 0.5rem 0; border-color: #E0E0E0;'>", 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("<h2 class='section-header'>❓ Current Question</h2>", unsafe_allow_html=True) | |
| # Display the current question | |
| st.markdown(f"<p class='question-header'>{st.session_state.current_question}</p>", 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("<p class='success-text'>✅ Answer submitted! Click 'Next Question' to continue.</p>", unsafe_allow_html=True) | |
| else: | |
| st.markdown("<p class='warning-text'>⚠️ Please provide an answer before submitting.</p>", 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("<h2 class='section-header'>📊 Interview Results</h2>", 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""" | |
| <div style="background-color: #F3F4F6; padding: 1.5rem; border-radius: 10px; margin: 1rem 0; box-shadow: 0 2px 5px rgba(0,0,0,0.1);"> | |
| <h3 style="color: #3949AB; margin-top: 0;">Overall Performance</h3> | |
| <div style="display: flex; align-items: center; margin-bottom: 1rem;"> | |
| <div style="background-color: {score_color}; color: white; font-size: 2rem; font-weight: bold; width: 80px; height: 80px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-right: 1.5rem;"> | |
| {overall_score}/5 | |
| </div> | |
| <div> | |
| <p style="font-size: 1.1rem; margin: 0; color: #333;">{score_results['overall_feedback']}</p> | |
| </div> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Display score table | |
| if "individual_scores" in score_results: | |
| st.markdown("<h3 class='section-header'>📈 Score Summary</h3>", 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""" | |
| <div style="overflow-x: auto;"> | |
| {table_html} | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Display individual scores with ideal answers | |
| if "individual_scores" in score_results: | |
| st.markdown("<h3 class='section-header'>📝 Detailed Feedback</h3>", unsafe_allow_html=True) | |
| st.markdown(""" | |
| <p style="margin-bottom: 1rem;">Click on each question below to see detailed feedback and ideal answers.</p> | |
| """, 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""" | |
| <div style="display: flex; align-items: center; margin-bottom: 1rem;"> | |
| <div style="background-color: {score_color}; color: white; font-size: 1.2rem; font-weight: bold; width: 50px; height: 50px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-right: 1rem;"> | |
| {score_value}/5 | |
| </div> | |
| <div> | |
| <p style="font-size: 1.1rem; margin: 0; color: #333; font-weight: 500;">{score_item['feedback']}</p> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown("<h4 class='user-answer-header'>🧑💼 Your Answer:</h4>", 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("<h4 class='ideal-answer-header'>💡 Ideal Answer:</h4>", 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("<h2 class='section-header'>👋 Welcome to Virtual Interviewer</h2>", unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div style="background-color: #E8EAF6; padding: 1.5rem; border-radius: 10px; margin-top: 1rem;"> | |
| <h3 style="color: #3949AB; font-size: 1.3rem; margin-bottom: 1rem;">This tool will help you practice for your upcoming interviews by:</h3> | |
| <ol style="color: #303F9F; font-size: 1.1rem;"> | |
| <li>Generating relevant interview questions based on a job description</li> | |
| <li>Simulating a real interview experience</li> | |
| <li>Providing feedback on your answers</li> | |
| </ol> | |
| <p style="color: #3949AB; font-size: 1.1rem; margin-top: 1rem;">To get started, fill out the interview setup form on the left and click "Start Interview".</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Bottom section for control buttons | |
| if st.session_state.interview_started and not st.session_state.interview_completed: | |
| st.markdown("<hr style='margin: 2rem 0 1rem 0;'>", 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("<hr style='margin: 2rem 0 1rem 0;'>", unsafe_allow_html=True) | |
| st.markdown("<p class='footer'>🎯 Powered by OpenAI GPT-4o | Created with Streamlit</p>", unsafe_allow_html=True) | |