Spaces:
Sleeping
Sleeping
| import os | |
| import re | |
| import time | |
| import asyncio | |
| import tempfile | |
| import streamlit as st | |
| import google.generativeai as genai | |
| import edge_tts | |
| import speech_recognition as sr | |
| from dotenv import load_dotenv | |
| import pandas as pd | |
| # β Streamlit page config | |
| st.set_page_config(page_title="GrillMaster", layout="wide") | |
| # Load API key | |
| load_dotenv() | |
| genai.configure(api_key=os.getenv("GOOGLE_API_KEY")) | |
| # ----------------------------- | |
| # SESSION STATE DEFAULTS | |
| # ----------------------------- | |
| defaults = { | |
| "generated_questions": [], | |
| "current_question_index": 0, | |
| "answers": [], | |
| "evaluations": [], | |
| "evaluation_feedback": "", | |
| "overall_score": 0, | |
| "percentage_score": 0, | |
| "is_recording": False, | |
| "question_played": False, | |
| "selected_domain": "", | |
| "response_captured": False, | |
| "timer_start": None, | |
| "show_intro": False, | |
| "recorded_text": "", | |
| "recording_complete": False, | |
| "recording_started": False, | |
| "audio_played": False, | |
| "question_start_time": 0.0, | |
| "record_phase": "", | |
| "improvement_suggestions_generated": False, | |
| "improvement_suggestions": "" | |
| } | |
| for key, value in defaults.items(): | |
| if key not in st.session_state: | |
| st.session_state[key] = value | |
| # ----------------------------- | |
| # QUESTIONS | |
| # ----------------------------- | |
| CANDIDATE_QUESTIONS = [ | |
| {"text": "Can you introduce yourself?", "type": "introduction"}, | |
| {"text": "Why do you want to be a part of Analytics domain?", "type": "introduction"}, | |
| {"text": "Can you try to explain any project of yours in detail?", "type": "project"}, | |
| {"text": "Any challenges faced while working on the project?", "type": "project"}, | |
| {"text": "What could be the business impact of the project?", "type": "project"} | |
| ] | |
| # ----------------------------- | |
| # AUDIO GENERATION | |
| # ----------------------------- | |
| async def generate_question_audio(question, voice="en-IE-EmilyNeural"): | |
| clean_question = re.sub(r'[^A-Za-z0-9.,?! ]+', '', question) | |
| tts = edge_tts.Communicate(text=clean_question, voice=voice) | |
| with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file: | |
| await tts.save(tmp_file.name) | |
| return tmp_file.name | |
| # ----------------------------- | |
| # EVALUATION FUNCTION | |
| # ----------------------------- | |
| def evaluate_answer(question_text, answer_text, q_type): | |
| model = genai.GenerativeModel("gemini-2.5-pro") | |
| if q_type == "introduction": | |
| prompt = f""" | |
| You are an expert interviewer evaluating a candidate's introduction. Assess the response based on: | |
| - Clarity & Fluency | |
| - Confidence & Professionalism | |
| - Relevance & Structure | |
| - Conciseness | |
| Provide an evaluation summary with a score out of 10. | |
| Candidate Introduction: | |
| {answer_text} | |
| """ | |
| else: # project explanation | |
| prompt = f""" | |
| You are an expert interviewer evaluating a candidate's project explanation. Assess the response based on: | |
| - Technical Understanding | |
| - Communication Clarity | |
| - Problem-Solving & Impact | |
| - Use of Examples | |
| - Logical Flow & Structure | |
| Provide an evaluation summary with a score out of 10. | |
| Candidate Project Explanation: | |
| {answer_text} | |
| """ | |
| response = model.generate_content(prompt) | |
| text = response.text.strip() | |
| score_match = re.search(r"\*\*Overall Score:\*\* (\d+)/10", text) | |
| score = int(score_match.group(1)) if score_match else 0 | |
| return {"score": score, "feedback": text} | |
| # ----------------------------- | |
| # IMPROVEMENT SUGGESTIONS | |
| # ----------------------------- | |
| def generate_improvement_suggestions(): | |
| model = genai.GenerativeModel('gemini-2.5-pro') | |
| if not st.session_state.get("answers"): | |
| st.session_state.improvement_suggestions = "No answers were recorded to generate improvement suggestions." | |
| return | |
| qa_context = [] | |
| for i, entry in enumerate(st.session_state["answers"]): | |
| qa_context.append( | |
| f"Question {i+1}: {entry['question']}\nCandidate's Answer {i+1}: {entry.get('response','[No response]')}" | |
| ) | |
| full_qa_context = "\n\n".join(qa_context) | |
| prompt = f""" | |
| You are an interview coach. Based on these Q&A: | |
| {full_qa_context} | |
| Provide detailed improvement suggestions for each answer. Be constructive and supportive. | |
| """ | |
| try: | |
| st.info("π€ Generating detailed improvement suggestions...") | |
| response = model.generate_content(prompt) | |
| st.session_state.improvement_suggestions = response.text.strip() | |
| st.session_state.improvement_suggestions_generated = True | |
| st.success("Detailed suggestions generated!") | |
| except Exception as e: | |
| st.error(f"Error generating suggestions: {e}") | |
| st.session_state.improvement_suggestions_generated = False | |
| # ----------------------------- | |
| # START PAGE: Candidate Intro Button | |
| # ----------------------------- | |
| if not st.session_state["show_intro"]: | |
| st.title("π₯π― Welcome to GrillMaster Mock Interview") | |
| st.markdown("Click the button below to start the Candidate Introduction + Project mock interview:") | |
| if st.button("π€ Candidate Intro + Project"): | |
| st.session_state.update({ | |
| "show_intro": True, | |
| "selected_domain": "Candidate Intro + Project", | |
| "current_question_index": 0, | |
| "answers": [], | |
| "evaluations": [], | |
| "question_played": False | |
| }) | |
| st.rerun() | |
| # ----------------------------- | |
| # CANDIDATE INTRO WORKFLOW | |
| # ----------------------------- | |
| if st.session_state.get("show_intro"): | |
| st.header("π― Candidate Introduction + Project") | |
| q_index = st.session_state["current_question_index"] | |
| if q_index < len(CANDIDATE_QUESTIONS): | |
| question = CANDIDATE_QUESTIONS[q_index] | |
| st.subheader(f"Q{q_index+1}: {question['text']}") | |
| # Generate audio | |
| if not st.session_state["question_played"]: | |
| audio_file = asyncio.run(generate_question_audio(question["text"])) | |
| st.audio(audio_file, format="audio/mp3") | |
| st.session_state["question_played"] = True | |
| # Record answer | |
| audio_data = st.audio_input("π€ Record your answer here") | |
| if audio_data: | |
| audio_bytes = audio_data.read() | |
| with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as f: | |
| f.write(audio_bytes) | |
| wav_path = f.name | |
| recognizer = sr.Recognizer() | |
| with sr.AudioFile(wav_path) as source: | |
| recorded_audio = recognizer.record(source) | |
| try: | |
| response_text = recognizer.recognize_google(recorded_audio) | |
| st.session_state["current_response"] = response_text | |
| st.success("β Answer recorded. You can re-record or move to next question.") | |
| except sr.UnknownValueError: | |
| st.error("β οΈ Could not understand audio.") | |
| # Buttons | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| if st.button("π Re-record Answer"): | |
| st.session_state.pop("current_response", None) | |
| st.session_state["question_played"] = False | |
| st.experimental_rerun() | |
| with col2: | |
| if "current_response" in st.session_state and st.button("β‘οΈ Next Question"): | |
| st.session_state["answers"].append({ | |
| "question": question["text"], | |
| "response": st.session_state.pop("current_response"), | |
| "type": question["type"] | |
| }) | |
| st.session_state["question_played"] = False | |
| st.session_state["current_question_index"] += 1 | |
| st.rerun() | |
| else: | |
| # Evaluate all answers | |
| if not st.session_state["evaluations"]: | |
| for ans in st.session_state["answers"]: | |
| ev = evaluate_answer(ans["question"], ans["response"], ans["type"]) | |
| st.session_state["evaluations"].append(ev) | |
| st.subheader("π Mock Interview Completed") | |
| total_score = sum([ev["score"] for ev in st.session_state["evaluations"]]) | |
| overall_score = round(total_score / len(st.session_state["evaluations"]), 2) | |
| st.write(f"**Overall Average Score:** {overall_score}/10") | |
| st.progress(overall_score / 10) | |
| # Show answers & feedback | |
| for i, ans in enumerate(st.session_state["answers"]): | |
| ev = st.session_state["evaluations"][i] | |
| st.write(f"**Q{i+1}: {ans['question']}**") | |
| st.write(f"**A:** {ans['response']}") | |
| st.write(f"**Score:** {ev['score']}/10") | |
| st.write(ev["feedback"]) | |
| st.write("---") | |
| # Improvement suggestions | |
| if st.button("π‘ Generate Improvement Suggestions"): | |
| generate_improvement_suggestions() | |
| st.rerun() | |
| if st.session_state.get("improvement_suggestions_generated"): | |
| with st.expander("π Improvement Suggestions", expanded=True): | |
| st.markdown(st.session_state["improvement_suggestions"]) | |
| # Download summary | |
| def prepare_summary(): | |
| text = "# GrillMaster Candidate Intro Summary\n\n" | |
| for i, ans in enumerate(st.session_state["answers"]): | |
| ev = st.session_state["evaluations"][i] | |
| text += f"**Q{i+1}: {ans['question']}**\n**A:** {ans['response']}\n**Score:** {ev['score']}/10\n\n" | |
| if st.session_state.get("improvement_suggestions_generated"): | |
| text += "## Improvement Suggestions:\n" + st.session_state["improvement_suggestions"] | |
| return text.encode("utf-8") | |
| st.download_button("πΎ Download Summary", data=prepare_summary(), | |
| file_name=f"GrillMaster_Summary_{time.strftime('%Y%m%d_%H%M')}.md", | |
| mime="text/markdown") | |