kedar-bhumkar's picture
Upload 4 files
43ceeff verified
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)