Spaces:
Build error
Build error
| 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) |