BHARATH0726 commited on
Commit
ac1e270
Β·
verified Β·
1 Parent(s): 3f9793e

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +636 -0
app.py ADDED
@@ -0,0 +1,636 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Literal, Tuple, Dict, Optional
2
+ import os
3
+ import time
4
+ import json
5
+ import requests
6
+ import PyPDF2
7
+ from datetime import datetime, timedelta
8
+ import pytz
9
+ import streamlit as st
10
+ from phi.agent import Agent
11
+ from phi.model.openai import OpenAIChat
12
+ from phi.tools.email import EmailTools
13
+ from phi.tools.zoom import ZoomTool
14
+ from phi.utils.log import logger
15
+ from streamlit_pdf_viewer import pdf_viewer
16
+
17
+
18
+
19
+ class CustomZoomTool(ZoomTool):
20
+ def __init__(self, *, account_id: Optional[str] = None, client_id: Optional[str] = None, client_secret: Optional[str] = None, name: str = "zoom_tool"):
21
+ super().__init__(account_id=account_id, client_id=client_id, client_secret=client_secret, name=name)
22
+ self.token_url = "https://zoom.us/oauth/token"
23
+ self.access_token = None
24
+ self.token_expires_at = 0
25
+
26
+ def get_access_token(self) -> str:
27
+ if self.access_token and time.time() < self.token_expires_at:
28
+ return str(self.access_token)
29
+
30
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
31
+ data = {"grant_type": "account_credentials", "account_id": self.account_id}
32
+
33
+ try:
34
+ response = requests.post(self.token_url, headers=headers, data=data, auth=(self.client_id, self.client_secret))
35
+ response.raise_for_status()
36
+
37
+ token_info = response.json()
38
+ self.access_token = token_info["access_token"]
39
+ expires_in = token_info["expires_in"]
40
+ self.token_expires_at = time.time() + expires_in - 60
41
+
42
+ self._set_parent_token(str(self.access_token))
43
+ return str(self.access_token)
44
+
45
+ except requests.RequestException as e:
46
+ logger.error(f"Error fetching access token: {e}")
47
+ return ""
48
+
49
+ def _set_parent_token(self, token: str) -> None:
50
+ """Helper method to set the token in the parent ZoomTool class"""
51
+ if token:
52
+ self._ZoomTool__access_token = token
53
+
54
+
55
+ # Role requirements as a constant dictionary
56
+ ROLE_REQUIREMENTS: Dict[str, str] = {
57
+ "ai_ml_engineer": """
58
+ Required Skills:
59
+ - Python, PyTorch/TensorFlow
60
+ - Machine Learning algorithms and frameworks
61
+ - Deep Learning and Neural Networks
62
+ - Data preprocessing and analysis
63
+ - MLOps and model deployment
64
+ - RAG, LLM, Finetuning and Prompt Engineering
65
+ """,
66
+
67
+ "frontend_engineer": """
68
+ Required Skills:
69
+ - React/Vue.js/Angular
70
+ - HTML5, CSS3, JavaScript/TypeScript
71
+ - Responsive design
72
+ - State management
73
+ - Frontend testing
74
+ """,
75
+
76
+ "backend_engineer": """
77
+ Required Skills:
78
+ - Python/Java/Node.js
79
+ - REST APIs
80
+ - Database design and management
81
+ - System architecture
82
+ - Cloud services (AWS/GCP/Azure)
83
+ - Kubernetes, Docker, CI/CD
84
+ """
85
+ }
86
+
87
+
88
+ def init_session_state() -> None:
89
+ """Initialize only necessary session state variables."""
90
+ defaults = {
91
+ 'candidate_email': "", 'openai_api_key': "", 'resume_text': "", 'analysis_complete': False,
92
+ 'is_selected': False, 'zoom_account_id': "", 'zoom_client_id': "", 'zoom_client_secret': "",
93
+ 'email_sender': "", 'email_passkey': "", 'company_name': "", 'current_pdf': None
94
+ }
95
+ for key, value in defaults.items():
96
+ if key not in st.session_state:
97
+ st.session_state[key] = value
98
+
99
+
100
+ def create_resume_analyzer() -> Agent:
101
+ """Creates and returns a resume analysis agent."""
102
+ if not st.session_state.openai_api_key:
103
+ st.error("Please enter your OpenAI API key first.")
104
+ return None
105
+
106
+ return Agent(
107
+ model=OpenAIChat(
108
+ id="gpt-4o",
109
+ api_key=st.session_state.openai_api_key
110
+ ),
111
+ description="You are an expert technical recruiter who analyzes resumes.",
112
+ instructions=[
113
+ "Analyze the resume against the provided job requirements",
114
+ "Be lenient with AI/ML candidates who show strong potential",
115
+ "Consider project experience as valid experience",
116
+ "Value hands-on experience with key technologies",
117
+ "Return a JSON response with selection decision and feedback"
118
+ ],
119
+ markdown=True
120
+ )
121
+
122
+ def create_email_agent() -> Agent:
123
+ return Agent(
124
+ model=OpenAIChat(
125
+ id="gpt-4o",
126
+ api_key=st.session_state.openai_api_key
127
+ ),
128
+ tools=[EmailTools(
129
+ receiver_email=st.session_state.candidate_email,
130
+ sender_email=st.session_state.email_sender,
131
+ sender_name=st.session_state.company_name,
132
+ sender_passkey=st.session_state.email_passkey
133
+ )],
134
+ description="You are a professional recruitment coordinator handling email communications.",
135
+ instructions=[
136
+ "Draft and send professional recruitment emails",
137
+ "Act like a human writing an email",
138
+ "Maintain a friendly yet professional tone",
139
+ "Always end emails with exactly: 'Best,\nthe ClutchAi recruiting team'",
140
+ "Never include the sender's or receiver's name in the signature",
141
+ f"The name of the company is '{st.session_state.company_name}'"
142
+ ],
143
+ markdown=True,
144
+ show_tool_calls=True
145
+ )
146
+
147
+
148
+ def create_scheduler_agent() -> Agent:
149
+ zoom_tools = CustomZoomTool(
150
+ account_id=st.session_state.zoom_account_id,
151
+ client_id=st.session_state.zoom_client_id,
152
+ client_secret=st.session_state.zoom_client_secret
153
+ )
154
+
155
+ return Agent(
156
+ name="Interview Scheduler",
157
+ model=OpenAIChat(
158
+ id="gpt-4o",
159
+ api_key=st.session_state.openai_api_key
160
+ ),
161
+ tools=[zoom_tools],
162
+ description="You are an interview scheduling coordinator.",
163
+ instructions=[
164
+ "You are an expert at scheduling technical interviews using Zoom.",
165
+ "Schedule interviews during business hours (9 AM - 5 PM EST)",
166
+ "Create meetings with proper titles and descriptions",
167
+ "Ensure all meeting details are included in responses",
168
+ "Use ISO 8601 format for dates",
169
+ "Handle scheduling errors gracefully"
170
+ ],
171
+ markdown=True,
172
+ show_tool_calls=True
173
+ )
174
+
175
+
176
+ def extract_text_from_pdf(pdf_file) -> str:
177
+ try:
178
+ pdf_reader = PyPDF2.PdfReader(pdf_file)
179
+ text = ""
180
+ for page in pdf_reader.pages:
181
+ text += page.extract_text()
182
+ return text
183
+ except Exception as e:
184
+ st.error(f"Error extracting PDF text: {str(e)}")
185
+ return ""
186
+
187
+ def extract_email_from_resume(resume_text: str, analyzer: Agent) -> Optional[str]:
188
+ """
189
+ Uses GPT-4 to extract an email address from the resume text.
190
+ """
191
+ try:
192
+ response = analyzer.run(
193
+ f"""Extract the email address from the following text:
194
+ Resume Text:
195
+ {resume_text}
196
+
197
+ Return the email address only, or 'None' if not found."""
198
+ )
199
+ assistant_message = next((msg.content for msg in response.messages if msg.role == 'assistant'), None)
200
+ return assistant_message.strip() if assistant_message and assistant_message != 'None' else None
201
+
202
+ except Exception as e:
203
+ logger.error(f"Error extracting email from resume: {str(e)}")
204
+ return None
205
+
206
+
207
+ def analyze_resume(
208
+ resume_text: str,
209
+ role: Literal["ai_ml_engineer", "frontend_engineer", "backend_engineer"],
210
+ analyzer: Agent
211
+ ) -> Tuple[bool, str,int]:
212
+ try:
213
+ response = analyzer.run(
214
+ f"""Act as a ATS tracking agent, and score the resume first and Please analyze this resume against the following requirements and provide your response in valid JSON format also provide a score out of 100.:
215
+ Role Requirements:
216
+ {ROLE_REQUIREMENTS[role]}
217
+ Resume Text:
218
+ {resume_text}
219
+ Your response must be a valid JSON object like this:
220
+ {{
221
+ "selected": true/false,
222
+ "feedback": "Detailed feedback explaining the decision",
223
+ "matching_skills": ["skill1", "skill2"],
224
+ "missing_skills": ["skill3", "skill4"],
225
+ "experience_level": "junior/mid/senior"
226
+ }}
227
+ Evaluation criteria:
228
+ 1. Match at least 70% of required skills
229
+ 2. Consider both theoretical knowledge and practical experience
230
+ 3. Value project experience and real-world applications
231
+ 4. Consider transferable skills from similar technologies
232
+ 5. Look for evidence of continuous learning and adaptability
233
+ Important: Return ONLY the JSON object without any markdown formatting or backticks.
234
+ """
235
+ )
236
+
237
+ assistant_message = next((msg.content for msg in response.messages if msg.role == 'assistant'), None)
238
+ if not assistant_message:
239
+ raise ValueError("No assistant message found in response.")
240
+
241
+ result = json.loads(assistant_message.strip())
242
+ if not isinstance(result, dict) or not all(k in result for k in ["selected", "feedback"]):
243
+ raise ValueError("Invalid response format")
244
+
245
+ return result["selected"], result["feedback"]
246
+
247
+ except (json.JSONDecodeError, ValueError) as e:
248
+ st.error(f"Error processing response: {str(e)}")
249
+ return False, f"Error analyzing resume: {str(e)}"
250
+
251
+ # The modified sections include analyze_resume_with_score and main logic.
252
+
253
+ def analyze_resume_with_score(
254
+ resume_text: str,
255
+ role: Literal["ai_ml_engineer", "frontend_engineer", "backend_engineer"],
256
+ analyzer: Agent
257
+ ) -> Tuple[bool, str, int]:
258
+ try:
259
+ response = analyzer.run(
260
+ f"""Act as an ATS tracking agent. Please score this resume and analyze it against the following requirements:
261
+ Role Requirements:
262
+ {ROLE_REQUIREMENTS[role]}
263
+ Resume Text:
264
+ {resume_text}
265
+ Your response must be a valid JSON object like this:
266
+ {{
267
+ "selected": true/false,
268
+ "feedback": "Detailed feedback explaining the decision",
269
+ "ats_score": # ATS score as an integer percentage
270
+ "matching_skills": ["skill1", "skill2"],
271
+ "missing_skills": ["skill3", "skill4"],
272
+ "experience_level": "junior/mid/senior"
273
+ }}
274
+ Evaluation criteria:
275
+ 1. Match at least 70% of required skills
276
+ 2. Consider both theoretical knowledge and practical experience
277
+ 3. Value project experience and real-world applications
278
+ 4. Consider transferable skills from similar technologies
279
+ 5. Look for evidence of continuous learning and adaptability
280
+ Important: Return ONLY the JSON object without any markdown formatting or backticks.
281
+ """
282
+ )
283
+
284
+ assistant_message = next((msg.content for msg in response.messages if msg.role == 'assistant'), None)
285
+
286
+ # DEBUG: Print the raw response
287
+ print("DEBUG: Raw assistant message:", assistant_message)
288
+
289
+ if not assistant_message:
290
+ raise ValueError("No assistant message found in response.")
291
+
292
+ result = json.loads(assistant_message.strip())
293
+
294
+ # DEBUG: Print parsed JSON response
295
+ print("DEBUG: Parsed JSON response:", result)
296
+
297
+ if not isinstance(result, dict) or not all(k in result for k in ["selected", "feedback", "ats_score"]):
298
+ raise ValueError("Invalid response format")
299
+
300
+ return result["selected"], result["feedback"], result["ats_score"]
301
+
302
+ except (json.JSONDecodeError, ValueError) as e:
303
+ st.error(f"Error processing response: {str(e)}")
304
+ return False, f"Error analyzing resume: {str(e)}", 0
305
+
306
+
307
+
308
+ def send_selection_email(email_agent: Agent, to_email: str, role: str) -> None:
309
+ email_agent.run(
310
+ f"""
311
+ Send an email to {to_email} regarding their selection for the {role} position.
312
+ The email should:
313
+ 1. Congratulate them on being selected
314
+ 2. Explain the next steps in the process
315
+ 3. Mention that they will receive interview details shortly
316
+ 4. The name of the company is 'ClutchAI Recruiting Team'
317
+ """
318
+ )
319
+
320
+
321
+ def send_rejection_email(email_agent: Agent, to_email: str, role: str, feedback: str) -> None:
322
+ """
323
+ Send a rejection email with constructive feedback.
324
+ """
325
+ email_agent.run(
326
+ f"""
327
+ Send an email to {to_email} regarding their application for the {role} position.
328
+ Use this specific style:
329
+ 2. be empathetic and human
330
+ 3. mention specific feedback from: {feedback}
331
+ 4. encourage them to upskill and try again
332
+ 5. suggest some learning resources based on missing skills
333
+ 6. end the email with exactly:
334
+ Best,
335
+ The Clutchai recruiting team
336
+
337
+ Do not include any names in the signature.
338
+ The tone should be like a human writing a quick but thoughtful email.
339
+ """
340
+ )
341
+
342
+
343
+ def schedule_interview(scheduler: Agent, candidate_email: str, email_agent: Agent, role: str) -> None:
344
+ """
345
+ Schedule interviews during business hours (9 AM - 5 PM IST).
346
+ """
347
+ try:
348
+ # Get current time in IST
349
+ ny_tz= pytz.timezone('US/Eastern')
350
+ current_time_ist = datetime.now(ny_tz)
351
+
352
+ tomorrow_ist = current_time_ist + timedelta(days=1)
353
+ interview_time = tomorrow_ist.replace(hour=11, minute=0, second=0, microsecond=0)
354
+ formatted_time = interview_time.strftime('%Y-%m-%dT%H:%M:%S')
355
+
356
+ meeting_response = scheduler.run(
357
+ f"""Schedule a 60-minute technical interview with these specifications:
358
+ - Title: '{role} Technical Interview'
359
+ - Date: {formatted_time}
360
+ - Timezone: EST (Eastern Standard Time)
361
+ - Attendee: {candidate_email}
362
+
363
+ Important Notes:
364
+ - The meeting must be between 9 AM - 5 PM IST
365
+ - Use EST (UTC+5:30) timezone for all communications
366
+ - Include timezone information in the meeting details
367
+ """
368
+ )
369
+
370
+ email_agent.run(
371
+ f"""Send an interview confirmation email with these details:
372
+ - Role: {role} position
373
+ - Meeting Details: {meeting_response}
374
+
375
+ Important:
376
+ - Clearly specify that the time is in EST (Eastern Standard Time)
377
+ - Ask the candidate to join 5 minutes early
378
+ - Include timezone conversion link if possible
379
+ - Ask him to be confident and not so nervous and prepare well for the interview
380
+ """
381
+ )
382
+
383
+ st.success("Interview scheduled successfully!")
384
+
385
+ except Exception as e:
386
+ logger.error(f"Error scheduling interview: {str(e)}")
387
+ st.error("Unable to schedule interview. Please try again.")
388
+
389
+
390
+ def main() -> None:
391
+ st.set_page_config(page_title="AI Recruitment System", page_icon="πŸ€–", layout="wide")
392
+
393
+ # Inject custom CSS for dark blue background
394
+ st.markdown(
395
+ """
396
+ <style>
397
+ .stApp {
398
+ background-color: #001f3f; /* Dark blue */
399
+ color: white; /* White text for contrast */
400
+ }
401
+ </style>
402
+ """,
403
+ unsafe_allow_html=True
404
+ )
405
+
406
+ st.title("🌟 AI Recruitment System")
407
+ st.markdown(
408
+ """
409
+ Welcome to the AI Recruitment System!
410
+ Streamline your hiring process with AI-powered resume analysis and interview scheduling.
411
+ """
412
+ )
413
+ st.markdown("---")
414
+
415
+
416
+ init_session_state()
417
+ with st.sidebar:
418
+ st.header("Configuration")
419
+
420
+ # OpenAI Configuration
421
+ st.subheader("OpenAI Settings")
422
+ api_key = st.text_input("OpenAI API Key", type="password", value=st.session_state.openai_api_key, help="Get your API key from platform.openai.com")
423
+ if api_key: st.session_state.openai_api_key = api_key
424
+
425
+ st.subheader("Zoom Settings")
426
+ zoom_account_id = st.text_input("Zoom Account ID", type="password", value=st.session_state.zoom_account_id)
427
+ zoom_client_id = st.text_input("Zoom Client ID", type="password", value=st.session_state.zoom_client_id)
428
+ zoom_client_secret = st.text_input("Zoom Client Secret", type="password", value=st.session_state.zoom_client_secret)
429
+
430
+ st.subheader("Email Settings")
431
+ email_sender = st.text_input("Sender Email", value=st.session_state.email_sender, help="Email address to send from")
432
+ email_passkey = st.text_input("Email App Password", type="password", value=st.session_state.email_passkey, help="App-specific password for email")
433
+ company_name = st.text_input("Company Name", value=st.session_state.company_name, help="Name to use in email communications")
434
+
435
+ if zoom_account_id: st.session_state.zoom_account_id = zoom_account_id
436
+ if zoom_client_id: st.session_state.zoom_client_id = zoom_client_id
437
+ if zoom_client_secret: st.session_state.zoom_client_secret = zoom_client_secret
438
+ if email_sender: st.session_state.email_sender = email_sender
439
+ if email_passkey: st.session_state.email_passkey = email_passkey
440
+ if company_name: st.session_state.company_name = company_name
441
+
442
+ required_configs = {'OpenAI API Key': st.session_state.openai_api_key, 'Zoom Account ID': st.session_state.zoom_account_id,
443
+ 'Zoom Client ID': st.session_state.zoom_client_id, 'Zoom Client Secret': st.session_state.zoom_client_secret,
444
+ 'Email Sender': st.session_state.email_sender, 'Email Password': st.session_state.email_passkey,
445
+ 'Company Name': st.session_state.company_name}
446
+
447
+ missing_configs = [k for k, v in required_configs.items() if not v]
448
+ if missing_configs:
449
+ st.warning(f"Please configure the following in the sidebar: {', '.join(missing_configs)}")
450
+ return
451
+
452
+ if not st.session_state.openai_api_key:
453
+ st.warning("Please enter your OpenAI API key in the sidebar to continue.")
454
+ return
455
+
456
+ role = st.selectbox("Select the role you're applying for:", ["ai_ml_engineer", "frontend_engineer", "backend_engineer"])
457
+ with st.expander("View Required Skills", expanded=True): st.markdown(ROLE_REQUIREMENTS[role])
458
+
459
+ # Add a "New Application" button before the resume upload
460
+ if st.button("πŸ“ New Application"):
461
+ # Clear only the application-related states
462
+ keys_to_clear = ['resume_text', 'analysis_complete', 'is_selected', 'candidate_email', 'current_pdf']
463
+ for key in keys_to_clear:
464
+ if key in st.session_state:
465
+ st.session_state[key] = None if key == 'current_pdf' else ""
466
+ st.rerun()
467
+
468
+ resume_file = st.file_uploader("Upload your resume (PDF)", type=["pdf"], key="resume_uploader")
469
+ if resume_file is not None and resume_file != st.session_state.get('current_pdf'):
470
+ st.session_state.current_pdf = resume_file
471
+ st.session_state.resume_text = ""
472
+ st.session_state.analysis_complete = False
473
+ st.session_state.is_selected = False
474
+ st.rerun()
475
+
476
+ if resume_file:
477
+ st.subheader("Uploaded Resume")
478
+ col1, col2 = st.columns([4, 1])
479
+
480
+ with col1:
481
+ import tempfile, os
482
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file:
483
+ tmp_file.write(resume_file.read())
484
+ tmp_file_path = tmp_file.name
485
+ resume_file.seek(0)
486
+ try: pdf_viewer(tmp_file_path)
487
+ finally: os.unlink(tmp_file_path)
488
+
489
+ with col2:
490
+ st.download_button(label="πŸ“₯ Download", data=resume_file, file_name=resume_file.name, mime="application/pdf")
491
+ # Process the resume text
492
+ if not st.session_state.resume_text:
493
+ with st.spinner("Processing your resume..."):
494
+ resume_text = extract_text_from_pdf(resume_file)
495
+ if resume_text:
496
+ st.session_state.resume_text = resume_text
497
+ st.success("Resume processed successfully!")
498
+
499
+ # Create a resume analyzer to extract email
500
+ resume_analyzer = create_resume_analyzer()
501
+ if resume_analyzer:
502
+ email_from_resume = extract_email_from_resume(resume_text, resume_analyzer)
503
+ if email_from_resume:
504
+ st.session_state.candidate_email = email_from_resume
505
+ st.success(f"Email ID extracted: {email_from_resume}")
506
+ else:
507
+ st.warning("Could not extract an email ID from the resume.")
508
+ else:
509
+ st.error("Could not process the PDF. Please try again.")
510
+
511
+
512
+ # Email input with session state
513
+ email = st.text_input(
514
+ "Candidate's email address",
515
+ value=st.session_state.candidate_email,
516
+ key="email_input"
517
+ )
518
+ st.session_state.candidate_email = email
519
+
520
+ # Analysis and next steps
521
+ if st.session_state.resume_text and email and not st.session_state.analysis_complete:
522
+ if st.button("Analyze Resume"):
523
+ with st.spinner("Analyzing your resume..."):
524
+ resume_analyzer = create_resume_analyzer()
525
+ email_agent = create_email_agent() # Create email agent here
526
+
527
+ if resume_analyzer and email_agent:
528
+ print("DEBUG: Starting resume analysis")
529
+ is_selected, feedback, ats_score = analyze_resume_with_score(
530
+ st.session_state.resume_text,
531
+ role,
532
+ resume_analyzer
533
+ )
534
+ print(f"DEBUG: Analysis complete - Selected: {is_selected}, Feedback: {feedback}")
535
+
536
+ # Display resume text
537
+ st.subheader("Resume Contents")
538
+ st.text_area("Extracted Resume Text", st.session_state.resume_text, height=300, disabled=True)
539
+
540
+ # Display ATS score
541
+ st.subheader("ATS Score")
542
+ st.metric(label="ATS Match Score", value=f"{ats_score}%", help="Percentage match with the job requirements")
543
+
544
+ if is_selected:
545
+ st.success("Great! Candiate's skills match our requirements.")
546
+ st.session_state.analysis_complete = True
547
+ st.session_state.is_selected = True
548
+ st.rerun()
549
+ else:
550
+ st.warning("Unfortunately, your skills don't match our requirements.")
551
+ st.write(f"Feedback: {feedback}")
552
+
553
+ # Send rejection email
554
+ with st.spinner("Sending feedback email..."):
555
+ try:
556
+ send_rejection_email(
557
+ email_agent=email_agent,
558
+ to_email=email,
559
+ role=role,
560
+ feedback=feedback
561
+ )
562
+ st.info("We've sent you an email with detailed feedback.")
563
+ except Exception as e:
564
+ logger.error(f"Error sending rejection email: {e}")
565
+ st.error("Could not send feedback email. Please try again.")
566
+
567
+ if st.session_state.get('analysis_complete') and st.session_state.get('is_selected', False):
568
+ st.success("Congratulations! Your skills match our requirements.")
569
+ st.info("Click 'Proceed with Application' to continue with the interview process.")
570
+
571
+ if st.button("Proceed with Application", key="proceed_button"):
572
+ print("DEBUG: Proceed button clicked") # Debug
573
+ with st.spinner("πŸ”„ Processing your application..."):
574
+ try:
575
+ print("DEBUG: Creating email agent") # Debug
576
+ email_agent = create_email_agent()
577
+ print(f"DEBUG: Email agent created: {email_agent}") # Debug
578
+
579
+ print("DEBUG: Creating scheduler agent") # Debug
580
+ scheduler_agent = create_scheduler_agent()
581
+ print(f"DEBUG: Scheduler agent created: {scheduler_agent}") # Debug
582
+
583
+ # 3. Send selection email
584
+ with st.status("πŸ“§ Sending confirmation email...", expanded=True) as status:
585
+ print(f"DEBUG: Attempting to send email to {st.session_state.candidate_email}") # Debug
586
+ send_selection_email(
587
+ email_agent,
588
+ st.session_state.candidate_email,
589
+ role
590
+ )
591
+ print("DEBUG: Email sent successfully") # Debug
592
+ status.update(label="βœ… Confirmation email sent!")
593
+
594
+ # 4. Schedule interview
595
+ with st.status("πŸ“… Scheduling interview...", expanded=True) as status:
596
+ print("DEBUG: Attempting to schedule interview") # Debug
597
+ schedule_interview(
598
+ scheduler_agent,
599
+ st.session_state.candidate_email,
600
+ email_agent,
601
+ role
602
+ )
603
+ print("DEBUG: Interview scheduled successfully") # Debug
604
+ status.update(label="βœ… Interview scheduled!")
605
+
606
+ print("DEBUG: All processes completed successfully") # Debug
607
+ st.success("""
608
+ πŸŽ‰ Application Successfully Processed!
609
+
610
+ Tasks Done:
611
+ 1. Selection confirmation βœ…
612
+ 2. Interview details with Zoom link πŸ”—
613
+
614
+ Next steps:
615
+ 1. Join the interview 5 minutes early
616
+ 2. Evaluate the candiate
617
+ 3. Report the result to the HR
618
+ """)
619
+
620
+ except Exception as e:
621
+ print(f"DEBUG: Error occurred: {str(e)}") # Debug
622
+ print(f"DEBUG: Error type: {type(e)}") # Debug
623
+ import traceback
624
+ print(f"DEBUG: Full traceback: {traceback.format_exc()}") # Debug
625
+ st.error(f"An error occurred: {str(e)}")
626
+ st.error("Please try again or contact support.")
627
+
628
+ # Reset button
629
+ if st.sidebar.button("Reset Application"):
630
+ for key in st.session_state.keys():
631
+ if key != 'openai_api_key':
632
+ del st.session_state[key]
633
+ st.rerun()
634
+
635
+ if __name__ == "__main__":
636
+ main()