import sys import warnings warnings.filterwarnings('ignore') # Python 3.13 audioop workaround if sys.version_info >= (3, 13): try: import audioop except ImportError: import types audioop = types.ModuleType('audioop') sys.modules['audioop'] = audioop for func in ['add', 'adpcm2lin', 'adpcm32lin', 'alaw2lin', 'avg', 'avgpp', 'bias', 'cross', 'findfactor', 'findfit', 'findmax', 'getsample', 'lin2adpcm', 'lin2alaw', 'lin2ulaw', 'max', 'maxpp', 'mul', 'ratecv', 'reverse', 'rms', 'tomono', 'tostereo', 'ulaw2lin']: setattr(audioop, func, lambda *args, **kwargs: b'') sys.modules['pyaudioop'] = audioop # Now import gradio import gradio as gr import os import time import json import requests import PyPDF2 from datetime import datetime, timedelta import pytz import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from typing import Literal, Tuple, Dict, Optional import traceback # Import Agno components try: from phi.agent import Agent from phi.model.google import Gemini from phi.utils.log import logger except ImportError: try: from agno.agent import Agent from agno.models.google import Gemini from agno.utils.log import logger except ImportError: # Fallback to dummy logger import logging logger = logging.getLogger(__name__) # Create dummy Agent class if needed class Agent: def __init__(self, *args, **kwargs): pass class Gemini: def __init__(self, *args, **kwargs): pass class CustomZoomTool: def __init__(self, *, account_id: Optional[str] = None, client_id: Optional[str] = None, client_secret: Optional[str] = None, name: str = "zoom_tool"): self.account_id = account_id self.client_id = client_id self.client_secret = client_secret self.name = name self.token_url = "https://zoom.us/oauth/token" self.base_url = "https://api.zoom.us/v2" self.access_token = None self.token_expires_at = 0 def get_access_token(self) -> str: if self.access_token and time.time() < self.token_expires_at: return str(self.access_token) if not self.account_id or not self.client_id or not self.client_secret: logger.error("Missing Zoom credentials") return "" headers = {"Content-Type": "application/x-www-form-urlencoded"} data = {"grant_type": "account_credentials", "account_id": self.account_id} try: response = requests.post( self.token_url, headers=headers, data=data, auth=(self.client_id, self.client_secret), timeout=30 ) if response.status_code != 200: logger.error(f"Zoom token request failed: {response.status_code} - {response.text}") return "" response.raise_for_status() token_info = response.json() self.access_token = token_info["access_token"] expires_in = token_info["expires_in"] self.token_expires_at = time.time() + expires_in - 60 return str(self.access_token) except Exception as e: logger.error(f"Error fetching access token: {e}") return "" def create_meeting(self, title: str, start_time: str, duration: int = 60, attendee_email: str = "") -> Dict: access_token = self.get_access_token() if not access_token: return {"error": "Could not get access token. Please check your Zoom credentials."} headers = { "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" } meeting_data = { "topic": title, "type": 2, "start_time": start_time, "duration": duration, "timezone": "Asia/Kolkata", "settings": { "host_video": True, "participant_video": True, "join_before_host": False, "mute_upon_entry": True, "waiting_room": False, "registrants_email_notification": True } } try: response = requests.post( f"{self.base_url}/users/me/meetings", headers=headers, json=meeting_data, timeout=30 ) if response.status_code != 201: error_text = response.text if "scopes" in error_text: return {"error": f"Zoom app missing required scopes. Please add 'meeting:write:meeting' and 'meeting:write:meeting:admin' scopes to your Zoom app."} return {"error": f"Meeting creation failed: {response.status_code} - {error_text}"} response.raise_for_status() meeting_info = response.json() if attendee_email: self.add_attendee(meeting_info["id"], attendee_email) return meeting_info except Exception as e: logger.error(f"Error creating meeting: {e}") return {"error": f"Error creating meeting: {str(e)}"} def add_attendee(self, meeting_id: str, email: str) -> bool: access_token = self.get_access_token() if not access_token: return False headers = { "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" } registrant_data = { "email": email, "first_name": "Candidate", "last_name": "Interview" } try: response = requests.post( f"{self.base_url}/meetings/{meeting_id}/registrants", headers=headers, json=registrant_data ) response.raise_for_status() return True except Exception as e: logger.error(f"Error adding attendee: {e}") return False ROLE_REQUIREMENTS: Dict[str, str] = { "ai_ml_engineer": """ Required Skills: - Python, PyTorch/TensorFlow - Machine Learning algorithms and frameworks - Deep Learning and Neural Networks - Data preprocessing and analysis - MLOps and model deployment - RAG, LLM, Finetuning and Prompt Engineering """, "frontend_engineer": """ Required Skills: - React/Vue.js/Angular - HTML5, CSS3, JavaScript/TypeScript - Responsive design - State management - Frontend testing """, "backend_engineer": """ Required Skills: - Python/Java/Node.js - REST APIs - Database design and management - System architecture - Cloud services (AWS/GCP/Azure) - Kubernetes, Docker, CI/CD """ } class SessionState: def __init__(self): self.candidate_email = "" self.resume_text = "" self.analysis_complete = False self.is_selected = False self.gemini_api_key = os.getenv("GEMINI_API_KEY", "") self.zoom_account_id = os.getenv("ZOOM_ACCOUNT_ID", "") self.zoom_client_id = os.getenv("ZOOM_CLIENT_ID", "") self.zoom_client_secret = os.getenv("ZOOM_CLIENT_SECRET", "") self.email_sender = os.getenv("EMAIL_SENDER", "") self.email_passkey = os.getenv("EMAIL_PASSKEY", "") self.company_name = os.getenv("COMPANY_NAME", "AI Recruiting Team") session_state = SessionState() def create_resume_analyzer(): if not session_state.gemini_api_key: return None try: from phi.agent import Agent from phi.model.google import Gemini return Agent( model=Gemini( id="gemini-2.0-flash", api_key=session_state.gemini_api_key ), description="You are an expert technical recruiter who analyzes resumes.", instructions=[ "Analyze the resume against the provided job requirements", "Be lenient with AI/ML candidates who show strong potential", "Consider project experience as valid experience", "Value hands-on experience with key technologies", "Return a JSON response with selection decision and feedback" ], markdown=True ) except Exception as e: logger.error(f"Error creating resume analyzer: {e}") return None def create_email_agent(): try: from phi.agent import Agent from phi.model.google import Gemini return Agent( model=Gemini( id="gemini-1.5-flash", api_key=session_state.gemini_api_key ), description="You are a professional recruitment coordinator handling email communications.", instructions=[ "Draft professional recruitment emails", "Act like a human writing an email and use all lowercase letters", "Maintain a friendly yet professional tone", "Always end emails with exactly: 'best,\nthe ai recruiting team'", "Never include the sender's or receiver's name in the signature", f"The name of the company is '{session_state.company_name}'" ], markdown=True ) except Exception as e: logger.error(f"Error creating email agent: {e}") return None def create_scheduler_agent(): try: from phi.agent import Agent from phi.model.google import Gemini zoom_tools = CustomZoomTool( account_id=session_state.zoom_account_id, client_id=session_state.zoom_client_id, client_secret=session_state.zoom_client_secret ) return Agent( name="Interview Scheduler", model=Gemini( id="gemini-1.5-flash", api_key=session_state.gemini_api_key ), description="You are an interview scheduling coordinator.", instructions=[ "You are an expert at scheduling technical interviews using Zoom.", "Schedule interviews during business hours (9 AM - 5 PM EST)", "Create meetings with proper titles and descriptions", "Ensure all meeting details are included in responses", "Use ISO 8601 format for dates", "Handle scheduling errors gracefully" ], markdown=True, show_tool_calls=True ) except Exception as e: logger.error(f"Error creating scheduler agent: {e}") return None def send_email(to_email: str, subject: str, body: str) -> tuple[bool, str]: try: if not session_state.email_sender or not session_state.email_passkey: return False, "Email credentials not configured properly" msg = MIMEMultipart() msg['From'] = session_state.email_sender msg['To'] = to_email msg['Subject'] = subject msg.attach(MIMEText(body, 'plain')) server = smtplib.SMTP('smtp.gmail.com', 587) server.starttls() server.login(session_state.email_sender, session_state.email_passkey) text = msg.as_string() server.sendmail(session_state.email_sender, to_email, text) server.quit() return True, f"Email sent successfully to {to_email}" except Exception as e: logger.error(f"Error sending email: {e}") return False, f"Error sending email: {e}" def extract_text_from_pdf(pdf_path) -> str: try: with open(pdf_path, 'rb') as file: pdf_reader = PyPDF2.PdfReader(file) text = "" for page in pdf_reader.pages: text += page.extract_text() return text except Exception as e: return f"Error extracting PDF text: {str(e)}" def analyze_resume(resume_text: str, role: str, analyzer) -> Tuple[bool, str]: try: response = analyzer.run( f"""Please analyze this resume against the following requirements and provide your response in valid JSON format: Role Requirements: {ROLE_REQUIREMENTS[role]} Resume Text: {resume_text} Your response must be a valid JSON object like this: {{ "selected": true, "feedback": "Detailed feedback explaining the decision", "matching_skills": ["skill1", "skill2"], "missing_skills": ["skill3", "skill4"], "experience_level": "junior/mid/senior" }} Evaluation criteria: 1. Match at least 70% of required skills 2. Consider both theoretical knowledge and practical experience 3. Value project experience and real-world applications 4. Consider transferable skills from similar technologies 5. Look for evidence of continuous learning and adaptability IMPORTANT: - Return ONLY the JSON object - Do not include any markdown formatting, backticks, or explanatory text """ ) assistant_message = None if hasattr(response, 'messages'): for msg in response.messages: if hasattr(msg, 'role') and msg.role == 'assistant': assistant_message = msg.content break elif hasattr(response, 'content'): assistant_message = response.content if not assistant_message: raise ValueError("No assistant message found in response.") content = assistant_message.strip() if content.startswith('```json'): content = content[7:] if content.startswith('```'): content = content[3:] if content.endswith('```'): content = content[:-3] content = content.strip() result = json.loads(content) if not isinstance(result, dict) or not all(k in result for k in ["selected", "feedback"]): raise ValueError("Invalid response format") return result["selected"], result["feedback"] except Exception as e: return False, f"Resume analysis failed: {str(e)}" def send_selection_email(email_agent, to_email: str, role: str) -> tuple[bool, str]: try: response = email_agent.run( f""" Draft an email to congratulate a candidate on being selected for the {role} position. The email should: 1. Congratulate them on being selected 2. Explain the next steps in the process 3. Mention that they will receive interview details shortly 4. The name of the company is 'AI Recruiting Team' 5. Use all lowercase letters 6. End with exactly: 'best,\nthe ai recruiting team' Return only the email body text, no subject line. """ ) email_content = None if hasattr(response, 'messages'): for msg in response.messages: if hasattr(msg, 'role') and msg.role == 'assistant': email_content = msg.content break elif hasattr(response, 'content'): email_content = response.content if not email_content: return False, "Could not generate email content" return send_email(to_email, f"congratulations! you've been selected for the {role} position", email_content) except Exception as e: return False, f"Error sending selection email: {str(e)}" def send_rejection_email(email_agent, to_email: str, role: str, feedback: str) -> tuple[bool, str]: try: response = email_agent.run( f""" Draft an email regarding rejection for the {role} position. Use this specific style: 1. use all lowercase letters 2. be empathetic and human 3. mention specific feedback from: {feedback} 4. encourage them to upskill and try again 5. suggest some learning resources based on missing skills 6. end the email with exactly: best,\nthe ai recruiting team Do not include any names in the signature. Return only the email body text, no subject line. """ ) email_content = None if hasattr(response, 'messages'): for msg in response.messages: if hasattr(msg, 'role') and msg.role == 'assistant': email_content = msg.content break elif hasattr(response, 'content'): email_content = response.content if not email_content: return False, "Could not generate email content" return send_email(to_email, f"update on your application for {role} position", email_content) except Exception as e: return False, f"Error sending rejection email: {str(e)}" def schedule_interview(scheduler, candidate_email: str, email_agent, role: str) -> tuple[bool, str]: try: ist_tz = pytz.timezone('Asia/Kolkata') current_time_ist = datetime.now(ist_tz) tomorrow_ist = current_time_ist + timedelta(days=1) interview_time = tomorrow_ist.replace(hour=11, minute=0, second=0, microsecond=0) formatted_time = interview_time.strftime('%Y-%m-%dT%H:%M:%S') zoom_tool = CustomZoomTool( account_id=session_state.zoom_account_id, client_id=session_state.zoom_client_id, client_secret=session_state.zoom_client_secret ) meeting_info = zoom_tool.create_meeting( title=f"{role} Technical Interview", start_time=formatted_time, duration=60, attendee_email=candidate_email ) if "error" in meeting_info: meeting_details = f""" ๐Ÿ“… Interview Details: - Meeting Title: {role} Technical Interview - Date & Time: {interview_time.strftime('%B %d, %Y at %I:%M %p IST')} - Duration: 60 minutes Note: Zoom meeting link will be shared separately by our team. Please be available at the scheduled time. Time zone: IST (India Standard Time - UTC+5:30) """ else: meeting_details = f""" ๐Ÿ“… Interview Details: - Meeting Title: {meeting_info.get('topic', 'Technical Interview')} - Date & Time: {interview_time.strftime('%B %d, %Y at %I:%M %p IST')} - Duration: 60 minutes - Join URL: {meeting_info.get('join_url', 'N/A')} - Meeting ID: {meeting_info.get('id', 'N/A')} - Passcode: {meeting_info.get('password', 'N/A')} Please join the meeting 5 minutes early and be confident! Time zone: IST (India Standard Time - UTC+5:30) """ response = email_agent.run( f"""Draft an interview confirmation email with these details: - Role: {role} position - Meeting Details: {meeting_details} Important: - Use all lowercase letters - Clearly specify that the time is in IST (India Standard Time) - Ask the candidate to join 5 minutes early - Ask them to be confident and not nervous - End with exactly: 'best,\nthe ai recruiting team' Return only the email body text, no subject line. """ ) email_content = None if hasattr(response, 'messages'): for msg in response.messages: if hasattr(msg, 'role') and msg.role == 'assistant': email_content = msg.content break elif hasattr(response, 'content'): email_content = response.content if not email_content: return False, "Could not generate interview email content" success, message = send_email( to_email=candidate_email, subject=f"interview details for {role} position", body=email_content ) if success: return True, f"Interview scheduled successfully! Check your email for details." else: return False, f"Failed to send email notification: {message}" except Exception as e: return False, f"Unable to schedule interview: {str(e)}" # Gradio Functions def process_resume(file, role, email, gemini_key, zoom_account, zoom_client, zoom_secret, email_sender, email_pass, company): session_state.gemini_api_key = gemini_key or os.getenv("GEMINI_API_KEY", "") session_state.zoom_account_id = zoom_account or os.getenv("ZOOM_ACCOUNT_ID", "") session_state.zoom_client_id = zoom_client or os.getenv("ZOOM_CLIENT_ID", "") session_state.zoom_client_secret = zoom_secret or os.getenv("ZOOM_CLIENT_SECRET", "") session_state.email_sender = email_sender or os.getenv("EMAIL_SENDER", "") session_state.email_passkey = email_pass or os.getenv("EMAIL_PASSKEY", "") session_state.company_name = company or os.getenv("COMPANY_NAME", "AI Recruiting Team") session_state.candidate_email = email if not all([session_state.gemini_api_key, session_state.zoom_account_id, session_state.zoom_client_id, session_state.zoom_client_secret, session_state.email_sender, session_state.email_passkey]): return "โŒ Please configure all required settings in the Configuration tab first.", None, None, "", "" if file is None: return "โŒ Please upload your resume PDF.", None, None, "", "" if not email: return "โŒ Please enter your email address.", None, None, "", "" try: resume_text = extract_text_from_pdf(file) if not resume_text or "Error" in resume_text: return f"โŒ {resume_text}", None, None, "", "" session_state.resume_text = resume_text analyzer = create_resume_analyzer() if analyzer is None: return "โŒ Failed to create resume analyzer. Please check your Gemini API key.", None, None, "", "" email_agent = create_email_agent() if email_agent is None: return "โŒ Failed to create email agent.", None, None, "", "" is_selected, feedback = analyze_resume(resume_text, role, analyzer) if is_selected: session_state.analysis_complete = True session_state.is_selected = True success, message = send_selection_email(email_agent, email, role) email_status = f"โœ… Selection email sent!\n{message}" if success else f"โš ๏ธ {message}" return f"โœ… Congratulations! Your skills match our requirements.\n\nFeedback: {feedback}\n\n{email_status}", "Selected", feedback, "", "โœ… Application approved! Click 'Schedule Interview' to proceed." else: success, message = send_rejection_email(email_agent, email, role, feedback) email_status = f"โœ… Rejection email sent!\n{message}" if success else f"โš ๏ธ {message}" return f"โŒ Unfortunately, your skills don't match our requirements.\n\nFeedback: {feedback}\n\n{email_status}", "Rejected", feedback, "", "" except Exception as e: return f"โŒ Error processing application: {str(e)}\n\n{traceback.format_exc()}", None, None, "", "" def schedule_interview_gradio(role, email, gemini_key, zoom_account, zoom_client, zoom_secret, email_sender, email_pass, company): session_state.gemini_api_key = gemini_key or os.getenv("GEMINI_API_KEY", "") session_state.zoom_account_id = zoom_account or os.getenv("ZOOM_ACCOUNT_ID", "") session_state.zoom_client_id = zoom_client or os.getenv("ZOOM_CLIENT_ID", "") session_state.zoom_client_secret = zoom_secret or os.getenv("ZOOM_CLIENT_SECRET", "") session_state.email_sender = email_sender or os.getenv("EMAIL_SENDER", "") session_state.email_passkey = email_pass or os.getenv("EMAIL_PASSKEY", "") session_state.company_name = company or os.getenv("COMPANY_NAME", "AI Recruiting Team") session_state.candidate_email = email if not all([session_state.gemini_api_key, session_state.zoom_account_id, session_state.zoom_client_id, session_state.zoom_client_secret, session_state.email_sender, session_state.email_passkey]): return "โŒ Please configure all required settings in the Configuration tab first." if not email: return "โŒ Please enter your email address." if not session_state.is_selected: return "โŒ You must be selected first. Please analyze your resume first." try: email_agent = create_email_agent() if email_agent is None: return "โŒ Failed to create email agent." scheduler_agent = create_scheduler_agent() if scheduler_agent is None: return "โŒ Failed to create scheduler agent." success, message = schedule_interview(scheduler_agent, email, email_agent, role) if success: return f"โœ… Interview scheduled successfully!\n\n{message}" else: return f"โš ๏ธ {message}" except Exception as e: return f"โŒ Error scheduling interview: {str(e)}\n\n{traceback.format_exc()}" def reset_application(): session_state.candidate_email = "" session_state.resume_text = "" session_state.analysis_complete = False session_state.is_selected = False return "๐Ÿ”„ Application reset successfully! You can start a new application.", "", "", "", "" def test_email_config(email_sender_val, email_pass_val): if not email_sender_val or not email_pass_val: return "โŒ Please fill in both Email Sender and Email App Password fields." session_state.email_sender = email_sender_val session_state.email_passkey = email_pass_val success, message = send_email( to_email=email_sender_val, subject="Test Email from AI Recruitment System", body="This is a test email to verify your email configuration.\n\nbest,\nthe ai recruiting team" ) return f"{'โœ…' if success else 'โŒ'} Test Result: {message}" def test_zoom_config(zoom_account_val, zoom_client_val, zoom_secret_val): if not all([zoom_account_val, zoom_client_val, zoom_secret_val]): return "โŒ Please fill in all Zoom configuration fields." zoom_test = CustomZoomTool( account_id=zoom_account_val, client_id=zoom_client_val, client_secret=zoom_secret_val ) token = zoom_test.get_access_token() if token: return "โœ… Zoom authentication successful!" else: return "โŒ Zoom authentication failed! Check your credentials." def update_role_requirements(role_choice): return ROLE_REQUIREMENTS[role_choice] # Create Gradio UI with gr.Blocks(title="AI Recruitment System", theme=gr.themes.Soft()) as demo: gr.Markdown(""" # ๐ŸŽฏ AI Recruitment System Upload your resume and get instant feedback! Our AI analyzes your skills against job requirements. """) with gr.Tabs(): with gr.TabItem("๐Ÿ“ Application"): with gr.Row(): with gr.Column(scale=2): role = gr.Dropdown( choices=["ai_ml_engineer", "frontend_engineer", "backend_engineer"], label="Select Role", value="ai_ml_engineer" ) resume_file = gr.File( label="Upload Resume (PDF)", file_types=[".pdf"], type="filepath" ) email = gr.Textbox( label="Candidate's Email Address", placeholder="Enter your email address", type="text" ) with gr.Row(): analyze_btn = gr.Button("๐Ÿ” Analyze Resume", variant="primary", size="lg") reset_btn = gr.Button("๐Ÿ”„ Reset Application", variant="secondary", size="lg") status_output = gr.Textbox( label="Status", lines=10, interactive=False, value="Ready to process your application..." ) with gr.Column(scale=1): gr.Markdown("### ๐Ÿ“‹ Role Requirements") role_requirements = gr.Markdown(ROLE_REQUIREMENTS["ai_ml_engineer"]) result_status = gr.Textbox(label="Application Result", lines=2, interactive=False, visible=True) feedback_output = gr.Textbox(label="Feedback Details", lines=5, interactive=False, visible=True) interview_status_indicator = gr.Textbox(label="Interview Status", lines=3, interactive=False, value="", visible=True) with gr.Row(): with gr.Column(): gr.Markdown("### ๐Ÿ“… Interview Scheduling") schedule_btn = gr.Button("๐Ÿ“… Schedule Interview", variant="primary", size="lg") interview_status = gr.Textbox( label="Interview Status", lines=6, interactive=False, value="Schedule your interview after resume analysis..." ) with gr.TabItem("โš™๏ธ Configuration"): gr.Markdown(""" ### ๐Ÿ”‘ API Configuration **Note:** You can either enter credentials here or set them as Hugging Face Space secrets. Secrets will be automatically used if fields are left empty. """) gemini_key = gr.Textbox( label="Gemini API Key", type="password", placeholder="Enter your Gemini API key (or set as GEMINI_API_KEY secret)", value=os.getenv("GEMINI_API_KEY", "") ) with gr.Row(): zoom_account = gr.Textbox( label="Zoom Account ID", type="password", placeholder="Zoom Account ID (or set as ZOOM_ACCOUNT_ID secret)", value=os.getenv("ZOOM_ACCOUNT_ID", "") ) zoom_client = gr.Textbox( label="Zoom Client ID", type="password", placeholder="Zoom Client ID (or set as ZOOM_CLIENT_ID secret)", value=os.getenv("ZOOM_CLIENT_ID", "") ) zoom_secret = gr.Textbox( label="Zoom Client Secret", type="password", placeholder="Zoom Client Secret (or set as ZOOM_CLIENT_SECRET secret)", value=os.getenv("ZOOM_CLIENT_SECRET", "") ) with gr.Row(): email_sender = gr.Textbox( label="Sender Email", placeholder="your-email@gmail.com (or set as EMAIL_SENDER secret)", value=os.getenv("EMAIL_SENDER", "") ) email_pass = gr.Textbox( label="Email App Password", type="password", placeholder="16-character app password (or set as EMAIL_PASSKEY secret)", value=os.getenv("EMAIL_PASSKEY", "") ) company = gr.Textbox( label="Company Name", placeholder="Your Company Name", value=os.getenv("COMPANY_NAME", "AI Recruiting Team") ) gr.Markdown("### ๐Ÿงช Test Configuration") with gr.Row(): test_email_btn = gr.Button("๐Ÿ“ง Test Email", variant="secondary") test_zoom_btn = gr.Button("๐Ÿ”— Test Zoom", variant="secondary") test_output = gr.Textbox( label="Test Results", lines=4, interactive=False, value="Click a test button to verify your configuration..." ) with gr.TabItem("๐Ÿ“– Help"): gr.Markdown(""" ## ๐Ÿ“‹ How to Use the AI Recruitment System ### Step 1: Configure Settings 1. Go to the **Configuration** tab 2. Enter your **Gemini API Key** from [Google AI Studio](https://aistudio.google.com) 3. Enter your **Zoom credentials** (Account ID, Client ID, Client Secret) 4. Enter your **Email settings** (Gmail address and App Password) 5. Click **Test Email** and **Test Zoom** to verify your configuration ### Step 2: Submit Application 1. Go to the **Application** tab 2. Select the **Role** you're applying for 3. Upload your **Resume** (PDF format) 4. Enter your **Email Address** 5. Click **Analyze Resume** ### Step 3: Review Results - If selected: You'll receive a confirmation email and can schedule an interview - If rejected: You'll receive constructive feedback via email ### Step 4: Schedule Interview (if selected) 1. Click **Schedule Interview** 2. Check your email for the Zoom meeting details 3. Join the interview 5 minutes early """) # Event handlers role.change(fn=update_role_requirements, inputs=role, outputs=role_requirements) analyze_btn.click( fn=process_resume, inputs=[resume_file, role, email, gemini_key, zoom_account, zoom_client, zoom_secret, email_sender, email_pass, company], outputs=[status_output, result_status, feedback_output, interview_status_indicator, status_output] ) schedule_btn.click( fn=schedule_interview_gradio, inputs=[role, email, gemini_key, zoom_account, zoom_client, zoom_secret, email_sender, email_pass, company], outputs=interview_status ) reset_btn.click( fn=reset_application, inputs=[], outputs=[status_output, result_status, feedback_output, interview_status_indicator, interview_status] ) test_email_btn.click( fn=test_email_config, inputs=[email_sender, email_pass], outputs=test_output ) test_zoom_btn.click( fn=test_zoom_config, inputs=[zoom_account, zoom_client, zoom_secret], outputs=test_output ) if __name__ == "__main__": demo.launch(share=True)