MOSES3377/ai-interview-app
0
1import gradio as gr2import time3import logging4import os5 6# Import utility functions from other modules7from llm_utils import generate_questions, evaluate_answer, get_interview_summary8from stt_utils import transcribe_audio9from tts_utils import speak_text10 11# Configure logging12logging.basicConfig(level=logging.INFO)13 14# --- State Management ---15def initialize_state():16 """Returns the initial state for the interview session."""17 return {18 "candidate_name": "", "role": "", "questions": [],19 "evaluations": [], "current_question_index": 0,20 }21 22# --- Core Interview Logic ---23 24def start_interview(name, role, num_questions):25 """Initializes the interview, generates questions, and returns the first question UI."""26 if not name or not role:27 gr.Warning("Please enter both your name and the job role to begin.")28 return None, gr.update(visible=True), gr.update(visible=False), "", "", None, gr.update(interactive=True)29 30 logging.info(f"Starting interview for {name} for the role of {role}.")31 32 state = initialize_state()33 state["candidate_name"] = name34 state["role"] = role35 36 questions = generate_questions(role, int(num_questions))37 if not questions:38 gr.Error("Failed to generate interview questions. Please check your API key or try again.")39 return None, gr.update(visible=True), gr.update(visible=False), "", "", None, gr.update(interactive=True)40 41 state["questions"] = questions42 first_question_text = questions[0]['text']43 audio_path = speak_text(first_question_text)44 progress_text = f"Question 1 of {len(questions)}"45 46 # Hide setup, show interview, and provide all initial values47 return state, gr.update(visible=False), gr.update(visible=True), progress_text, first_question_text, audio_path, gr.update(interactive=True)48 49# FINAL VERSION: This is the most robust and simple function for processing.50def process_answer(state, audio_input):51 """52 Handles audio submission directly, performs AI tasks, and returns all UI updates at once.53 This is the most stable design.54 """55 # 1. Validate the audio input immediately56 if audio_input is None or not os.path.exists(audio_input):57 logging.warning("Submission failed: Audio input is missing or file path is invalid.")58 gr.Warning("Your audio was not recorded or sent correctly. Please try recording again.")59 # Return updates that re-enable the UI without changing the question60 return state, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(interactive=True)61 62 # 2. Process the valid audio file63 logging.info(f"Processing audio file: {audio_input}")64 transcribed_answer = transcribe_audio(audio_input)65 if not transcribed_answer:66 transcribed_answer = "(Audio was unclear or could not be transcribed)"67 68 current_question = state["questions"][state["current_question_index"]]['text']69 evaluation = evaluate_answer(current_question, transcribed_answer)70 71 state["evaluations"].append({"question": current_question, "answer": transcribed_answer, **evaluation})72 state["current_question_index"] += 173 74 # 3. Decide what to show next: another question or the final results75 if state["current_question_index"] >= len(state["questions"]):76 # FINISH: Show the results screen77 summary_data = get_interview_summary(state["evaluations"])78 final_score = f"Final Score: {summary_data.get('final_score', 'N/A')} / 10"79 summary_text = summary_data.get('summary', 'Could not generate a summary.')80 return state, gr.update(visible=False), gr.update(visible=True), "", "", final_score, summary_text, gr.update(value=None), gr.update(interactive=False)81 else:82 # NEXT QUESTION: Prepare UI for the next question83 next_q_index = state["current_question_index"]84 next_q_text = state["questions"][next_q_index]['text']85 progress_text = f"Question {next_q_index + 1} of {len(state['questions'])}"86 next_q_audio = speak_text(next_q_text)87 return state, gr.update(visible=True), gr.update(visible=False), progress_text, next_q_text, "", "", next_q_audio, gr.update(interactive=True)88 89# --- Gradio UI Definition ---90with gr.Blocks(theme=gr.themes.Soft(), title="AI Interviewer") as demo:91 state = gr.State(value=initialize_state())92 93 gr.Markdown("# 🤖 AI Interviewer")94 gr.Markdown("Welcome! Please set up your interview, and the AI will guide you through the questions.")95 96 with gr.Row(visible=True) as setup_screen:97 candidate_name_input = gr.Textbox(label="Your Name")98 role_input = gr.Textbox(label="Job Role You're Applying For")99 num_questions_slider = gr.Slider(minimum=3, maximum=10, value=5, step=1, label="Number of Questions")100 start_button = gr.Button("Start Interview", variant="primary")101 102 with gr.Row(visible=False) as interview_screen:103 with gr.Column(scale=2):104 progress_label = gr.Label()105 # Use gr.Video for better compatibility106 webcam_feed = gr.Video(sources=["webcam"], label="Live Monitoring")107 question_audio = gr.Audio(autoplay=True, interactive=False, label="AI Interviewer")108 with gr.Column(scale=3):109 question_display = gr.Textbox(label="Current Question", interactive=False, lines=4)110 audio_answer_input = gr.Audio(sources=["microphone"], type="filepath", label="Record Your Answer Here")111 submit_answer_button = gr.Button("Submit Answer", variant="primary", interactive=False)112 113 with gr.Column(visible=False) as results_screen:114 final_score_display = gr.Label(label="Overall Performance")115 summary_display = gr.Textbox(label="Interview Summary", interactive=False, lines=10)116 117 # --- Event Handling Logic ---118 start_button.click(119 fn=start_interview,120 inputs=[candidate_name_input, role_input, num_questions_slider],121 outputs=[state, setup_screen, interview_screen, progress_label, question_display, question_audio, submit_answer_button]122 )123 124 submit_answer_button.click(125 fn=lambda: gr.Button("Processing...", interactive=False),126 outputs=[submit_answer_button]127 ).then(128 fn=process_answer,129 inputs=[state, audio_answer_input],130 outputs=[state, interview_screen, results_screen, progress_label, question_display, final_score_display, summary_display, question_audio, submit_answer_button]131 ).then(132 fn=lambda: gr.update(value=None),133 outputs=[audio_answer_input]134 )135 136if __name__ == "__main__":137 demo.launch(debug=True)